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?
Related
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?
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();
}
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
Asp.Net Core 3.0
I am using the ASP.NET Core web application with Angular and Authentication (Individual User Accounts) template (from Visual Studio 2019).
My intention is to add some Custom Claims in the generated JWT and use them in browser.
In order to do that, I have extended the UserClaimsPrincipalFactory
public class MyCustomClaimsInjector : UserClaimsPrincipalFactory<ApplicationUser>
{
public MyCustomClaimsFactory(UserManager<ApplicationUser> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor)
{
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
{
var id = await base.GenerateClaimsAsync(user);
id.AddClaim(new Claim("my_claim1", "AdditionalClaim1"));
id.AddClaim(new Claim("my_claim2", "AdditionalClaim2"));
return id;
}
}
As well, I have registered the extension in the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddClaimsPrincipalFactory<MyCustomClaimsFactory>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
During Sign In phase, started from the SPA client, the debugger passes through MyCustomClaimsFactory and adds the claims to the ClaimsIdentity in the GenerateClaimsAsync method.
But, I find strange why the JWT received in browser does not contain the Claims added by the MyCustomClaimsFactory.
Is my expectation to see the Custom Claim in the JWT in browser OK ?
Can anyone suggest the direction to dig in... Why the claims isn't present in the JWT ?
Decoded JWT is:
The SPA app:
Will share my results. Hope that will help anyone else.
I have implemented IProfileService and piped the .AddProfileService<ProfileService>()implementation in ConfigureServices.
public class ProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
var claims = new List<Claim>
{
new Claim("my_FirstName", "user_FirstName"),
new Claim("my_LastName", "user_LastName")
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null);
}
}
the Startup.cs file
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddProfileService<ProfileService>();
With that, now the JWT contains my custom claims.
I am not sure why the override for UserClaimsPrincipalFactory was not able to solve that.
Will try to study deeper those areas.
So today I encountered the same problem. I haven't used IdentityServer before so I'm not totally sure if the following is the correct way of doing it, but it is certainly easier than creating your own IProfileService implementation.
The default profile service calls
context.AddRequestedClaims(principal.Claims);
The requested claims are defined on the IdentityServer ApiResource that you are accessing. This ApiResource configuration is created by calling
services.AddAuthentication()
.AddIdentityServerJwt();
This creates a default ApiResource configuration with the name "{Environment.ApplicationName}API".
This object also holds a collection of claim types to include when generating the JWT.
I couldn't find any documentation on whether this could also be set in the appsettings.json, but you can access it in the startup code.
TLDR; Change your startup code to something similar to this:
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
// ...
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddIdentityServer()
.AddApiAuthorization<User, ApplicationDbContext>(options =>
{
// The options.ApiResources collection is automatically populated
// by services.AddAuthentication().AddIdentityServerJwt();
var apiResource = options.ApiResources[$"{Environment.ApplicationName}API"];
// Example: add the user's roles to the token
apiResource.UserClaims.Add(JwtClaimTypes.Role);
// Example: add another custom claim type
apiResource.UserClaims.Add("CustomClaimName");
});
services.AddAuthentication()
.AddIdentityServerJwt();
// ...
}
}
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