Francesco B.
Francesco B.

Reputation: 3107

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();

Upvotes: 0

Views: 1829

Answers (1)

Zaha
Zaha

Reputation: 946

UPDATE: I solve the problem without AddIdentityServer, the problem was in the oerder of

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

I don't know why, but the problem were this for me:

        app.UseAuthorization();//<-- Should be after UseAuthentication
        app.UseAuthentication();

My Workaround services app.AddIdentityServer() and using app.UseIdentityServer()

Program class

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) {
        return Host.CreateDefaultBuilder(args)
            .UseLamar()
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
   }
}




  // Startup ConfigureContainer (Similar to configure service)   
//I 'am using Lamar, so for that the ServiceRegistry (Maybe could be the problem xd, [I don't think so it])

     private readonly MultiSocialTokenValidator multiSocialTokenValidator = new();

     public void ConfigureContainer(ServiceRegistry services)
           {
       ...

            //Is not necesary 
            /*
            services
              .AddIdentityServer()
              .AddInMemoryIdentityResources(new List<IdentityResource>())//Minimal things to use app.UseIdentitServer (Weird)
              .AddInMemoryClients(new List<Client>());//Minimal things to use app.UseIdentitServer (Weird)
             */
              
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(o =>
            {
                o.SecurityTokenValidators.Clear();
                //Commented but if you have your own OAUTH you could uncomment
                //o.Authority = Configuration.GetSection("Global:DomainNameServer").Value;
                //o.RequireHttpsMetadata = false;
                //o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                //{
                //    ValidateAudience = false,
                //};
                o.SecurityTokenValidators.Add(this.multiSocialTokenValidator);
            });

            services.AddControllers();

            services.AddHttpContextAccessor();//IHttpContextAccesor

            ... 
        }

//Startup Configuration

public void Configure(
              IApplicationBuilder app
            , IWebHostEnvironment env
            , ILoggerFactory loggerFactory
            , IServiceProvider serviceProvider
            )
        {
             this.multiSocialTokenValidator.HttpContextAccesor = () => app.ApplicationServices.GetService<IHttpContextAccessor>();
            

            app.UseRouting();

            //app.UseIdentityServer();//Not necesary now
            
            //Be careful with this order Authentication and Authorization 
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseMiddleware<HttpExceptionMiddleware>();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

//SecurityTokenValidator //I didn't cut all code, if someone see it usefull for himself

  public class MultiSocialTokenValidator : JwtSecurityTokenHandler, ISecur
ityTokenValidator
    {
    public Func<IHttpContextAccessor> HttpContextAccesor { get; set; }

    public override ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        validatedToken = null;
        var httpContext = this.HttpContextAccesor.Invoke();
        httpContext.HttpContext.Request.Headers.TryGetValue("SocialLoginId", out StringValues values);

        var social = "dmc";
        if (values.Count > 0)
            social = values[0].ToLower();

        if (social == "dmc")
        {
            ClaimsPrincipal cp = new();
            try
            {
                cp = base.ValidateToken(securityToken, validationParameters, out validatedToken);
            }
            catch (Exception ex) { }

            return cp;

        }
        else if (social == "google")
        {
            var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()
            ).Result; // here is where I delegate to Google to validate

            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),
                //new Claim("userId", userId.Id.ToString())
            };

            try
            {
                var principle = new ClaimsPrincipal();
                principle.AddIdentity(new ClaimsIdentity(claims, "Bearer"));
                validatedToken = this.ReadJwtToken(securityToken);
                //validatedToken.ThrowIfNull(nameof(securityToken));
                return principle;
            }
            catch
            {
                throw;
            }
        }
        else
        {
            throw new InvalidJwtException($"{nameof(MultiSocialTokenValidator)}: Invalid multi social header, (SocialLoginId)");
        }
    }
}

I think it is all, then in your api action add [Authority] Attribute, I was using angular "@abacritt/angularx-social-login": "1.2.4" to get client token, but i don't believe so that was the problem.

NOTE: "dmc" is my custom OATUH API 2.0

  • Net "Core" 6.0 (More weirds things xd)

Upvotes: 3

Related Questions