4

I'm currently converting my Web API 2.0 to .NET Core Web API but there is one section I'm struggling with.

In my existing API, I have an attribute with the following code:

public class JwtAuthentication : Attribute, IAuthenticationFilter
{
    public string Realm { get; set; }

    public bool AllowMultiple => false;

    public async Task AuthenticateAsync(
        HttpAuthenticationContext context, 
        CancellationToken cancellationToken)
    {
        var request = context.Request;

        var authorization = request.Headers.Authorization;

        // checking request header value having required scheme "Bearer" or not.
        if (authorization == null ||
            authorization.Scheme.ToLowerInvariant() != "bearer" ||
            string.IsNullOrEmpty(authorization.Parameter))
        {
            context.ErrorResult = new AuthenticationFailureResult("JWT Token is Missing", request);
            return;
        }

        // Getting Token value from header values.
        var token = authorization.Parameter;
        var principal = await AuthJwtToken(token);

        if (principal == null)
        {
            context.ErrorResult = new AuthenticationFailureResult("Invalid JWT Token", request);
        }
        else
        {
            context.Principal = principal;
        }
    }

    private static bool ValidateToken(string token, out ICollection<Claim> claims)
    {
        claims = null;

        var simplePrinciple = JwtAuthManager.GetPrincipal(token);

        if (simplePrinciple == null)
        {
            return false;
        }

        var identity = simplePrinciple.Identity as ClaimsIdentity;

        if (identity == null)
        {
            return false;
        }

        if (!identity.IsAuthenticated)
        {
            return false;
        }

        var usernameClaim = identity.FindFirst(ClaimTypes.Name);
        var emailClaim = identity.FindFirst(ClaimTypes.Email);

        var username = usernameClaim?.Value;
        var email = emailClaim?.Value;

        if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(email))
        {
            return false;
        }

        claims = identity.Claims.ToList();

        return true;
    }

    protected Task<IPrincipal> AuthJwtToken(string token)
    {
        if (ValidateToken(token, out var claims))
        {
            var identity = new ClaimsIdentity(claims, "Jwt");

            IPrincipal user = new ClaimsPrincipal(identity);

            return Task.FromResult(user);
        }

        return Task.FromResult<IPrincipal>(null);
    }

    public Task ChallengeAsync(
        HttpAuthenticationChallengeContext context, 
        CancellationToken cancellationToken)
    {
        Challenge(context);
        return Task.FromResult(0);
    }

    private void Challenge(HttpAuthenticationChallengeContext context)
    {
        string parameter = null;

        if (!string.IsNullOrEmpty(Realm))
        {
            parameter = "realm=\"" + Realm + "\"";
        }

        context.ChallengeWith("Bearer", parameter);
    }
}

If I understand correctly, in ASP.NET Core, all I have to do is define the following in my startup:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>  
    {  
        options.TokenValidationParameters = new TokenValidationParameters  
        {  
            ValidateIssuer = true,  
            ValidateAudience = true,  
            ValidateLifetime = true,  
            ValidateIssuerSigningKey = true,  
            ValidIssuer = Configuration["Jwt:Issuer"],  
            ValidAudience = Configuration["Jwt:Issuer"],  
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))  
        };  
    });

and I'm not sure whether or not I'll need the below but it looks like it:

services.AddMvc(); 

and all I could do is use the [Authorize] attribute but what if I want to replicate the Attribute I used in my ASP.NET MVC Web API 2.0?

Should I? I like the fact that I can see where things have gone wrong with the token. If it can be used the same way and assuming it is OK to do so, how do I do this? I haven't found anything that would help when googling for a solution?

Thanks.

2 Answers 2

1

Based on @dropoutcoder answer,

As Events in options.Events is null, I was getting an error object reference not set... and to get around this problem I used the following instead:

options.Events = new JwtBearerEvents()
{
    OnMessageReceived = context =>
    {
        return Task.CompletedTask;
    },
    OnAuthenticationFailed = context =>
    {
        return Task.CompletedTask;
    },
    OnTokenValidated = context =>
    {
        return Task.CompletedTask;
    },
    OnChallenge = context =>
    {
        return Task.CompletedTask;
    },
    OnForbidden = context =>
    {
        return Task.CompletedTask;
    }
};
Sign up to request clarification or add additional context in comments.

Comments

0

I guess you don't want to reinvent the whole bearer token authentication wheel.

In case you'd like to customize how events are handled you can use JwtBearerOptions.Events Property to hook your own delegates to one or more of them. (OnAuthenticationFailed Property, OnChallenge Property, OnMessageReceived Property, OnTokenValidated Property).

Example failed authentication logging.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>  
    {  
        options.TokenValidationParameters = new TokenValidationParameters  
        {  
            ValidateIssuer = true,  
            ValidateAudience = true,  
            ValidateLifetime = true,  
            ValidateIssuerSigningKey = true,  
            ValidIssuer = Configuration["Jwt:Issuer"],  
            ValidAudience = Configuration["Jwt:Issuer"],  
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))  
        };

    options.Events.OnAuthenticationFailed = (context) =>
    {
        // Log failed authentication here

        // Return control back to JWT Bearer middleware
        return Task.CompletedTask;
    }
});

Hope it helps

2 Comments

Apologies for the delay. The events is exactly what I was looking for. I'll check the rest of them in a sec, but that's definitely a good start. Thanks again.
Actually I spoke to quickly. I'm getting an error System.NullReferenceException: 'Object reference not set to an instance of an object.' and upon reading it further on the Microsoft website, it appears to only be compatible up to .net core 2.2 unless their documentation is out of date. Will update later.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.