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