Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate AWS Cognito JWT in .NET Core Web API using .AddJwtBearer()

I was having some trouble figuring out how to go about validating a JWT given to the client by AWS Cognito inside my .NET Core Web API.

Not only could I not figure out what the variables for Microsoft.IdentityModel.Tokens.TokenValidationParameters were supposed to be, but once I finally did, I didn't know how to retrieve the JWT key set from https://cognito-idp.{region}.amazonaws.com/{pool ID}/.well-known/jwks.json

Finally, though a lot of random Googling and trial and error, I found a (seemingly-not-very-efficient solution) solution. However, I spent way too much time doing it. Citing that, plus the fact that AWS documentation on the subject is severely lacking, I decided to post this Q&A to help others find this solution more easily in the future.

If there's a better way to do this, somebody please tell me because I have yet to find a way to do this besides my answer listed below.

like image 983
foxtrotuniform6969 Avatar asked Nov 10 '18 23:11

foxtrotuniform6969


People also ask

How do you validate a JWT token Cognito?

To verify JWT claimsVerify that the token is not expired. The aud claim in an ID token and the client_id claim in an access token should match the app client ID that was created in the Amazon Cognito user pool. The issuer ( iss ) claim should match your user pool.

How can I decode and verify the signature of an Amazon Cognito JSON Web token?

To verify the signature of an Amazon Cognito JWT, search for the key with a key ID that matches the key ID of the JWT, then use libraries to decode the token and verify the signature. Be sure to also verify that: The token is not expired.

How do I know if my JWT token is valid?

Verify RS256-signed tokensGo to Dashboard > Applications. Go to the Settings view, and open Advanced Settings. Go to the Certificates view, locate the Signed Certificate field, and copy the Public Key. Navigate to the JWT.io website, locate the Algorithm dropdown, and select RS256.


1 Answers

This has been easily the most difficult bit of code I've had to work with in the last year. "Authenticating JWT tokens from AWS Cognito in a .NET Web API app". AWS documentation still leaves much to be desired.

Here's what I used for a new .NET 6 Web API solution (so Startup.cs is now contained within Program.cs. Adjust to fit your version of .NET if needed. Main difference vs .NET 5 and earlier is the Services object is accessed via a variable called builder, so anytime you see code like services.SomeMethod..., you can likely replace it with builder.Services.SomeMethod... to make it .NET 6-compatible):

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidIssuer = "https://cognito-idp.{aws region here}.amazonaws.com/{Cognito UserPoolId}",
            ValidateIssuerSigningKey = true,
            ValidateIssuer = true,
            ValidateLifetime = true,
            ValidAudience = "{Cognito AppClientId here}",
            ValidateAudience = false
        };

        options.MetadataAddress = "https://cognito-idp.{aws region here}.amazonaws.com/{Cognito UserPoolId here}/.well-known/openid-configuration";
    });

Note that I have ValidateAudience set to false. I was getting 401 Unauthorized responses from the .NET app otherwise. Someone else on SO has said they had to do this to get OAuth's Authentication/Authentication Code grant type to work. Evidently ValidateAudience = true will work just fine for implicit grant. Why are you using implicit grant in 2022 though?

Also note that I am setting options.MetadataAddress. Per another SO user, this apparently allows for behind the scenes caching of the signing keys from AWS that they rotate from time to time.

I was led astray by some official AWS documentation (boo) that had me using builder.Services.AddCognitoIdentity(); (services.AddCognitoIdentity(); for .NET 5 and earlier). Apparently this is for "ASP.NET" apps where the backend serves up the frontend (e.g. Razor/Blazor). Or maybe it's deprecated, who knows. It is on AWS's website so it could very well be deprecated...

As for the Controllers, a simple [Authorize] attribute at the class level sufficed. No need to specify "Bearer" as the AuthenticationScheme in the [Authorize] attribute, or create middleware.

If you want to skip having to add another using to every controller as well as the [Authorize] attribute, and you want every endpoint in every controller to require a JWT, you can put this in Startup/Program.cs:

builder.Services.AddControllers(opt =>
{
    var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
    opt.Filters.Add(new AuthorizeFilter(policy));
});

Make sure that in Program.cs (Startup.cs for .NET 5 and earlier) app.UseAuthentication comes before app.UseAuthorization().

Here are the usings in Program.cs/Startup.cs:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.IdentityModel.Tokens;
like image 84
jspinella Avatar answered Sep 20 '22 07:09

jspinella