Scorpio
Scorpio

Reputation: 97

Issue with custom Authentication filter asp.net core

I'm trying to create a custom authentication filter in ASP.NET Core. Need to use it in the controller to authenticate the JWT provided to me and create a Claim Principal. However when I place the authentication tag above the controller, nothing happens and the controller is getting processed without the authentication.

The following are the steps which were done:

  1. Added the app.UseAuthentication() in the startup.cs under
    Configure(IApplicationBuilder app, IHostingEnvironment env)
     {
        ......
        ......
        app.UseAuthentication();
     }
  1. Created a new class file ProcessAuth in the same project, containing the AuthenticationAsync and ChallengeAsync
   public class ProcessAuth : Attribute, IAuthenticationFilter
    {
        public bool AllowMultiple { get { return false; } }


        public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
        {            
            HttpRequestMessage request = context.Request;
            AuthenticationHeaderValue authorization = request.Headers.Authorization;
            
            // More code to be added for validating the JWT
        }

        public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
        {
            throw new NotImplementedException(); //sample code
        }
    } 
  1. Addded a reference of this new file in the controller
  2. Placed the tag [ProcessAuth] at the top of the controller
    [ProcessAuth]
    [Route("api/[controller]")]
    [ApiController] 
  1. Used Postman to send a JSON data, along with the Authorization Header containing a valid JWT token as "Bearer "
  2. The code just ignores the filter and processes code in the controller and returns the result

More info: If I add [Authorize] to the controller, Postman just returns a 401 Unauthorized error

Also checked this URL, but couldn't find the issue.

Update: I checked the answers to the similar Stack Overflow questions and followed the same but still the issue remains the same.

Nuget Packages installed: Microsoft.AspNet.WebApi.Core and also Microsoft.AspNet.WebApi

Namespaces used:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Controllers;

Similar Issue - Link

How do I get this to work? Am I missing something?

Upvotes: 1

Views: 3933

Answers (1)

Brando Zhang
Brando Zhang

Reputation: 27962

According to your codes, I found you have used asp.net authentication filter in asp.net core application. This is will not work.

In asp.net core, we should use JWT bear authentication middleware to achieve your requirement.

You could create custom OnChallenge to validate the jwt token and OnTokenValidated to add the claims.

More details, you could refer to below codes:

        services.AddAuthentication(auth =>
        {
            auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(token =>
        {
            token.RequireHttpsMetadata = false;
            token.SaveToken = true;
            token.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                //Same Secret key will be used while creating the token
                IssuerSigningKey = new SymmetricSecurityKey(SecretKey),
                ValidateIssuer = true,
                //Usually, this is your application base URL
                ValidIssuer = "http://localhost:45092/",
                ValidateAudience = true,
                //Here, we are creating and using JWT within the same application.
                //In this case, base URL is fine.
                //If the JWT is created using a web service, then this would be the consumer URL.
                ValidAudience = "http://localhost:45092/",
                RequireExpirationTime = true,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            };

            token.Events = new JwtBearerEvents { 
                
                 OnChallenge = async ctx => { 
                 
                 },
                  OnTokenValidated = async ctx =>
                  {
                      //Get the calling app client id that came from the token produced by Azure AD
                      string clientId = ctx.Principal.FindFirstValue("appid");

                      //Get EF context
                      //var db = ctx.HttpContext.RequestServices.GetRequiredService<AuthorizationDbContext>();

                      //Check if this app can read confidential items
                      bool canReadConfidentialItems = await db.Applications.AnyAsync(a => a.ClientId == clientId && a.ReadConfidentialItems);
                      if (canReadConfidentialItems)
                      {
                          //Add claim if yes
                          var claims = new List<Claim>
                {
                    new Claim("ConfidentialAccess", "true")
                };
                          var appIdentity = new ClaimsIdentity(claims);

                          ctx.Principal.AddIdentity(appIdentity);
                      }
                  }
            };
        });

Edit:

You could create the AuthenticationHandler and AuthenticationSchemeOptions class like below and register the class in the startup.cs. Then you could use [Authorize(AuthenticationSchemes = "Test")] to set the special AuthenticationSchemes.

More details, you could refer to below codes sample:

public class ValidateHashAuthenticationSchemeOptions : AuthenticationSchemeOptions
{

}

public class ValidateHashAuthenticationHandler
: AuthenticationHandler<ValidateHashAuthenticationSchemeOptions>
{
    public ValidateHashAuthenticationHandler(
        IOptionsMonitor<ValidateHashAuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        //TokenModel model;

        // validation comes in here
        if (!Request.Headers.ContainsKey("X-Base-Token"))
        {
            return Task.FromResult(AuthenticateResult.Fail("Header Not Found."));
        }

        var token = Request.Headers["X-Base-Token"].ToString();

        try
        {
            // convert the input string into byte stream
            using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(token)))
            {
                // deserialize stream into token model object
                //model = Serializer.Deserialize<TokenModel>(stream);
            }
        }
        catch (System.Exception ex)
        {
            Console.WriteLine("Exception Occured while Deserializing: " + ex);
            return Task.FromResult(AuthenticateResult.Fail("TokenParseException"));
        }

        //if (model != null)
        //{
        //    // success case AuthenticationTicket generation
        //    // happens from here

        //    // create claims array from the model
        //    var claims = new[] {
        //        new Claim(ClaimTypes.NameIdentifier, model.UserId.ToString()),
        //        new Claim(ClaimTypes.Email, model.EmailAddress),
        //        new Claim(ClaimTypes.Name, model.Name) };

        //    // generate claimsIdentity on the name of the class
        //    var claimsIdentity = new ClaimsIdentity(claims,
        //                nameof(ValidateHashAuthenticationHandler));

        //    // generate AuthenticationTicket from the Identity
        //    // and current authentication scheme
        //    var ticket = new AuthenticationTicket(
        //        new ClaimsPrincipal(claimsIdentity), this.Scheme.Name);

        //    // pass on the ticket to the middleware
        //    return Task.FromResult(AuthenticateResult.Success(ticket));
        //}

        return Task.FromResult(AuthenticateResult.Fail("Model is Empty"));
    }

}
public class TokenModel
{
    public int UserId { get; set; }
    public string Name { get; set; }
    public string EmailAddress { get; set; }
}

Startup.cs add below codes into the ConfigureServices method:

            services.AddAuthentication(options =>
            {
                options.DefaultScheme
                    = "Test";
            })
.AddScheme<ValidateHashAuthenticationSchemeOptions, ValidateHashAuthenticationHandler>
        ("Test", null);

Controller:

[Authorize(AuthenticationSchemes = "Test")]

Upvotes: 4

Related Questions