Reputation: 3107
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
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
Upvotes: 3