Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET JWT: Signature validation failed. No security keys were provided to validate the signature

I've been making a web api in F#, mostly following this guide: https://www.blinkingcaret.com/2017/09/06/secure-web-api-in-asp-net-core/. However, I've been getting this error whenever I try to hit an authenticated endpoint in my aspnet webapi:

Failed to validate the token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiciIsImV4cCI6IjE1MjI2MzUwNDMiLCJuYmYiOiIxNTIyNTQ4NjQzIn0.VofLygSMitkmEsTBFNG-7-3jMAZYkyvfwc2UIs7AIyw.
    Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
       at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
       at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
       at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__6.MoveNext()

I've found similar issues linked here, but none whose resolution helped me. My Startup.fs looks like:

type Startup private () =
    new (configuration: IConfiguration) as this =
        Startup() then
        this.Configuration <- configuration

    // This method gets called by the runtime. Use this method to add services to the container.
    member this.ConfigureServices(services: IServiceCollection) =
        // Add framework services
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(fun options ->
            options.TokenValidationParameters = TokenValidationParameters (
                ValidateAudience = false,
                ValidateIssuer = false,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")), 
                ValidateLifetime = false, //validate the expiration and not before values in the token
                ClockSkew = TimeSpan.FromMinutes(5.0) //5 minute tolerance for the expiration date
            ) |> ignore
        ) |> ignore
        services.AddMvc() |> ignore
        services.AddSwaggerGen (fun c -> c.SwaggerDoc("v1", Swagger.Info()))  |> ignore
        services.AddCors() |> ignore


    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    member this.Configure(app: IApplicationBuilder, env: IHostingEnvironment) =
        app.UseExceptionHandler(
            fun options ->
                options.Run(
                    fun context ->
                        let ex = context.Features.Get<IExceptionHandlerFeature>()
                        match ex.Error with
                        | HttpCodedException (code, message) ->
                             printfn "code: %i, msg: %s" (int code) message
                             context.Response.StatusCode <- int code
                             context.Response.WriteAsync(message)
                        | exn -> raise (exn)
                )
        ) |> ignore

        // let cors = Action<CorsPolicyBuilder> (fun builder -> builder.WithOrigins("http://localhost:3000").AllowAnyHeader().AllowAnyMethod() |> ignore)
        app.UseCors(fun policy ->
            policy.AllowAnyHeader()
                    .AllowAnyOrigin()
                    .AllowCredentials()
                    .AllowAnyMethod()
                    .Build() |> ignore
        ) |> ignore

        app.UseAuthentication() |> ignore 

        app.UseMvc() |> ignore


    member val Configuration : IConfiguration = null with get, set

I've tried turning off basically all of the validation, so I'm confused why this is still failing. If it's helpful, the place where I generate the tokens looks like:

let GenerateToken (username) =
    let claims = [|
        Claim (ClaimTypes.Name, username)
        Claim (JwtRegisteredClaimNames.Exp, DateTimeOffset(DateTime.Now.AddDays(1.0)).ToUnixTimeSeconds().ToString())
        Claim (JwtRegisteredClaimNames.Nbf, DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString())
    |]
    let cred =
        new SigningCredentials(
            SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
            SecurityAlgorithms.HmacSha256
        )
    let token = JwtSecurityToken(JwtHeader(cred), JwtPayload(claims))
    JwtSecurityTokenHandler().WriteToken(token)

Hoping someone can see what I'm doing wrong.

like image 481
ssorl Avatar asked Apr 01 '18 02:04

ssorl


2 Answers

Finally figured this out. F# doesn't use = for assignment, it uses <-. So needed to change my service AddAuthenticaton call to:

    services.AddAuthentication(fun options ->
        options.DefaultScheme <- JwtBearerDefaults.AuthenticationScheme
        options.DefaultAuthenticateScheme <- JwtBearerDefaults.AuthenticationScheme 
        options.DefaultChallengeScheme <- JwtBearerDefaults.AuthenticationScheme
    ).AddJwtBearer(fun options ->
        options.TokenValidationParameters <- TokenValidationParameters (
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateIssuerSigningKey = false,
            IssuerSigningKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")), 
            ValidateLifetime = false, //validate the expiration and not before values in the token
            ClockSkew = TimeSpan.FromMinutes(5.0), //5 minute tolerance for the expiration date
            ValidateActor = false,
            ValidateTokenReplay = false
        )
    ) |> ignore

Now everything works fine.

like image 80
ssorl Avatar answered Oct 05 '22 23:10

ssorl


This has been working well for me.

JWT Authentication settings

services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.RequireHttpsMetadata = false;
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("thisKeyIs32CharactersLong1234567"))
        ValidateIssuer = true,
        ValidIssuer = "MyIssuer",
        ValidateAudience = true,
        ValidAudience = "MyAudience",
        ValidateLifetime = true,
        ClockSkew = TimeSpan.Zero
    };
});

And then to create the actual token

var handler = new JwtSecurityTokenHandler();
var securityToken = handler.CreateToken(
    new SecurityTokenDescriptor
    {
        Issuer = "MyIssuer",
        Audience = "MyAudience",
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes("thisKeyIs32CharactersLong1234567")), SecurityAlgorithms.HmacSha512Signature),
        Subject = new ClaimsIdentity(    
            new[] {
                new Claim(ClaimTypes.Name, "My Name"),
                new Claim(ClaimTypes.Sid, "My UID"),
                new Claim(ClaimTypes.GroupSid, "My GID")
            },
        Expires = DateTime.Now + TimeSpan.FromMinutes("30")                
    });

// Save token
handler.WriteToken(securityToken);

Hope it helps.

like image 41
Milan Vidakovic Avatar answered Oct 06 '22 01:10

Milan Vidakovic