rest.net-coreasp.net-core-webapibearer-tokenauthorize-attribute

Return a custom response when using the Authorize Attribute on a controller


I have just implemented the Bearer token and I have added the Authorize attribute to my controller class and that works fine. It looks like this:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

What I would like to do is to create a more complex response from the server when it fails, rather then the standard 401.

I tried filters but they are not invoked at all.

Any ideas how to do this?


Solution

  • Have a custom scheme, custom authorization handler and poof!

    Notice that I injected the Handler in ConfigureServices:

    services.AddAuthentication(options =>
                {
                    options.DefaultScheme = ApiKeyAuthenticationOptions.DefaultScheme;
                    options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme;
                })
                    .AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
                        ApiKeyAuthenticationOptions.DefaultScheme, o => { });
    

    ApiKeyAuthenticationOptions

    public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
        {
            public const string DefaultScheme = "API Key";
            public string Scheme => DefaultScheme;
            public string AuthenticationType = DefaultScheme;
            public const string HeaderKey = "X-Api-Key";
        }
    

    ApiKeyAuthenticationHandler

        /// <summary>
        /// An Auth handler to handle authentication for a .NET Core project via Api keys.
        ///
        /// This helps to resolve dependency issues when utilises a non-conventional method.
        /// https://stackoverflow.com/questions/47324129/no-authenticationscheme-was-specified-and-there-was-no-defaultchallengescheme-f
        /// </summary>
        public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
        {
            private readonly IServiceProvider _serviceProvider;
    
            public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, ILoggerFactory logger, 
                UrlEncoder encoder, ISystemClock clock, IServiceProvider serviceProvider) 
                : base (options, logger, encoder, clock) 
            {
                _serviceProvider = serviceProvider;
            }
    
            protected override Task<AuthenticateResult> HandleAuthenticateAsync() 
            {
                var token = Request.Headers[ApiKeyAuthenticationOptions.HeaderKey];
    
                if (string.IsNullOrEmpty(token)) {
                    return Task.FromResult (AuthenticateResult.Fail ("Token is null"));
                }
    
                var customRedisEvent = _serviceProvider.GetRequiredService<ICustomRedisEvent>();
                var isValidToken = customRedisEvent.Exists(token, RedisDatabases.ApiKeyUser);
    
                if (!isValidToken) {
                    return Task.FromResult (AuthenticateResult.Fail ($"Invalid token {token}."));
                }
    
                var claims = new [] { new Claim ("token", token) };
                var identity = new ClaimsIdentity (claims, nameof (ApiKeyAuthenticationHandler));
                var ticket = new AuthenticationTicket (new ClaimsPrincipal (identity), Scheme.Name);
                return Task.FromResult (AuthenticateResult.Success (ticket));
            }
        }
    

    Focus on the handler class. Apart from the sample code I've provided, simply utilise the base class properties like Response to set your custom http status code or whatever you may need!

    Here's the derived class if you need it. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhandler-1?view=aspnetcore-3.1