I am trying to incorporate auth* in an ASP.NET Core Web API. I set up a KeyCloak server and am trying to add this.
In my Program.cs
I have:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => {
o.MetadataAddress = "https://MyKeyCloak/auth/realms/MyRealm/.well-known/openid-configuration";
o.Authority = "https://MyKeyCloak/auth/realms/MyRealm";
o.Audience = "account";
});
builder.Services.AddAuthorization();
And:
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
// Other setup follows
I created a simple test controller:
[ApiController]
public class AuthTestController: ControllerBase
{
[HttpGet("/test")]
[Authorize]
public IActionResult AuthTest() => Ok();
}
Calling /test
endpoint just results in a 401. From the logs I can't even see that my Web API is making any attempts to contact the KeyCloak server. What am I missing?
I now tried this with Thunder Client (similar to Postman) which has an OAuth2 function. Generating a token with KeyCloak works, but my Web API doesn't seem to accept it:
This gives me a token, but calling the API with it still results in 401.
I cranked log levels all up to debug and get the following errors:
dbug: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[9]
AuthenticationScheme: Bearer was not authenticated.
dbug: Microsoft.AspNetCore.Authorization.AuthorizationMiddleware[0]
Policy authentication schemes did not succeed
I changed the setup a bit:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => {
o.MetadataAddress = "https://MyKeyCloak/auth/realms/MyRealm/.well-known/openid-configuration";
o.Authority = "https://MyKeyCloak/auth/realms/MyRealm";
o.Audience = "aspnet-test";
o.TokenValidationParameters = new() {
ValidIssuer = o.Authority,
ValidAudience = o.Audience,
IssuerSigningKey = GetSigningKey(),
};
});
And GetSigningKey
looks like this (for testing purposes with hardcoded cert):
static SecurityKey GetSigningKey() {
var bytes = Convert.FromBase64String("REDACTED");
return new X509SecurityKey(new X509Certificate2(bytes));
}
From the KeyCloak admin interface in my realm, I used the Certificate
of both RS256 and RSA-OAEP to no avail:
I thought using a MetadataAddress
should handle all this for me, or shouldn't it?
It is still not working. Can anybody help?
I am answering my own question because then I can mark it as accepted in two days.
I finally got it to work. Here are the steps:
openid-connect
confidential
(needed later to fetch tokens)http://localhost:6789/callback
, but for testing, you can use *
(not recommended for production)Access Token Lifespan
to have your tokens expirebrowser
direct grant
Credentials
TabClient Authenticator
is set to Client Id and Secret
, and note the Secret
Roles
(not Roles
in the navbar at the left) add the roles you want to use in your APIRole Mappings
, Client Roles
, select the created client, and assign the desired roles to the userThen, I added:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => {
o.MetadataAddress = "https://MyKeyCloak/auth/realms/MyRealm/.well-known/openid-configuration";
o.Authority = "https://MyKeyCloak/auth/realms/MyRealm";
o.Audience = "aspnet-test";
o.TokenValidationParameters = new() {
ValidIssuer = o.Authority,
ValidAudience = "account",
IssuerSigningKey = GetSigningKey(),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
};
});
builder.Services.AddAuthorization();
static SecurityKey GetSigningKey() {
var bytes = Convert.FromBase64String("(Certificate MD5 of the RS256 key)");
return new X509SecurityKey(new X509Certificate2(bytes));
}
// below when the app is constructed:
app.UseAuthentication();
app.UseAuthorization();
The GetSigningKey
method should fetch the certs endpoint from the Metadata Address and get the certificate from there for production use.
With this in place, I can fetch a JWT using the Thunder Client and use this JWT to access the API.