We are rebuilding a Web API with .Net 6.
When adding in the authentication we need two authentication schemes, JWT and a custom token scheme.
The custom authentication scheme is used to pull out a token (a randomly generated alphanumeric string) from the header, check it against a stored value in the database and validate the expiry date. This is required to support the old authentication method in the previous API as downloaded apps are still connecting this way.
services
.AddAuthentication()
.AddJwtBearer()
.AddScheme<CustomAuthenticationOptions, CustomAuthentication>(
"CustomAuthentication",
"Custom Authentication",
options => {}
);
When registering the middleware we want to add some custom roles from our database, so have a custom middleware after authentication but before the roles have been authorized.
app.UseAuthentication();
app.UseMiddleware<AuthMiddleware>();
app.UseAuthorization();
This works great for the JWT Bearer authentication but doesn't for the custom authentication scheme.
The AuthMiddleware gets invoked before our CustomAuthentication does, meaning we are not authenticated before we add custom roles.
If we move the app.UseMiddleware<AuthMiddleware>(); after app.UseAuthorization(); then the CustomAuthentication is called before AuthMiddleware but the roles have already been authorized and the API returns unauthorized as expected.
Question
Why does AddJwtBearer() call authentication and middleware at the correct time, but my custom scheme does not? Or is there a better way to do custom authentication?
Simplified files for reference
CustomAuthentication.cs
public class CustomAuthentication : AuthenticationHandler<CustomAuthenticationOptions>
{
public CustomAuthentication(
IOptionsMonitor<CustomAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock
) : base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// This is called too late
}
}
AuthMiddleware.cs
public class AuthMiddleware
{
private readonly RequestDelegate _next;
public AuthMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
if ((httpContext.User?.Identity?.IsAuthenticated).GetValueOrDefault())
{
// This is not authenticated for custom authentication
}
await _next(httpContext);
}
}
I would refactor your custom roles function from AuthMiddleware into a new service. Removing your AuthMiddleware entirely.
Then hook the jwt bearer OnTokenValidated event to add role claims to the authenticated principal;
services.AddAuthentication()
.AddJwtBearer(options => {
if (options.Events == null)
options.Events = new();
var validate = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context => {
var service = context.HttpContext.RequestServices.GetService<...>();
await service.AddDbRoles(context.Principal);
};
})
With a similar implementation within your CustomAuthentication handler.
Related
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 implemented the default single page application template in Visual studio 2019, because I figured it would be the easiest way to implement a good authorization for a webApi. However the Token function, to obtain a bearer token for a webApi returns following error when called through postman.
"error": "Invalid_client"
I bet I am probably missing some config setting that I need to enable, but some extensive google searching did not bring up any working results. Anyone knows what is still missing to actually make this work?
Default Code behind is the Startup.Auth.cs
static Startup()
{
PublicClientId = "Web";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/Account/Authorize"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
app.bunchofotherregistrationsfordefaultapp();
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
I definitely not recommend to use SPA template for set up WebApi authorization, but if you want to...
To accomplish this task you need to override 2 methods in your ApplicationOAuthProvider (inherited from OAuthAuthorizationServerProvider class):
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated(); // Set up your context to be valid every time
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); // Disable CORS policies
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("email", context.UserName)); // Add required claims you want to encrypt in bearer token
context.Validated(identity); // Return valid token
}
You can change validation or grant resources logic in these two methods to accomplish your authorization workflow. At least without any additional conditions we can receive valid access token.
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 inherited an ASPnetzero application that uses both a Web API and a MVC front-end. The API authenticated via Bearer and the front-end via AbpIdentity (Cookies). A couple of days ago I got brave and decided to update my nuGet packages. The update came with an upgrade from .netCore v1 to v2. But I had some difficulties with the authentication after the JwtBearer middleware became obsolete. I could authenticate using the cookies, but not using Bearer Tokens.
I tried almost everything. Using multiple authentication methods meant that only one worked at a time.
In Startup.cs I had the following (snippets):
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddAbpIdentity<Tenant, User, Role>()
.AddUserManager<UserManager>()
.AddRoleManager<RoleManager>()
.AddSignInManager<SignInManager>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AuthConfigurer.Configure(app, _appConfiguration);
}
This is however a self-answer question and I'm hoping that I'll help anyone with similar or equal cases, since I've put together my own solution. The idea was to get the application working with a Bearer token (only when using the API) and cookies (only when using the MVC).
I also had a challenge as the MVC did XHR calls to the API to get data to display on the front-end. This meant that the API also needed to work with Cookies (but only for MVC users).
So I finally figured it out and it required quite a bit of transformation. The result was that:
API users only authenticated with a Bearer Token
MVC users authenticated with Cookies and the same authentication was used for the API calls in the application after they logged in.
All of the changes were made in Startup.cs, and I also commented out the reference to the AuthConfigure.cs file, which is now obsolete. I am open to any improvements or suggestions to the solution.
The important pieces in the Startup.cs file:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
// The application URL is used for the Issuer and Audience and is included in the appsettings.json
ValidIssuer = _appConfiguration["Authentication:JwtBearer:Issuer"],
ValidAudience = _appConfiguration["Authentication:JwtBearer:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appConfiguration["Authentication:JwtBearer:SecurityKey"]))
};
});
// Activate Cookie Authentication without Identity, since Abp already implements Identity below.
services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/Login");
// Add the Authentication Scheme Provider which will set the authentication method based on the kind of request. i.e API or MVC
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthenticationSchemeProvider, CustomAuthenticationSchemeProvider>();
// Some of these extensions changed
services.AddAbpIdentity<Tenant, User, Role>()
.AddUserManager<UserManager>()
.AddRoleManager<RoleManager>()
.AddSignInManager<SignInManager>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
//…
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// app.UseAuthentication is critical here
app.UseAuthentication();
app.UseAbp(); //Initializes ABP framework.
app.UseCors("CorsPolicy");
//…
//AuthConfigurer.Configure(app, _appConfiguration);
//…
}
}
public class CustomAuthenticationSchemeProvider : AuthenticationSchemeProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomAuthenticationSchemeProvider(
IHttpContextAccessor httpContextAccessor,
IOptions<AuthenticationOptions> options)
: base(options)
{
this.httpContextAccessor = httpContextAccessor;
}
private async Task<AuthenticationScheme> GetRequestSchemeAsync()
{
var request = httpContextAccessor.HttpContext?.Request;
if (request == null)
{
throw new ArgumentNullException("The HTTP request cannot be retrieved.");
}
// For API requests, use authentication tokens.
var authHeader = httpContextAccessor.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader?.StartsWith("Bearer ") == true)
{
return await GetSchemeAsync(JwtBearerDefaults.AuthenticationScheme);
}
// For the other requests, return null to let the base methods
// decide what's the best scheme based on the default schemes
// configured in the global authentication options.
return null;
}
public override async Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultAuthenticateSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultChallengeSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultForbidSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultForbidSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultSignInSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultSignInSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultSignOutSchemeAsync();
}
I am trying to have my own custom authentication for my server. But it is called for every endpoint even if it has the [AllowAnonymous] attribute on the method. With my current code, I can hit my breakpoint in the HandleAuthenticateAsync method everytime, even on the allow anonymous functions.
The AddCustomAuthentication adds the authenticationhandler correctly
public void ConfigureServices(IServiceCollection services)
{
//services.AddAuthorization();
services.AddAuthentication(options =>
{
// the scheme name has to match the value we're going to use in AuthenticationBuilder.AddScheme(...)
options.DefaultAuthenticateScheme = "scheme";
options.DefaultChallengeScheme = "scheme";
})
.AddCustomAuthentication(o => { });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
...
public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthenticationOptions>
{
public RvxAuthenticationHandler(
IOptionsMonitor<RvxAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var token = Request.Headers["token"].ToString();
if (string.IsNullOrWhiteSpace(token))
{
return AuthenticateResult.Fail("Invalid Credentials");
}
return AuthenticateResult.Success(new AuthenticationTicket(new System.Security.Claims.ClaimsPrincipal(), "Hi"));
}
Add this to the top of your HandleAuthenticateAsync method
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var endpoint = Context.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
{
return Task.FromResult(AuthenticateResult.NoResult());
}
....
}
This is what Microsoft use under the covers in the AuthorizeFiler - https://github.com/dotnet/aspnetcore/blob/bd65275148abc9b07a3b59797a88d485341152bf/src/Mvc/Mvc.Core/src/Authorization/AuthorizeFilter.cs#L236
It will allow you to use the AllowAnonymous attribute in controllers to bypass your custom AuthenticationHandler.
This is how it is designed to work.
Authentication step is executed for every incoming call by the ASP.Net middleware added by your app.UseAuthentication() call. The step only sets up an instance of IPrincipal to the request.
If authentication succeeds, the request gets the IPrincipal that you pass to the AuthenticationTicket.
If it fails, the request gets an unauthenticated IIdentity in its IPrincipal (principal.Identity.IsAuthenticated will be false)
Then the request will still be passed to the next middleware and eventually to your endpoint method.
It's the AuthorizeAttribute that will prevent the request from reaching protected methods, not any AuthenticationHandler<T>.
I guess Your problem is different, After exiting from the
HandleAuthenticateAsync method it can not find the endpoint:
because the address is not correct
or it goes in error before exiting the HandleAuthenticateAsync method.
(For example on Request.Headers["Authorization"] as the header "Authorization" does not exists : fires an Exception.).
Recommendation:
Use Shlager UI to test the Api first.