'Validate Google JWT with .NET Core 6 - Always ending up with a 401

My ASP.NET Core WebApi (.NET 6) should authenticate incoming requests using a JWT in their Bearer header.

JWTs are being issued by Google, so in Program.cs I have a custom token validator, i.e. GoogleTokenValidator:

builder.Services.AddAuthentication(o =>
{
    o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
    o.IncludeErrorDetails = true;
    o.SecurityTokenValidators.Clear();
    o.SecurityTokenValidators.Add(new GoogleTokenValidator());
    o.SaveToken = true;
});

Here's the validator code:

using Google.Apis.Auth;

// ..

    public class GoogleTokenValidator : ISecurityTokenValidator
    {
        // ...

        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
        {
            var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()).Result;

            var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.NameIdentifier, payload.Name),
                    new Claim(ClaimTypes.Name, payload.Name),
                    new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
                    new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName),
                    new Claim(JwtRegisteredClaimNames.Email, payload.Email),
                    new Claim(JwtRegisteredClaimNames.Sub, payload.Subject),
                    new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer),
                };

            var principal = new ClaimsPrincipal();
            principal.AddIdentity(new ClaimsIdentity(claims, "Google"));

            validatedToken = new JwtSecurityToken(securityToken); // it was = null;

            return principal;
        }
    }

Which is exactly what I see in this answer apart from the second-to-last line of code, where the original validatedToken = null; got HandleAuthenticateAsync() in JwtBearerHandler furiously mad and slapping me with 401s because of this check:

tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo);

Is it right to assign new JwtSecurityToken(securityToken) then? If so, why did the original null work?


ADDED Here's my full Program.cs:

using Core.API.Infrastructure.OAuth;
using Core.API.Infrastructure.WebApplicationBuilderServices;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Runtime.ExceptionServices;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddAuthentication(authenticationOptions =>
{
    authenticationOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    authenticationOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    authenticationOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtBearerOptions =>
{
    jwtBearerOptions.IncludeErrorDetails = true;
    jwtBearerOptions.SecurityTokenValidators.Clear();
    jwtBearerOptions.SecurityTokenValidators.Add(new GoogleTokenValidator());
    jwtBearerOptions.SaveToken = true;
});

builder.Services.AddSwagger();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source