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.
Related
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.
defined an oauth handler like so which works just fine.
public class MyHandler : OAuthHandler<MyOptions> {
public MyHandler(IOptionsMonitor<MyOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock) { }
// overriden CreateTicketAsync and BuildChallengeUrl protected methods.
}
but the authenticator requires that user can revoke authorization which doesn't seem to be supported by oauth in a native fashion. so in the handler, I added a specialization which is to revoke authorization like so.
public class MyHandler : OAuthHandler<MyOptions> {
public async Task RevokeAuthorizationAsync(string token) {
if(token == null) throw new ArgumentNullException(nameof(token));
// below, Options throws a NullReferenceException???
var request = new HttpRequestMessage(HttpMethod.Post, Options.RevocationEndpoint);
// other relevant code here...
}
}
so when the user hits the revoke button from the app, the account controller revoke method is called.
public class AccountController : Controller {
[Authorize]
public async Task Revoke()
=> HttpContext.RevokeAuthorizationAsync("refreshTokenObtainedByWhateverMean");
}
// which in turn calls upon the HttpContext extension written expressly for the purpose
public static class MyHttpContextExtensions {
public static async Task RevokeAuthorizationAsync(this HttpContext context, string accessOrRefreshToken) {
var handler=context.RequestServices.GetRequiredService<MyHandler>();
await handler.RevokeAuthorizationAsync(accessOrRefreshToken);
}
}
upon the call to MyHandler.RevokeAuthorizationAsync, the OAuthHandler.Options is null? how can that be? when authenticating and authorizing from within the MyHandler.CreateTicketAsync and MyHandler.BuildChallengeUrl, the OAuthHandler.Options property is set.
I suspect that I might not instantiate the MyHandler class properly using the HttpContext.RequestServices.GetRequiredService method. but if this is not it, how could I specialize MyHandler and provide OAuthHandler.Options ? because I need the Options.ClientId and Options.ClientSecret to revoke the authorization. both are configured like so from the program class.
public class Program {
builder.Services
.AddAuthentication(o => {
// some config here
})
.AddCookie()
.AddMyAuthenticator(o => {
o.ClientId=Configuration["ClientId"];
o.ClientSecret=Configuration["ClientSecret"];
});
}
so how is that OAuthHandler.Options is instantiated within the MyHandler.CreateTicketAsync and MyHandler.BuildChallengeUrl, and is null when I call upon the MyHandler.RevokeAuthorizationAsync method?
In the web application I'm currently working on there is a requirement for all users to be authenticated. This is currently handled by using an AuthorizeFilter.
I now need to be able to apply different authorization policies to different parts of the application, and therefore I wanted to switch from using a global authorization filter to setting the fallback policy (as described in, and recommended by, the official documentation).
This works as intended, with the exception that requests for resources which don't exist now returns HttpStatusCode 401 if not authenticated, or 403 if authenticated but some other requirement is not fulfilled (and we have a few in the default/fallback policy). Previously, with the authorization filter solution, a 404 would be returned. I would guess the reason is that the fallback policy is evaluated earlier in the pipeline, than the authorization filter is, but it is still a side-effect I would like to avoid.
(How) Can I get the application to return 404s like before while utilizing the FallbackPolicy? I guess I could use a custom IAuthorizationMiddlewareResultHandler if the application was using net5.0 (or later), but an upgrade isn't in the short term plan which means the solution must work for netcoreapp3.1.
I've managed to get the result I was after while utilizing netcoreapp3.1 by adding a custom authorization handler which extends DenyAnonymousAuthorizationRequirement:
public class CustomDenyAnonymousAuthorizationRequirement : DenyAnonymousAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
{
if (context.Resource != null)
{
return base.HandleRequirementAsync(context, requirement);
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
This code just marks the requirement as fulfilled if there is no resource.
Add it the service collection like so:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IAuthorizationHandler, CustomDenyAnonymousAuthorizationRequirement>();
}
If using net5.0+ a custom IAuthorizationMiddlewareResultHandler can be used to accomplish the same:
public class CustomAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new();
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
if ((authorizeResult.Challenged || authorizeResult.Forbidden) && context.GetEndpoint() == null)
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
await _defaultHandler.HandleAsync(next, context, policy, authorizeResult);
}
}
And added to the service collection:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationMiddlewareResultHandler>();
}
To returns 404 with fallback policy:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapFallbackToController("api/{**slug}", nameof(ErrorController.Error404), "Error");
endpoints.MapFallbackToPage("{**slug}", "/Public/Errors/404");
});
And obviously the action decorated with AllowAnonymous
[Route("api/[controller]")]
[ApiController]
public class ErrorController : ControllerBase
{
[HttpGet]
[AllowAnonymous]
public IActionResult Error404()
{
return NotFound();
}
}
For razor pages:
builder.AddRazorPagesOptions(options =>
{
options.Conventions.AllowAnonymousToFolder("/Public");
Policies
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole(LoginEntities.Sede.ToString())
.Build();
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
To solve this problem where by applying FallBackPolicy we are no longer receiving 404,, we can make use of Middlewares.
public class TestMiddleware
{
private readonly RequestDelegate next;
public TestMiddleware(RequestDelegate requestDelegate)
{
next = requestDelegate;
}
public async Task Invoke(HttpContext httpContext)
{
await next(httpContext);
var ep = httpContext.GetEndpoint();
if(ep == null)
{
httpContext.Response.StatusCode = 404;
await httpContext.Response.WriteAsync("Not Found!!!");
}
}
}
Above is just a sample implementation of such middleware. We can add this middleware after UseEndpoints middleware in the pipeline.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseMiddleware<TestMiddleware>();
In the middleware code we are using GetEndPoints method on HttContext to understand whether the call actually mapped to any ActionMethod if not we can return 404. So we can have fallback policy and yet we can return 404 for Not found resources
I am trying to add a custom middleware to the pipeline (to be easier I will pick the .NET Core documentation example).
Let's say we want to have the Spanish culture set whenever a call to API is fired.
Here's the code which runs perfectly:
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
CultureInfo.CurrentCulture = new CultureInfo("es-ES");
CultureInfo.CurrentUICulture = new CultureInfo("es-ES");
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
and the Startup class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//here is our custom middleware!
app.UseRequestCulture();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
That's fine, but as you can see, the RequestCultureMiddleware does not implement an interface or a base class/abstract class. You just need to remember when defining a middleware to create a constructor that receives the next middleware and also you need to create a method called specifically "InvokeAsync" with "HttpContext" as a parameter.
I tried to find a contract... a base class or an interface and guess what, we have "IMiddleware" which is part of "Microsoft.AspNetCore.Http" assembly. Wow, that's perfect. Let's implement it.
The interface looks like this:
namespace Microsoft.AspNetCore.Http
{
//
// Summary:
// Defines middleware that can be added to the application's request pipeline.
public interface IMiddleware
{
//
// Summary:
// Request handling method.
//
// Parameters:
// context:
// The Microsoft.AspNetCore.Http.HttpContext for the current request.
//
// next:
// The delegate representing the remaining middleware in the request pipeline.
//
// Returns:
// A System.Threading.Tasks.Task that represents the execution of this middleware.
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
}
And here is the implementation:
public class RequestCultureMiddleware : IMiddleware
{
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
CultureInfo.CurrentCulture = new CultureInfo("es-ES");
CultureInfo.CurrentUICulture = new CultureInfo("es-ES");
// Call the next delegate/middleware in the pipeline
return next(context);
}
}
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
But, when running the API I am getting the following error at run-time:
System.InvalidOperationException: No service for type 'WebApplication1.RequestCultureMiddleware' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Http.MiddlewareFactory.Create(Type middlewareType)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass5_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
How exactly I am supposed to register this middleware if not by using the extension "UseMiddleware"?
Thanks.
I'm sure this problem has been solved long ago after 5 months, but I'm writing this advice just in case.
The problem is the "InvokeAsync" method of your custom middleware program is not be executed even though you built in it in "Configure" method of Startup.
I had the same problem the other day and solved it by, but I putting built in code right before the app.UseEndpoints method.
in your case
app.UseAuthorization();
app.UseRequestCulture(); // <- this way.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
By the way, if you put it after the app.UseEndpoints method, the constructor will be called, but the InvokeAsync method will not be executed.
You're using factory-based middleware. As described in those docs, you've missed an important step:
... the IMiddlewareFactory instance registered in the container is used to resolve the IMiddleware implementation instead of using the convention-based middleware activation logic. The middleware is registered as a scoped or transient service in the app's service container.
In your case, that registration would look something like this:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<RequestCultureMiddleware>();
}
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();
}