How to Validate JWT token generated from one app in another?

Viewed 3066

Firstly, thank you for taking the time to read this.

I have a need to validate JWT tokens generated by one .net core 2.2 application in another .net core 3.1 application.

Currently, I'm unable to use the .net core authorization to validate the token but am able to write a separate method to validate the token. I've ensured that the secret used to sign the tokens are the same.

How can I use the built in authentication in .net core 3.1 to validate the token generated from a different application.

Below is a detailed description:

I created an API for login requests that generates a JWT token for validation on .net core 2.2 using this tutorial.

In my Login API, at startup.cs, I've added authentication as follows:

        var appSettings = appSettingsSection.Get<AppSettings>();
        var key = Encoding.ASCII.GetBytes(appSettings.Secret);
        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(x =>
        {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };
        });

I generate the token like this:

        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new Claim[]
            {
                new Claim(ClaimTypes.Name, dbUser.UserID.ToString())
            }),
            Expires = DateTime.Now.AddMinutes(_appSettings.Expiry_Min),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
            Audience = _appSettings.Audience,
            Issuer = _appSettings.Issuer,
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);

The JWT token works great. In my controller, I add the [Authorize] attribute and requests with invalid tokens are immediately rejected.

Now, I want to validate the token being generated by my login API in another Application in .net core 3.1 to validate the login response. To do this, I referred to this tutorial. In the startup.cs of this second application, I have:

    private void SetupJWTServices(IServiceCollection services)
    {
        // configure strongly typed settings objects
        var appSettingsSection = Configuration.GetSection("AppSettings");
        services.Configure<AppSettings>(appSettingsSection);

        // configure jwt authentication
        var appSettings = appSettingsSection.Get<AppSettings>();
        var key = Encoding.ASCII.GetBytes(appSettings.Secret);

        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(x =>
        {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };
        });

This method, is called as follows:

    public void ConfigureServices(IServiceCollection services)
    {
        SetupJWTServices(services);            
        services.AddControllers();

        var appSettingsSection = Configuration.GetSection("AppSettings");
        services.Configure<AppSettings>(appSettingsSection);
    }

In my second application, when I use the [Authorize] attribute, the request is immediately rejected with 401, unauthorized. However, when I do not use the [Authorize] attribute and create a method to validate the token, the method is able to validate all valid requests. The method I wrote is:

    private void Validator(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var appSettingsSection = _appConfiguration.GetSection("AppSettings");
        var appSettings = appSettingsSection.Get<AppSettings>();
        var key = Encoding.ASCII.GetBytes(appSettings.Secret);
        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            var jwtToken = (JwtSecurityToken)validatedToken;

            // return account id from JWT token if validation successful
        }
        catch
        {
            // return null if validation fails
        }

This implies to me that to validate all requests, I need to first pass it through this validator method before I process anything. However, this is not using the built in authorization controls. It doesn't seem right.

Is this how JWT tokens are to be validated between different applications?

2 Answers

Use Claims to validated Token

static public bool ValidacionTokenManual(string Token, string secretKey, string audienceToken, string issuerToken)
    {
        TokenValidationParameters validationParameters = new TokenValidationParameters()
        {
            ValidAudience = audienceToken,
            ValidIssuer = issuerToken,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(secretKey))
        };

        SecurityToken validateToken;
        JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();

        if(handler.CanReadToken(Token))
        {
            var user = handler.ValidateToken(Token, validationParameters, out validateToken);

            string roles = user.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value;
            if(roles != "")
            {
                return true;
            }
        }

        return false;
    }

[1] You don't make any mention of how you are passing the JWT to your API endpoint. You have only shown the back-end config code.

You need to add an appropriate Authorization header to all client requests with the following format:

Authorization: Bearer {token}

Where {token} is your JWT. You use the AuthenticationHeaderValue class in .NET core 3.1 to do this (see below).

You can verify that the token is being added to the Authorization header correctly by using a tool such as Fiddler to sniff the http traffic: https://www.telerik.com/fiddler

You can validate the token signature manually (to ensure it correctly structured) using this site: https://jwt.io/

Sample client code (Razor Page) to add JWT to correct auth header (this is for .NET Core 3.1, so you may need to amend it slightly for 2.2)

Index.cshtml.cs

public class IndexModel : PageModel
{
    private static HttpClient Client = new HttpClient();

    [BindProperty]public string Token { get; set; }
    [BindProperty]public string Data { get; set; }
    [BindProperty]public HttpStatusCode HttpCode { get; set; }

    private async Task<string> GetAsync(string uri)
    {
        const string serverDomain = "https://localhost:44334/";
        string fullUri = $"{serverDomain}{uri}";

        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, fullUri);
        if (Token != null)
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);

        HttpResponseMessage response = await Client.SendAsync(request);
        HttpCode = response.StatusCode;
        string data = await response.Content.ReadAsStringAsync();

        return data;
    }

    public async Task OnGet()
    {
        // first get the token
        Token = await GetAsync("api/Main/gettoken");
        
        // now use it to call a method protected with [Authorize]
        Data = await GetAsync("WeatherForecast");
    }
}

Index.cshtml

@page
@model IndexModel
@{  ViewData["Title"] = "Home page"; }

<div class="text-center">
  <h1>Test Client</h1>

    <p>Token is @Model.Token</p>
    <p>HttpCode is @((int)Model.HttpCode) (@Model.HttpCode)</p>
    <p>Data is @Model.Data</p>
</div>

[2] Are you using ASP.NET Identity SigninManger?

If so you need to set the TokenValidationParameters.AuthenticationType correctly:

TokenValidationParameters.AuthenticationType = IdentityConstants.ApplicationScheme

If you supplied your client/front-end code, it would improve your chances of someone finding the problem.

I have tried the back-end code you have supplied, and it works fine for me on .NET 3.1 + Microsoft.AspNetCore.Authentication.JwtBearer v3.1.10

If you supplied an example token generated by your front-end, I can tell you whether it validates using the [Authorize] attribute.

Related