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 here}",
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, however implicit grant is regarded as deprecated by most and you should try to avoid it if possible.
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 using
s in Program.cs/Startup.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.IdentityModel.Tokens;