asp.net-coreoauth-2.0google-apiasp.net-core-2.0google-authentication

Google JWT Authentication with AspNet Core 2.0


I am trying to integrate google authentication in my ASP.NET Core 2.0 web api and I cannot figure out how to get it to work.

I have this code in my Startup.cs ConfigureServices:

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders();

services.AddAuthentication()
.AddGoogle(googleOptions => 
 {
     googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
     googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
});

And this in Configure(IApplicationBuilder app, IHostingEnvironment env):

 app.UseAuthentication();

When I navigate to an Authorized endpoint, the result is a 302 Found because presumably it is redirecting to some login endpoint (which I never created). How do I prevent the redirection and just have the API expect a token and return a 401 if no token is provided?


Solution

  • Posting my ultimate approach for posterity.

    As Tratcher pointed out, the AddGoogle middleware is not actually for a JWT authentication flow. After doing more research, I realized that what I ultimately wanted is what is described here: https://developers.google.com/identity/sign-in/web/backend-auth

    So my next problems were

    1. I could not rely on the standard dotnet core Jwt auth middleware anymore since I need to delegate the google token validation to google libraries
    2. There was no C# google validator listed as one of the external client libraries on that page.

    After more digging, I found this that JWT validation support was added to C# here using this class and method: Google.Apis.Auth.Task<GoogleJsonWebSignature.Payload> ValidateAsync(string jwt, GoogleJsonWebSignature.ValidationSettings validationSettings)

    Next I needed to figure out how to replace the built in JWT validation. From this SO questions I came up with an approach: ASP.NET Core JWT Bearer Token Custom Validation

    Here is my custom GoogleTokenValidator:

    public class GoogleTokenValidator : ISecurityTokenValidator
    {
        private readonly JwtSecurityTokenHandler _tokenHandler;
    
        public GoogleTokenValidator()
        {
            _tokenHandler = new JwtSecurityTokenHandler();
        }
    
        public bool CanValidateToken => true;
    
        public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
    
        public bool CanReadToken(string securityToken)
        {
            return _tokenHandler.CanReadToken(securityToken);
        }
    
        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
        {
            validatedToken = null;
            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),
                    };
    
            try
            {
                var principle = new ClaimsPrincipal();
                principle.AddIdentity(new ClaimsIdentity(claims, AuthenticationTypes.Password));
                return principle;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
    
            }
        }
    }
    

    And in Startup.cs, I also needed to clear out the default JWT validation, and add my custom one:

    services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    
                })
                .AddJwtBearer(o =>
                    {
                        o.SecurityTokenValidators.Clear();
                        o.SecurityTokenValidators.Add(new GoogleTokenValidator());
                    }
    

    Maybe there is an easier way, but this is where I landed and it seems to work fine! There was additional work I did that I left out of here for simplicity, for example, checking if there is already a user in my user's DB that matches the claims provided by google, so I apologize if the code above does not 100% work since I may have removed something inadvertently.