In this article we cover various aspects of integrating .NET applications with Keycloak including handling the login process in different kinds of client applications, authenticating incoming requests in backend services and integrating the Keycloak REST API.

As we mentioned in our previous article, Keycloak implements standard OIDC and SAML protocols. Usually, in modern web-based applications we use previous, OIDC-based solutions. This means most backend and client-side configurations regarding authentication also work with other OIDC-compliant identity providers such as Azure AD.

Authentication in an ASP.NET Core backend application

In this section we’ll see how we can use the OIDC interface of Keycloak to authenticate incoming requests in an ASP.NET Core application on .NET 7. In this process, we need to know which user is calling our API and relevant information on the user (such as their role). 

For this, we’ll use the Microsoft.AspNetCore.Authentication.JwtBearer Nuget package developed by Microsoft. This allows us to check the incoming request authorisation header for a valid JWT issued by an OIDC-compliant identity provider such as our Keycloak instance.

During the startup initialisation, we can set up authentication the following way:

services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
{        
        options.Authority = “https://mykeycloak.net/auth/realms/myapp“;
        options.MetadataAddress = “https://mykeycloak.net/auth/realms/myapp/.well-known/openid-configuration“;
        options.RequireHttpsMetadata = true;        
        options.TokenValidationParameters = new TokenValidationParameters        
        {            
                NameClaimType = ClaimTypes.Name,
                RoleClaimType = ClaimTypes.Role,            
                ValidateIssuer = true,           
                ValidIssuers = new[] { “https://mykeycloak.net/auth/realms/myapp” },
                ValidateAudience = true,
                ValidAudiences = new[] { “frontend”, “mobile”, “swagger”, },
         };
    });

First we define a JWT bearer authentication scheme clients can use, then we use the JwtBearerOptions parameter in the AddJwtBearer method to configure the following options:

  • Authority: defines a party issuing access tokens we’d like to accept. When we are using Keycloak this identifies a Keycloak realm, each realm can be a separate and different authority.
  • MetadataAddress: a URL where our backend application can access meta information describing the authority’s auth API. For example, it defines the path where our backend application can access the Keycloak instance’s public key used for JWT validation.
  • RequireHttpsMetadata: is set to true because we want to access the metadata endpoint on a secure channel.
  • TokenValidationParameters: these parameters allow us to limit what kind of access tokens we accept.
    • Name, RoleClaimType: defines a claim type under which we can access the current user’s name and role in the application. These are needed for the HttpContext.User.Identity.Name to be set properly which we can use during the request to access the current user’s name, and also for the role-based authorisation policies of ASP.NET Core.
    • ValidateIssuer, ValidIssuers: we can set one or many URLs identifying certain parties who we accept as valid access tokens issuers. We can also skip the issuer validation by setting the ValidateIssuer to false. Usually, the issuer and authority fields are the same but there are a few cases where we need to support multiple issuers different from the authority.
    • ValidateAudiance, ValidAudiances: our backend services are usually accessed by multiple kinds of client applications, these clients are separate Keycloak clients. As a security measure, it’s always wise to limit which kind of clients can access our API. We can also skip the audience validation by setting the ValidateAudiance to false.

After the necessary configuration, the Nuget package mentioned above handles most of the authentication integrating into the standard ASP.NET Core authentication solutions. The backend service does need to access the Keycloak instance to use a public key to validate the incoming JWTs reliability and integrity. 

As a result, we can be sure that whatever is encoded in the token can only have been done by someone with the private key so we can use its content (for example, the issuer or audience claims) to authenticate the request according to the configuration options mentioned above.

Authentication flows in client applications

On the client side, we have to set up a secure flow to authenticate the user during a login process, after which we can request ID, access and refresh tokens depending on the flow used. As mentioned before, Keycloak implements many standard OIDC flows, but we usually use the following two depending on what kind of client we’d like to authenticate.

  • Authorization Code flow: this flow is suitable for clients capable of keeping secrets private and does not expose them for example server-side applications or mobile, thick client applications.
  • Authorization Code flow with PKCE: this flow is for public clients not capable of keeping any kind of secrets private for example Single Page Applications running in the user’s browser.

During these flows, users authenticate themselves using Keycloak’s login page which adds an extra layer of security since this way the sensitive credentials will not reach our application, only the Keycloak instance. After the user is authenticated, and we’ve received an access token, we’ll have to attach the token in the Authorisation header to every outgoing HTTP request.

Every client we want to authenticate using Keycloak must be registered in the admin console. We have numerous options for configuring these clients but the most important settings are the following:

  • Client ID: the unique identifier of the client app inside the realm.
  • Client Protocol: the auth protocol used by this client, can be SAML or OpenId-Connect. We mostly use the latter, and this article covers that.
  • Access Type: this can be confidential or public. Only confidential apps can use client-side secrets. When we have a client-side secret enabled we can also authenticate the client itself, not just the user using it.
  • Standard Flow Enabled: by standard flow, Keycloak means the authorisation code flow, since we want to use that, we have to enable it.
  • Valid Redirect URIs: the accepted redirect URIs where the client can be redirected to after certain processes like login or logout.

Authentication in an ASP.NET Core Blazor client application

For Blazor applications targeting WebAssembly we can use the Microsoft.AspNetCore.Components.WebAssembly.Authentication Nuget package. We will use authorisation code flow with PKCE since a Blazor WebAssembly application is considered public.

During the client startup initialisation, we can set up the authentication the following way:

services.AddOidcAuthentication(options =>
{
    options.ProviderOptions.Authority =https://mykeycloak.net/auth/realms/myapp;
    options.ProviderOptions.ClientId = “frontend”;
    options.ProviderOptions.ResponseType = “code”;
    options.ProviderOptions.RedirectUri = “oidc/login-callback”;
    options.ProviderOptions.PostLogoutRedirectUri = “oidc/logout-callback”;
    options.ProviderOptions.DefaultScopes.Add(“profile”);
    options.ProviderOptions.DefaultScopes.Add(“email”);
    options.ProviderOptions.DefaultScopes.Add(“roles”);
});

We use the ProviderOptions property of the RemoteAuthenticationOptions parameter in the AddOidcAuthentication method to configure the following options:

  • Authority: the issuing OIDC provider we are going to use and accept.
  • ClientId: the identifier of the Blazor client app, it has to be a valid audience on the backend side.
  • ResponseType: defines what kind of OIDC flow we’d like to use. Here we use code and the library will automatically use PKCE and does not allow setting a client secret since this library is only usable in public, Blazor WebAssembly applications.
  • RedirectUri: the login flow builds on a browser redirection process. After the user’s successful authentication on the Keycloak login page, the user’s browser will be redirected to this URL handled by our application and will receive an authorisation code which is basically a one-time password and will be exchanged by the library for an access token requested on a separate token endpoint.
  • PostLogoutRedirectUri: when the user wants to log out from our application the Keycloak instance must be notified to clear out any remaining session the user has. This is also done with a browser redirection like the login flow after which the user will be redirected to this endpoint of our app.
  • DefaultScopes: these are the OAuth scopes we’d like to access after the user’s successful login, these scopes can limit what an application may access using the created JWT. With the setup above we can access the user’s detailed profile information, email and roles on the client side.

The Nuget package we use will handle attaching the access token to outgoing HTTP requests automatically using an HttpClient DelegatingHandler.

Authentication in a MAUI client application

We can also secure a MAUI, mobile application using Keycloak and a standard OIDC flow. For this, we’ll be using the IdentityModel.OidcClient Nuget package. In most cases, a mobile application can be considered a secure application so we can use the authorisation code flow with a client secret. 

We use a similar browser to the one used in the Blazor example, for the login and logout processes, but since this is a mobile platform we’ll be performing this inside the mobile’s default, installed browser.

We can make the necessary initialisations the following way:

services.AddTransient<IBrowser, AuthBrowser>();
services.AddSingleton((sp) => new OidcClient(new OidcClientOptions
{    
    Authority = “https://mykeycloak.net/auth/realms/myapp”,
    ClientId = “mobile”,
    ClientSecret = “secret”,
    Scope = oidcOptions.Scope,
    RedirectUri = “oidc/login-callback”,
    PostLogoutRedirectUri = “oidc/logout-callback”,
    Browser = sp.GetRequiredService<IBrowser>(),
    HttpClientFactory = (_) => sp.GetService<IHttpClientFactory>().CreateClient(OidcHttpClientId),
}));

First we register an IBrowser implementation in the DI framework. IBrowser is an abstraction for handling the login and logout OIDC flows. Since the IdentityModel.OidcClient library can be used on numerous platforms using a browser can be quite different. For example, we can use an installed browser like here but we can use a custom WebView or a similar solution.

Secondly, we initialise an OidcClient instance which is quite similar to what we’ve done in our Blazor client. The notable differences are the following settings:

  • ClientSecret: this is a secret value generated by Keycloak. As we’ve mentioned, here we consider a mobile app a private client so we may use a secret on the client-side during the Authorisation Code flow. 
  • Browser: this will be IBrowser implementation we just discussed.

Lastly, the AuthBrowser implementation looks like this with only one InvokeAsync method which will be called for login and logout. Here we use the WebAuthenticator solution of MAUI, this gives us a platform-independent abstraction of a browser which we can pop up for the user and initiate an auth flow on a specific StartUrl and wait for the flow’s end on another EndUrl. The authentication result must also be converted into a & separate key-value pair list for the OIDC library.

public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken ct = default)
{
    try
    {
        var result = await WebAuthenticator.AuthenticateAsync(
                     new Uri(options.StartUrl), new Uri(options.EndUrl));

         return new BrowserResult
        {
            ResultType = BrowserResultType.Success,
            Response = $”{options.EndUrl}#{string.Join(
                                   “&”, result.Properties.Select(pair => $”{pair.Key}={pair.Value}))},
         };
    }
    catch (Exception ex)
    {
        return new BrowserResult()
        {
             ResultType = BrowserResultType.UnknownError,
             Error = ex.ToString(),
        };
    }
}

Keycloak REST API

Keycloak has an administrator UI with quite extensive configuration options and most of these can be accessed using a REST API. 

There are many times when we authenticate users using Keycloak that we also want to be able to access user information like name, email or other properties when a user is not present or when the user in question is not present, only a different one. For example, we might want to list users in a dropdown for someone to choose from a certain function of an app. Since Keycloak stores all user information, it would be redundant to store this information in our app as well. In most cases, it’s a better choice to use Keycloak’s REST API to query for users and our application’s database should only store references to the Keycloak users using their GUID.

Using the REST API also allows us to replace the administrator UI of Keycloak. For example, if we need a custom-built UI to manage users we can adjust the level of customisations of user management based on our project’s needs and still use a solution like Keycloak.We can use the Keycloak REST API by building a custom client with a solution like Refit but for the most often used APIs one of our colleagues created a wrapper library named Keycloak.Client.

Here we only have a really basic configuration, setting only the base address of our Keycloak instance and the realm we want to use. We have to provide a client ID since applications accessing the REST APIs use the same auth processes as a user-facing application. 

We also have a secret here since we have a confidential client but we are not using the authorisation code flow. Still, the client credentials flow, because we do not necessarily have an active user present when our backend accesses the Keycloak REST API. The client credentials flow is similar to the authorisation code flow in a way that the client is considered private but we are requesting access on behalf of an application, not a user.

services.AddKeycloakClient(c =>{
    c.KeycloakBasePath = “https://mykeycloak.net”;
    c.ClientSecret = “secret”;
    c.ClientId = “backend”;
    c.Realm = “myapp”;
    c.AuthRealm = “myapp”;
});

After this we can inject an IKeycloakUserClient anywhere in our app to access the user related APIs in a managed C# environment.

Using Keycloak extensions

Keycloak is highly expandable by creating third-party extensions or plugins, these can add new or extend existing functionality. Since Keycloak is a Java application most often these modules are distributed as jar files and must be placed in a specific directory, for example at a Keycloak docker image build. 

Let’s see an example. One such extension allows us to extend the login options of Keycloak with a magic link login meaning we can generate one-time links the user can access only once to authenticate. Our application can use Keycloak’s REST API to generate these magic links and then send them to the user securely via email. For this to work in our Keycloak setup described previously, we have to either build or download a prebuilt jar distribution of this extension and copy it to the provider’s directory in our dockerfile like so:

COPY /Providers/magic-link-login.jar /opt/keycloak/providers

You can find a list of a few Keycloak extensions on its official site.

Keycloak offers a variety of options in terms of user and identity management. We can set up a basic installation quite easily and efficiently and we can also extend the default behavior of Keycloak if it’s needed. Based on our own experience in multiple projects using Keycloak we can save a considerable amount of time and resources when we need to implement user management and authentication-related functions while not compromising on quality and security.