I'm using a flow with Openid, where I redirect my user to another provider, to take care about login, and after this login I receive the code
in my /login/callback?code=xxxx URL.
So, the JWT is generated using the code
but I can't validate it. I don't have well-known
endpoint in my STS, I need to configure manually in this way:
services.AddAuthorization(cfg =>
{
cfg.AddPolicy("MyPolicy", cfgPolicy =>
{
cfgPolicy.AddRequirements().RequireAuthenticatedUser();
cfgPolicy.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme);
});
}).AddAuthentication(cfg =>
{
cfg.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(cfg =>
{
cfg.ClientId = authenticationConfig.ClientId;
cfg.ClientSecret = authenticationConfig.ClientSecret;
cfg.ResponseType = "code";
cfg.CallbackPath = "/login/callback";
cfg.Scope.Clear();
cfg.Scope.Add("openid");
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://myissuer"
};
cfg.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://mysts/api/oauth/authorize",
TokenEndpoint = "https://mysts/api/oauth/token",
UserInfoEndpoint = "https://mysts/api/oauth/token_info"
};
});
Some important points:
- I have a token instrospection endpoint, to validate my token (token_info endpoint).
- I don't have an default endpoint to return public keys (jwks). My endpoint is always a concat from some values, something like that --> https://mysts/offline/jwks/{kid}/{clientid}, so this is dynamic and depends of the token.
- I don't have a well-known endpoint.
CodePudding user response:
You can't validate it because, ASP.NET Core tries to validate the JWT signature by default. However, since you've set up the metadata yourself, and not given a JwksUri
, it has no way of getting the public key of the OIDC provider to validate the signature against.
You can specify the JwksUri
, or turn off signature validation (NOT SECURE), or validate it yourself. If you have access to the public key, somehow, use options.SecurityTokenValidator
and implement a ISecurityTokenValidator
for custom validation.
Here's a (non-tested) implementation that fetches the JWKS dynamically and validates a token:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddOpenIdConnect(options => {
options.SecurityTokenValidator = new MyJwtValidator(Configuration);
});
}
class MyJwtValidator: ISecurityTokenValidator
{
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
private readonly JwtSecurityTokenHandler _tokenHandler;
public MyJwtValidator(IConfiguration configuration)
{
_configuration = configuration;
_tokenHandler = new JwtSecurityTokenHandler();
_httpClient = new HttpClient
{
BaseAddress = new Uri(_configuration.GetSection("OidcProvider").Get<string>())
};
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(
string securityToken,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken
)
{
// parse the token (without validating) to extract a value
var parsedToken = new JwtSecurityToken(securityToken);
var keyId = parsedToken.Claims.First(c => c.Type == "kid").Value;
// fetch JWKS and validate the token
var clientId = _configuration.GetSection("OidcProvider:ClientId").Get<string>();
var jwks = _httpClient.GetStringAsync($"url/to/jwks/{keyId}/{clientId}").Result;
// jwks == "{ keys: [..."
var signingKeys = new JsonWebKeySet(jwks).GetSigningKeys();
return _tokenHandler.ValidateToken(securityToken, new TokenValidationParameters
{
IssuerSigningKeys = signingKeys
}, out validatedToken);
}
public bool CanValidateToken { get; } = true;
public int MaximumTokenSizeInBytes { get; set; } = int.MaxValue;
}
It's doing sync-over-async, which is frowned upon, but you might wanna cache the keys to avoid paying the penalty.