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 usings in Program.cs/Startup.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.IdentityModel.Tokens;