How to Integrate an Identity Server with Keycloak in .NET Web APIs | ASSIST Software Romania
get in touch
>

LIKE

SHARE

Facebook Share Tweet LinkedIn Share

FOLLOW

LinkedIn Follow Xing Follow



1. Introduction

This article is meant for developers or solution architects who want to integrate an Identity Server in their application. 

Keycloak is an open-source Identity Server and Access Management Solution that provides user federation, strong authentication, user management, fine-grained authorization, and more. 

Nowadays, a lot of Web APIs need the best security solutions and architecture to survive in this industry. The main reasons why developers may want to use a dedicated Identity Server are because it has built-in secure services, and the integration can be done with minimum effort.  

Also, there is no need to deal with managing the users or the authentication and authorization processes. Since an Identity Server has built-in standard protocols, like OpenID Connect, OAuth2, and SAML2.0, developers will have a more secure API.
How_to_Integrate_an_Identity_Server_with_Keycloak_in_NET_Web_APIs_Sebastian_Fulga_ASSIST_Software

Image 1: Keycloak logo - https://www.keycloak.org


2. Main advantages of using Keycloak 

  • Single-sign on 
  • Standard protocols 
  • Centralized management for admins and users
  • Built-in admin portal 
  • Integration with a lot of SQL databases (MySQL, Microsoft SQL, MariaDB, PostgreSQL) 
  • Customizable themes 
  • High performance 
  • Password policies 
  • Identity Brokering and Social Login (authenticate with external identity providers like Facebook, GitHub, Google, Instagram, Twitter, PayPal and more) 
  • Maintainable and efficient
     

3. Best Practices when using Keycloak 

  • Use a Docker Compose file. 
  • You should be using Kubernetes nodes to protect your Identity Server and have a fallback mechanism. 
  • Use PostgreSQL. 
  • Always use the integrated middleware from .NET SDK. (for example: JwtBearer or OpenIdConnect Middlewares) 

NOTE: On industry projects, in a Production Environment, we usually see Microsoft SQL and MySQL as well. These are the so-called “community databases”, which also have all the needed support in Keycloak.
 

4. Implementation 

First, let’s look at the high-level diagram (see Image 2). Here we see that the Frontend requests the Keycloak token by the API Endpoint. That endpoint gets implemented in the Keycloak Identity Server, which is our web application with its own Web API. Then the Frontend saves the token into the HTTP Headers and after that, at every request, the Backend, which receives the token, will validate it by using its own authorization middleware. After that, the Backend will return either 200 OK, which means Authorized, or 401 Unauthorized.
How_to_Integrate_an_Identity_Server_with_Keycloak_in_NET_Web_APIs_Sebastian_Fulga_ASSIST_Software

Image 2: Keycloak Authentication Flow – High-level diagram 

 

4.1. Design and Project Architecture for the Identity Server

Here we can see the initial solution for our Identity Server. 

How_to_Integrate_an_Identity_Server_with_Keycloak_in_NET_Web_APIs_Sebastian_Fulga_ASSIST_Software

Image 3: Project structure - Identity Server with Keycloak


If we expand all the folders in every assembly, we will see the following:

How_to_Integrate_an_Identity_Server_with_Keycloak_in_NET_Web_APIs_Sebastian_Fulga_ASSIST_Software

Image 4: Expanded Project structure - Identity Server with Keycloak

I preferred to use the recommended Clean Architecture design pattern, so I created an application layer, which has the Request DTOs, some extension methods for our service registration into our IoC container, and some services related to the Keycloak API endpoints. 

You will notice that I have also created a shared layer which has the needed constants, a custom exception related to Keycloak, the KeycloakSettings which we will be using through the IOptions pattern to register our configuration variables, and a Utils static class which will convert our initial request to a request DTO needed for the Keycloak token endpoint.

 

4.2. The Docker Compose file 

 As mentioned in the Keycloak best practices section, we will need a Docker Compose file because most of the requirements for an industry level project call for such a file. 

Generally, it is best to use a single Docker Compose file that creates all the needed instances. It is not recommended to use a separate Dockerfile for each instance or resource. Instead use a Docker Compose file that will contain all your needed instances and eventually, you can reference the Dockerfile for each separate resource in the Docker Compose file. 

Here is the .yml file that we will use to run Keycloak in Development Mode.

version '3'

volumes:

  postgres_data: 
      driver: local 

services:
  postgres:
      image: postgres
      volumes:
        - postgres_data:/var/lib/postgresql/data
      environment:

        POSTGRES_DB: postgresDB
        POSTGRES_USER: postgresUser
        POSTGRES_PASSWORD: postgresPW
  keycloak:
      image: quay.io/keycloak/keycloak:latest
      entrypoint: /opt/keycloak/bin/kc.sh start-dev --hostname-strict=false --hostname-port=7011
      environment:
        DB_VENDOR: POSTGRES
        DB_ADDR: postgres
        DB_DATABASE: keycloak
        DB_USER: postgresUser
        DB_SCHEMA: public
        DB_PASSWORD: postgresPW
        KEYCLOAK_ADMIN: admin
        KEYCLOAK_ADMIN_PASSWORD: admin
      ports:
        - '7011:8080'
      depends_on:
        - postgres

 

Here you can notice that the preferred database is PostgreSQL. As described in the Best Practices section, we can also use “community databases” like Microsoft SQL, MySQL and so on.  

Be careful to always check the preferred database documentation to construct your own Docker Compose file, because every connection string is different. 

 

The image used is obviously the latest one from Keycloak and the entrypoint is using Development Mode. We also used the HTTP scheme instead of HTTPS (the hostname scheme is set to false), and the port is set to 7011. 

To run the Administration Console with the admin credentials, we need to set the KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD environment variables. 

After running the docker compose up command in our CLI, we will have our Identity Server on our local machine.

How_to_Integrate_an_Identity_Server_with_Keycloak_in_NET_Web_APIs_Sebastian_Fulga_ASSIST_Software

How_to_Integrate_an_Identity_Server_with_Keycloak_in_NET_Web_APIs_Sebastian_Fulga_ASSIST_Software

Image 5: Keycloak Admin Portal

NOTE: If you want to use Keycloak in Production Mode, you will need to use the Start command. 

 

The Production Mode is targeted for deployments of Keycloak into Production environments and follows a "secure by default" principle. This means that instead of kc.sh start-dev, we will use the kc.sh start command. 

 

The following defaults will be set:

  • HTTP is disabled as transport layer security (HTTPS) is essential. 
  • Hostname configuration is expected. 
  • HTTPS/TLS configuration is expected. 

 

4.3. Web API Implementation 

Now, since we have our Identity Server, we are able to use it in order to get the access token from Keycloak. 

On a client-server architecture or on a microservices architecture, we can integrate the JWT middleware into our Web API.  This is because we can propagate our JWT token through all the requests since we have a Frontend client. 

 However, if the application was an MVC one, we would need to think about a different strategy, because the request would need to be always kept on our MVC client-side. 

Let’s start with the Program.cs file. 

var builder = WebApplication.CreateBuilder(args); 

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

 

builder.Services.AddServices();
builder.AddKeycloakSettings();
builder.AddKeycloakAuthorization();
builder.AddSwagerBearerAuthentication();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers(); 

app.Run(); 

 

Here you can see that we added the services using the .NET IoC pattern and a couple of extension methods as shown above to make our code much cleaner.  

After we added the services, we then configured the Keycloak configuration variables through the AddKeycloakSettings extension, and then we added the authorization using the specific extension for it as well. And of course, the usual Swagger Bearer Authentication button, which we all see on our current projects, was integrated into our Open API.

 

Next, I will present a couple of the extensions that I have used through this solution. 

public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddServices(this IServiceCollection services)
        { 
            services.AddHttpClient();
            services.AddScoped<IKeycloakTokenService, KeycloakTokenService>();

 

            return services;
        }
    }

 

After this, we added the Keycloak configuration variables (BaseUrl, ClientId and ClientSecret) to be able to call their APIs and get the access token. 

To register the configuration variables, I decided to use the IOptions pattern. This means that our appsettings.json file has a Configuration JSON like this: 

"Keycloak": {
    "BaseUrl": "http://{keycloakHost}:{keycloakPort}/realms/{keycloakRealm}/protocol/openid-connect",
    "ClientId": "{keycloakClientId}",
    "ClientSecret": "{keycloakClientSecret}"
  } 

 

Here is the AddKeycloakSettings extension:

public static void AddKeycloakSettings(this WebApplicationBuilder builder) 
        {
            var keycloakSettings = builder.Configuration.GetSection("Keycloak"); 


            builder.Services.Configure<KeycloakSettings>(keycloakSettings); 
        } 

 

The KeycloakSettings class is the below one: 

public class KeycloakSettings
    {
        public string? BaseUrl { get; set; }

        public string? ClientId { get; set; }
        public string? ClientSecret { get; set; } 

    } 

 

Next, we will move to the AddKeycloakAuthorization. 

public static void AddKeycloakAuthorization(this WebApplicationBuilder builder)
        {
            IdentityModelEventSource.ShowPII = true; 

 

            builder.Services
                .AddAuthentication(option =>
                {
                    option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                }) 
                .AddJwtBearer(options =>
                {
                    options.Authority = "http://localhost:7011/realms/develop";
                    options.SaveToken = false;
                    options.RequireHttpsMetadata = false; 

 

                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = false,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = "http://{keycloakHost}:{keycloakPort}/realms/{yourRealm}"
                    };
                }); 
        } 

 

Here we can see the options for the JWT middleware used to validate our Keycloak token. 

The RequireHttpsMetadata option was set to false only because the running environment is Development. In Production, this option is by default set as true. 

ValidateIssuer  was set to true to mitigate forwarding attacks that could occur when our Identity Provider represents multiple tenants and signs tokens with the same keys. 

ValidateAudience also mitigates forwarding attacks. The default value was set as true. We also set lifetime validation as true, and we validated the issuer signing key (because it is possible for tokens to contain the public key needed to check the signature). This applies to default signing key validation. The default was set as false. 

After that we added the Swagger Authentication. 

 

Now let’s move to our Controllers folder. 

[ApiController]
[Route("api/user")]
[Produces("application/json")]
public class UserController : ControllerBase
{
    private readonly IKeycloakTokenService keycloakTokenService; 

 

    public UserController(IKeycloakTokenService keycloakTokenService)
    {
        this.keycloakTokenService = keycloakTokenService;
    }

 

        [HttpPost("token")]
        public async Task<IActionResult> AuthorizeAsync([FromBody] KeycloakUserDto keycloakUserDto)
        {
            try
            {
                var response = await keycloakTokenService
                    .GetTokenResponseAsync(keycloakUserDto)
                    .ConfigureAwait(false);

                return new OkObjectResult(response);
            }
            catch (KeycloakException)
            {

                return BadRequest("Authorization has failed!");
            }
            catch (Exception)
            {
                return BadRequest("An error has occured!");
            }
        }

        [Authorize]
        [HttpGet("check/authorization")]
        public IActionResult CheckKeycloakAuthorization()
        {
            return new OkObjectResult(HttpStatusCode.OK);
        }
    } 

 

You will see that our KeycloakTokenService calls the GetTokenResponseAsync method which will create our Request DTO to call the Keycloak token endpoint. 

public async Task<KeycloakTokenResponseDto?> GetTokenResponseAsync(
            KeycloakUserDto keycloakUserDto)
        {
            using (var httpClient = httpClientFactory.CreateClient())
            { 

                var keycloakTokenRequestDto = new KeycloakTokenRequestDto
                {
                    GrantType = KeycloakAccessTokenConsts.GrantTypePassword,
                    ClientId = keycloakSettings.ClientId ??
                        throw new KeycloakException(nameof(keycloakSettings.ClientId)),
                    ClientSecret = keycloakSettings.ClientSecret ??
                        throw new KeycloakException(nameof(keycloakSettings.ClientSecret)),
                    Username = keycloakUserDto.Username,
                    Password = keycloakUserDto.Password
                }; 

 

var tokenRequestBody = KeycloakTokenUtils.GetTokenRequestBody(keycloakTokenRequestDto); 

                var response = await httpClient
                    .PostAsync($"{keycloakSettings.BaseUrl}/token", tokenRequestBody)
                    .ConfigureAwait(false);

 

var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

var keycloakTokenResponseDto = JsonConvert.DeserializeObject<KeycloakTokenResponseDto>(
                    responseJson);

                return keycloakTokenResponseDto;
            }
        } 

 

The $"{keycloakSettings.BaseUrl}/token" is equivalent to  

http://{keycloakHost}:{keycloakPort}/realms/{yourRealm}/protocol/openid-connect/token 

 

The KeycloakTokenRequestDto class that I used is presented here:

public class KeycloakTokenRequestDto
    {
        [JsonProperty("grant_type")]
        public string GrantType { get; set; }

        [JsonProperty("client_id")]
        public string ClientId { get; set; }

        [JsonProperty("username")]
        public string Username { get; set; }

        [JsonProperty("password")]
        public string Password { get; set; }

        [JsonProperty("client_secret")]
        public string ClientSecret { get; set; }

    } 

 

Now we can see the response DTO that was represented by the KeycloakTokenResponseDto  class: 

public class KeycloakTokenResponseDto
    {
        [JsonProperty("access_token")]
        public string? AccessToken { get; set; }

        [JsonProperty("expires_in")]
        public int ExpiresIn { get; set; }

        [JsonProperty("refresh_expires_in")]
        public int RefreshExpiresIn { get; set; }

        [JsonProperty("refresh_token")]
        public string? RefreshToken { get; set; }

        [JsonProperty("token_type")]
        public string? TokenType { get; set; } 

    }

 

To make an HTTP Post Request to Keycloak, we will need to use FormUrlEncodedContent. That means we will create a list of KeyValuePairs containing our x-www-form-urlencoded Key Value Pairs. If we open Postman, the request will look like this:

Image 6: Keycloak Token API Endpoint 

 

Now since we are talking about the request format, let’s take a look at our KeycloakTokenUtils static class which parses our initial request into the request preferred for the Keycloak token endpoint. Since we need a FormUrlEncodedContent type, we will have to create a list of KeyValue Pairs. 

public static class KeycloakTokenUtils
    {
        public static FormUrlEncodedContent GetTokenRequestBody(
            KeycloakTokenRequestDto keycloakTokenDto)
        {
            var keyValuePairs = new List<KeyValuePair<string, string>>()
            {
                new KeyValuePair<string, string>(
                    KeycloakAccessTokenConsts.GrantType, keycloakTokenDto.GrantType),
                new KeyValuePair<string, string>(
                    KeycloakAccessTokenConsts.ClientId, keycloakTokenDto.ClientId),
                new KeyValuePair<string, string>(
                    KeycloakAccessTokenConsts.ClientSecret, keycloakTokenDto.ClientSecret), 
                new KeyValuePair<string, string>(
                    KeycloakAccessTokenConsts.Username, keycloakTokenDto.Username),
                new KeyValuePair<string, string>(
                    KeycloakAccessTokenConsts.Password, keycloakTokenDto.Password)
            };
            return new FormUrlEncodedContent(keyValuePairs);
        }
    } 

 

And this is the KeycloakAccessTokenConsts static class. 

public static class KeycloakAccessTokenConsts
    {
        public static string GrantType => "grant_type";
        public static string GrantTypePassword => "password";
        public static string ClientId => "client_id";
        public static string ClientSecret => "client_secret";
        public static string Username => "username";
        public static string Password => "password";
    } 

As you can see, the GrantType used has the type of “password”, and besides the credentials, we also need the client_id and the client_secret. Please look again at the Postman request, because this way you will have a better understanding of the request itself. 

 

4.4. Let’s get to practice! Test the already created API 

Here you can see the 2 endpoints: one for getting the access token from Keycloak and the other, a validation endpoint to see that we are authorized (so the API will return 200 HTTP Status OK instead of 401 HTTP Status Unauthorized).

How_to_Integrate_an_Identity_Server_with_Keycloak_in_NET_Web_APIs_Sebastian_Fulga_ASSIST_Software
Image 7: Swagger API Endpoints

 

Our request body will contain the following JSON: 

{
  "username": "{yourKeycloakUser}",
  "password": "{yourKeycloakUserPassword}" 

Then we will get the following response: 

{
"accessToken": "{yourKeycloakAccessToken}",
"expiresIn": 300, 
"refreshExpiresIn": 1800,
"refreshToken": "{yourKeycloakRefreshToken}",
  "tokenType": "Bearer"
}

Then after adding the Bearer Token into your Frontend header, the api/user/check/authorization endpoint will return the 200 OK HTTP status code. If you delete the token, the API will return the 401 Unauthorized HTTP status code. 

 

5. Auth0 vs Keycloak:  Pros and Cons

When we talk about Identity Servers, we will always think about Auth0 since it is the most popular Identity Server in our industry, so I considered it appropriate to make a Pros and Cons topic. 

While both have a lot of features in common, it is important to mention that Auth0 has a better administration portal, so it has a better UX. Auth0 also has better integration for specific industries like financial services, healthcare, retail, public sectors, etc. 

Also, it is much easier to customize your login themes than on  Keycloak. Auth0 has much better UX regarding themes, while on Keycloak you must build your own theme based on Freemarker templates (which are like HTML templates). 

Auth0 has anomaly detection which offers brute-force protection and breached password detection while Keycloak is poorly situated on these. 

Regarding the pricing, Keycloak is open source while Auth0 is free only for the first 7,000 active users (no credit card required), then you will have to pay according to your plan. 

So, keep in mind these main differences between these Identity Servers when you want to integrate one into your own application. 

 

6. Conclusion

In this blog article, we have covered how to create your own Identity Server using Keycloak on your local machine and how to integrate it into your Web APIs using .NET. 

We followed best practices when achieving these steps, and also made a comparison to other Identity Servers like Auth0. Even though they seem the same, we saw that Auth0 has much more interesting features and for a paid plan it is more popular than Keycloak. 

On the other hand, having Keycloak for an industry project is a very safe option. This is because it is very customizable, and it can easily fit your needs for your application on the authorization side. 

It is important to note that Keycloak is newer, but it is starting to get even more popular every year, so if you want a stable Identity Server that provides all the security needs for your application, you should go for it. 

I hope you enjoyed this article, and that it will help you to develop your current and future projects! 

Want to learn more on your .NET journey? Try a blog article that highlights best practices in the case of managed and unmanaged code

Vous souhaitez nous contacter ? 

Si vous êtes intéressés par nos services de développement de logiciel, si vous souhaitez rejoindre notre équipe, ou si vous souhaitez tout simplement en savoir plus sur nous, nous sommes à votre disposition. Contactez-nous ! Écrivez-nous et un membre de l'équipe ASSIST vous répondra dans les plus brefs délais. Nous pourrons certainement vous ASSISTer.

CONTACTEZ-NOUS