Search code examples
c#asp.net-corekeycloak

ASP.net Core Web API: KeyCloak server not even contacted


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?

Followup 1

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:

OAuth2 config

This gives me a token, but calling the API with it still results in 401.

Followup 2

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: certs

I thought using a MetadataAddress should handle all this for me, or shouldn't it?

It is still not working. Can anybody help?


Solution

  • 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:

    1. Create a realm or use one you want to use
    2. In the realm, create a new client. Use the following settings:
    • Client ID: The Client ID you want to use.
    • Client Protocol: openid-connect
    • Access Type: confidential (needed later to fetch tokens)
    • Valid Redirect URIs: At least http://localhost:6789/callback, but for testing, you can use * (not recommended for production)
    • Advanced Settings: Specify the Access Token Lifespan to have your tokens expire
    • Authentication Flow Overrides:
      • Browser Flow: browser
      • Direct Grant Flow: direct grant
    1. Save the page
    2. Open the newly-appearing Credentials Tab
    3. Make sure Client Authenticator is set to Client Id and Secret, and note the Secret
    4. Here under Roles (not Roles in the navbar at the left) add the roles you want to use in your API
    5. Create a user
    6. Under Role Mappings, Client Roles, select the created client, and assign the desired roles to the user

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