Search code examples
authenticationjwtauthorizationbearer-token.net-7.0

WWW-Authenticate Bearer error="invalid_token", No Description


I have a test .NET 7 web api that I am using to test a class library of action filter attributes. My goal is to be able to use Postman to change claims to test the action filter attributes on the route. So, actual security is of no value. But, to get the claims from Postman into the _httpContextAccessor.HttpContext.User.Claims, I am attempting to generate a JWT token in Postman Pre-request script.

My Pre-request script is:

function base64url(source) {
  // Encode in classical base64
  encodedSource = CryptoJS.enc.Base64.stringify(source);

  // Remove padding equal characters
  encodedSource = encodedSource.replace(/=+$/, '');

  // Replace characters according to base64url specifications
  encodedSource = encodedSource.replace(/\+/g, '-');
  encodedSource = encodedSource.replace(/\//g, '_');

  return encodedSource;
}

// Set up the claims for the JWT token
var claims = { 
    "name": "john doe",
};

// Set up the JWT token options
var header = {
  "alg": "HS256",
  "typ": "JWT"
};

// Encode the header and payload as base64 strings
var encodedHeader = base64url(CryptoJS.enc.Utf8.parse(JSON.stringify(header)));
var encodedClaims = base64url(CryptoJS.enc.Utf8.parse(JSON.stringify(claims)));

// Create the token string by concatenating the encoded header, encoded claims, and signature
var token = encodedHeader + '.' + encodedClaims;
var signature = CryptoJS.HmacSHA256(token, 'this_secret_doesnt_matter');
var encodedSignature = base64url(signature);

token += '.' + encodedSignature;

// Save the token to the environment variable
pm.environment.set('jwt_token', token);

In the test web api, the Program.cs looks like:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.IncludeErrorDetails = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = false,
        ValidateIssuer = false,
        ValidateAudience = false,
        ClockSkew = TimeSpan.Zero
    };
});

builder.Services.AddHttpContextAccessor();

builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseHttpsRedirection();

app.UseAzureAppConfiguration();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

I have installed both of these nuget packages:

  • Microsoft.AspNetCore.Authentication.JwtBearer
  • System.IdentityModel.Tokens.Jwt

But, every time I make a call to the [Authorize] route, I get 401 Unauthorized and WWW-Authenticate Bearer error="invalid_token", error_description="The signature key was not found", is in the header. I'm not sure why the signature key is not found.

This is copied from the Request Header: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vbnMuZGRpd29ybGQuY29tL3dzLzIwMTEvMDMvaWRlbnRpdHkvY2xhaW1zL2NsaWVudGd1aWQiOiIxNjcwMTYyNy04OTY4LTRDOTItOTVGQy1ENzhDQjY3RjMxODQifQ.W_Xw0LLuFPbBShMuKkkSm0prGW4C29aGLiLOnE1aeZc


Solution

  • I was able to figure out the issue. It was a combination of things. I needed to utilize the base64url function that I added as per @jps's comment.

    function base64url(source) {
      // Encode in classical base64
      encodedSource = CryptoJS.enc.Base64.stringify(source);
    
      // Remove padding equal characters
      encodedSource = encodedSource.replace(/=+$/, '');
    
      // Replace characters according to base64url specifications
      encodedSource = encodedSource.replace(/\+/g, '-');
      encodedSource = encodedSource.replace(/\//g, '_');
    
      return encodedSource;
    }
    

    After that, I was still getting an invalid_token error, but with setting IncludeErrorDetails = true on the server-side, I got the additional description of error_description="The signature key was not found".

    To fix that, I updated the server-side code with IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("this_secret_doesnt_matter")), // This is set in Postman as well. in the TokenValidationParameters.

    I then got an error stating that the token did not have an expiration. So, I had to add "exp": Math.floor(Date.now() / 1000) + (60 * 10), // Expiration claim to the claims.

    This is what the code looks like now.

    Pre-request script:

    function base64url(source) {
      // Encode in classical base64
      encodedSource = CryptoJS.enc.Base64.stringify(source);
    
      // Remove padding equal characters
      encodedSource = encodedSource.replace(/=+$/, '');
    
      // Replace characters according to base64url specifications
      encodedSource = encodedSource.replace(/\+/g, '-');
      encodedSource = encodedSource.replace(/\//g, '_');
    
      return encodedSource;
    }
    
    // Set up the claims for the JWT token
    var claims = { 
        "name": "john doe",
        "exp": Math.floor(Date.now() / 1000) + (60 * 10), // Expiration claim
    };
    
    // Set up the JWT token options
    var header = {
      "alg": "HS256",
      "typ": "JWT"
    };
    
    // Encode the header and payload as base64 strings
    var encodedHeader = base64url(CryptoJS.enc.Utf8.parse(JSON.stringify(header)));
    var encodedClaims = base64url(CryptoJS.enc.Utf8.parse(JSON.stringify(claims)));
    
    // Create the token string by concatenating the encoded header, encoded claims, and signature
    var token = encodedHeader + '.' + encodedClaims;
    var signature = CryptoJS.HmacSHA256(token, 'this_secret_doesnt_matter');
    var encodedSignature = base64url(signature);
    
    token += '.' + encodedSignature;
    
    // Save the token to the environment variable
    pm.environment.set('jwt_token', token);
    

    Server-side code:

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.IncludeErrorDetails = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = false,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("this_secret_doesnt_matter")),  // This is set in Postman as well.
            ValidateIssuer = false,
            ValidateAudience = false,
            ClockSkew = TimeSpan.Zero
        };
    });
    
    builder.Services.AddControllers();
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    
    app.UseHttpsRedirection();
    
    app.UseAzureAppConfiguration();
    
    app.UseAuthentication();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();