Getting Azure Active Directory roles for a logged in user - c#

In my .Net Core Web application, I've added a check for a view to be displayed only when an user with an "Admin" role logs in using Azure AD authentication. This is the code in Startup.cs file.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddRazorPages();
}
for the check of User role, I'm using following code:
if (User.IsInRole("Admin"))
{
haveAccess = true;
}
but even if I login with a user which is having other role assigned than "Admin", like "Sales", still the variable haveAccess returns true ,i.e User.IsInRole("Admin") always returns true.
Is there any other way to get roles assigned to user from Azure AD in .Net Core?

Related

Cannot add Identity to services inside HostBuilder

For context I am coming onto a new project that is using ASPNET Core 7. I am trying to create a custom authorizationHandler for Role management. This part seems to work it fires my custom handler but the user context has no information in it. No claims no nothing. I am impersonating users from the database. After reading the documentation from what I understand the problem is I need to use AddIdentity or AddIdentityCore and add this to services, I found examples of this but never inside this type of inline scaffolding. So adding this inside the configureservices block seems to throw an out of scope error.
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory1[Microsoft.AspNetCore.Identity.IdentityUser] Lifetime: Scoped ImplementationType: Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory1[Microsoft.AspNetCore.Identity.IdentityUser]': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager1[Microsoft.AspNetCore.Identity.IdentityUser]'.) (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.UserManager1[Microsoft.AspNetCore.Identity.IdentityUser] Lifetime: Scoped ImplementationType: Microsoft.AspNetCore.Identity.UserManager1[Microsoft.AspNetCore.Identity.IdentityUser]': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager1[Microsoft.AspNetCore.Identity.IdentityUser]'.)'
Not sure how I can keep the current "WebHostBuilder" scaffolding and include Identity so my Claims are populated.
//program.cs
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseIISIntegration().UseStartup<Startup>();
})
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IAuthorizationHandler, RoleHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("Roles", policy =>
{
policy.Requirements.Add(new RoleAuthRequirement { Role = "Role1" });
policy.Requirements.Add(new RoleAuthRequirement { Role = "Role2" });
policy.Requirements.Add(new RoleAuthRequirement { Role = "Role3" });
});
});
services.AddIdentity<IdentityUser, IdentityRole>(); //throws out of scope ERROR!! same as AddIdentityCore
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.LoginPath = new PathString("/login");
options.AccessDeniedPath = new PathString("/denied");
});
});
When logging in I am grabbing the users role from the database and adding it as a Claim
//Login Action
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userObj.UserId.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Name, userObj.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, userObj.Role));
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
After this in my Authorization handler I cant see any claims the user is null I also cant use User.IsInRole() to verify since the user context is essentially empty.
public class RoleHandler : AuthorizationHandler<RoleAuthRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleAuthRequirement requirement)
{
var requiredRole = requirement.Role;
var hasRole = context.User.IsInRole(requiredRole);
if (hasRole)
{
context.Succeed(requirement);
}
else
{
context.Fail(new AuthorizationFailureReason(this, $"User Role {requirement.Role} missing"));
}
return Task.CompletedTask;
}
}
You didn't regist the dbcontext which is required in Identity,Try Regist Identity as below :
services.AddDbContext<SomeContext>(options =>
options.UseSqlServer(connectionString));
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<SomeContext>();
And After checking your codes,it seems that you are trying Login Attemps with CookieAuthentication(Not Identity) ,Is Identity Really necessary on your side?

Preventing AuthenticationState from removing claims added to ClaimsPrincipal

I am working on a Blazor Server application that uses Window Authentication through IIS and I'm having issues with updating the current Claims Principal with custom claims. Once the app has authenticated the user I have planned to use the authentication information to check against Active Directory and assign claims based on their AD Group Memberships.
I set up an IClaimsTransformation Service and while the TransformAsync Task is called successfully, each time it is called the ClaimsPrincipal has been reverted back to the original WindowsIdentity and any changes are lost. As long as everything is reapplied the claims do work after the last time transformAsync runs but I would like to see if there is a way to persist the changes to avoid the need to constantly reauthorize or manage a separate user object to store .
I have tried the following
Setting the service as both a scoped and a singleton
Modifying the WindowIdentity provided with the initial authentication
adding an additional claims identity to the ClaimsPrincipal
adding Claims as CI.CustomRoleType as well as just setting the Type and Value
My IClaimsTransormation Class:
public class UserAuthorizationService : IClaimsTransformation {
private readonly IConfiguration _configuration;
public UserAuthorizationService(IConfiguration configuration) {
_configuration = configuration;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) {
var claimsIdentity = new ClaimsIdentity();
if (!principal.HasClaim("TestClaim", "Test")) {
Claim customClaim = new Claim("TestClaim", "Test");
claimsIdentity.AddClaim(customClaim);
}
principal.AddIdentity(claimsIdentity);
return Task.FromResult(principal);
}
}
Related Startup class configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
});
services.AddAuthorization(config => {
config.AddPolicy("TestPolicy", policy => policy.RequireClaim("TestClaim", "Test"));
});
services.AddScoped<IClaimsTransformation, UserAuthorizationService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
}

accessing AD user through HttpContext using .NET Core 2.2

I have searched for a solution online but to no avail - I am trying to access logged-in AD user through HttpContext in .NET Core 2.2 for an intranet application...
I saw a similar problem (link below)and have implemented their solution but my context is still null:-
Link to similar problem
appsettings.json
StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
services.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = true;
});
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthorization(options =>
{
options.AddPolicy("ADGroup", policy =>
policy.Requirements.Add(new UserHelper.CheckADGroupRequirement(Configuration["SecuritySettings:ADGroup"])));
});
services.AddSingleton<IAuthorizationHandler, UserHelper.CheckADGroupHandler>();
}
I have implemented the CheckADGroupHandler in the same way as the solution in the link
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Screenshot showing context with null value:-
HomeController.cs
[Authorize(Policy = "ADGroup")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
I am missing something but can't figure out what - any pointers appreciated...
According to the documentation for Windows Authentication with HTTP.sys, you're missing the call to UseHttpSys() in your Program.cs:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseHttpSys(options =>
{
options.Authentication.Schemes =
AuthenticationSchemes.NTLM |
AuthenticationSchemes.Negotiate;
options.Authentication.AllowAnonymous = false;
})
.Build();
Windows Authentication is much easier to implement if you're running your app behind IIS, but if you're using HTTP.sys because you cannot use IIS, then it's worth noting that if you're able to upgrade to ASP.NET 3.0, you can now use Windows Authentication with Kestrel.
It turned out to be an issue within IIS Express...combined with upgrading Framework to
.NET Core 3.0

Using dependency injection to replace JWT Bearer Options in ASP.NET Core

In the web API, I'm securing with Jwt Auth, I have the following ConfigureServices Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection")));
// Some additional application dependencies here with AddTransient()...
services.AddMvc();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "xxxxx";
options.RequireHttpsMetadata = false;
options.Audience = "xxxxx";
options.BackchannelHttpHandler = BackChannelHandler;
});
}
Here, BackChannelHandler is a public property of the Startup.cs:
public static HttpMessageHandler BackChannelHandler { get; set; }
The reason why I'm using this property is that when I'm doing integration testing using xUnit, I'm using in-memory TestServer from Microsoft.AspNetCore.TestHost. So, I need to register backchannel handler in TestFixture class like
testIdentityServer = new TestServer(identityServerBuilder);
My.Project.Startup.BackChannelHandler = testIdentityServer.CreateHandler();
testApiServer = new TestServer(apiServerBuilder);
While this is working fine, I would like to override either the services.AddAuthentication() or AddJwtBearer() in the DI container, so I can inject the authentication middleware configuration for testing purposes instead of using a public static property just like how I would do with any other dependency. When I try to do this using:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "xxxxx";
options.RequireHttpsMetadata = false;
options.Audience = "xxxxx";
options.BackchannelHttpHandler = testIdentityServer.CreateHandler();
});
in TestFixture.cs I get the error: authentication scheme with "Bearer token" already exists.
How can I do this with correctly ASP.NET Core?
The behaviour you encounter is normal as you can't have 2 handlers for the same authentication scheme. Here, AddJwtBearer adds a handler for the authentication scheme Bearer, which value comes from JwtBearerDefaults.AuthenticationScheme.
The lambda expression you pass to the AddJwtBearer is registered as a named option configuration, the name it's registered against being the authentication scheme.
So here's what you could do in your test project:
services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.BackchannelHttpHandler = testIdentityServer.CreateHandler();
});
Doing this will not change the authentication schemes that are already registered in the container but will only modify the options associated with the JWT handler.

aspnetcore2.0 using services with AzureAd authentication

I'm about to migrate an ASP.NET Core MVC web app from 1.1 to 2.0. The app uses AzureAd for identity management.
In 1.1 I have handled the openidconnect events (like OnTokenReceiver, OnAuthorizationCodeReceived, OnRemoteFailure, etc) in Startup.cs (in Configure()), where I was able to use Dependency Injection. I have injected a lot of services, like EF db context and used them in the event handlers.
After upgrading to 2.0 I had to migrate the whole authentication to AzureAdAuthenticationBuilderExtensions's ConfigureAzureOptions class (which implements IConfigureNamedOptions<OpenIdConnectOptions> interface) where (as I saw) DI cannot be used.
So now only this is in Startup's ConfigureServices:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
I used this guide for the migration: https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x#authentication-middleware-and-services
Has somebody an idea how can be services used in openidconnect events?
UPDATE: I was able to fix this with the help of #Balah's answer. Basically the solution was to use the generic .AddOpenIdConnect() instead of creating an extension named .AddAzureAd().
A small addition to the answer: As the authentication part was moved from Configure() to ConfigureServices() where DI isn't enabled and services are not yet registered, the way to get those services after all is this:
var scopeFactory = services
.BuildServiceProvider()
.GetRequiredService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
var provider = scope.ServiceProvider;
var dbContext = provider.GetRequiredService<ApplicationDbContext>();
var graphSdkHelper = provider.GetRequiredService<IGraphSDKHelper>();
var memoryCache = provider.GetRequiredService<IMemoryCache>();
...
Keep in mind, that you have to add those services above this code!
You'll find that those events that you mentioned, have been moved into (the aptly named) Events property on the authentication options.
Accessing any services registered in the DI container can be done via the HttpContext.RequestServices property, like so:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(o =>
{
o.Events.OnAuthorizationCodeReceived = async ctx =>
{
var db = ctx.HttpContext.RequestServices.GetService<DbContext>();
await ...
};
});
You might need to add services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); because that's what I have too. But I suspect the above will work without it.
Here's an article that covers it nicely.
I'm using .net core 3.1 and I was facing a similar issue.
I think it can be made a bit cleaner by moving the authentication logic to a separate handler class as we want to keep the Startup.cs as tight as possible.
public class AzureAdOpendIdHandler : IConfigureNamedOptions<OpenIdConnectOptions>
{
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.UseTokenLifetime = true;
// The callback path located in AzureAd settings should match the callback path setup up in Azure portal
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.TokenValidationParameters = new TokenValidationParameters
{
// Ensure that User.Identity.Name is set correctly after login
NameClaimType = JwtRegisteredClaimNames.Email,
ValidateIssuer = false,
};
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async context =>
{
var dbContext = (HighEloDbContext)context.HttpContext.RequestServices.GetService(typeof(HighEloDbContext));
var acc = dbContext.Accounts.First(x => x.EmailAddress == userEmail);
...
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
};
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
}
And here is how my Starup.cs looks like:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => { Configuration.Bind(nameof(AzureAdConfig), options); });
//here comes registration of services, DAL contexts etc.
services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, AzureAdOpendIdHandler>();
}
Please notice that it works with .AddAzureAD

Categories