SamBC
SamBC

Reputation: 189

.NET Core Authorize attribute with external JWT authentication microservice?

So I'm just having a bit of trouble getting my head round the .NET Core [Authorize] attribute.

I have an authentication service running (let's say authapi.com) which when provided with valid authentication details will return a JWT. When this JWT is given back to it, it will validate the JWT and return a message indicating such.

So, I'm now building another WebAPI (let's say genericapi.com)which will require authorization for some of the actions/controllers. The idea being, the JWT will be passed in the headers of the request to genericapi which then needs to pass those on to authapi.com to validate them.

I tried adding a Policy but it got convoluted really quick, and I had to write [Authorize(Policy="TokenValid")] on everything, when I'd rather just the default [Authorize] did this, since ALL authorization will have to hit authapi.

How would I go about getting that JWT from the header and passing it to the authapi as standard behaviour for [Authorize]?

Bear in mind: I don't want to do anything with the JWTs on genericapi, all authentication is to be handled by authapi.

Upvotes: 2

Views: 3622

Answers (3)

Jtheuner
Jtheuner

Reputation: 1

I know this thread is over 7 years old, but i had the same problem found this and wasnt quite happy with writing my custom middleware. I finally found what i was looking for an it was this:

builder.Services.AddAuthentication()
   .AddCookie()
   .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
                 options =>
                 {
                   options.Authority = builder.Configuration["Jwt:authority"];
                   options.Audience = builder.Configuration["Jwt:audience"];

                   options.TokenValidationParameters = new TokenValidationParameters
                   {
                     ValidateAudience = false,
                   };

                   options.RequireHttpsMetadata = false;
                   options.IncludeErrorDetails = true;
                 })

By setting the authority u can let another service validate the token (or not) and can use [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] on your controllers.

Upvotes: 0

adem caglin
adem caglin

Reputation: 24123

As far as i understand you don't want to use JwtBearerAuthentication in genericapi. In this case you can write custom authentication middleware(send jwt to authapi and validate it, then set current user) for genericapi. Then just use [Authorize] attribute.

To write custom authentication middleware take a look at https://stackoverflow.com/a/37415902/5426333

However if possible, i wouldn't go with your way. I would use JwtBearerAuthentication for genericapi. Then i would use OnTokenValidated event to handle other validations.

        app.UseJwtBearerAuthentication(new JwtBearerOptions()
        {
            Events = new JwtBearerEvents()
            {
                OnTokenValidated = (context) =>
                {
                    // send jwt to auth api
                    // validate it
                    if (!valid)
                    {
                        context.SkipToNextMiddleware();
                    }
                    return Task.FromResult(0);
                }
            }
        });

Upvotes: 1

Federico Dipuma
Federico Dipuma

Reputation: 18295

You may try to customize the default JwtBearerAuthenticationMiddleware providing to it a custom ISecurityTokenValidator. Your user identity will be automatically set by the middleware, and you may continue using the Authorize attribute inside MVC:

class MyTokenValidator : ISecurityTokenValidator
{
    public string AuthenticationScheme { get; }

    public MyTokenValidator(string authenticationScheme)
    {
        AuthenticationScheme = authenticationScheme;
    }

    public bool CanValidateToken => true;

    public int MaximumTokenSizeInBytes
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public bool CanReadToken(string securityToken) => true;

    public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        validatedToken = null;

        //your logic here
        var response = GetResponseFromMyAuthServer(securityToken);
        //assuming response will contain info about the user

        if(response == null || response.IsError)
            throw new SecurityTokenException("invalid");

        //create your identity by generating its claims
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, response.UserId),
            new Claim(ClaimTypes.Email, response.Email),
            new Claim(ClaimsIdentity.DefaultNameClaimType, response.UserName),
        };

        return new ClaimsPrincipal(new ClaimsIdentity(claims, AuthenticationScheme));
    }
}

And in your startup class:

var options = new JwtBearerOptions();
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new MyTokenValidator(options.AuthenticationScheme));

app.UseJwtBearerAuthentication(options);

//the rest of your code here
app.UseMvc();

You may need to further refine this approach, but this way you can achieve what you need by delegating all the validation to the remote endpoint.

Upvotes: 2

Related Questions