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
- 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
- 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.