How I won KeyCloak

Business requirements:

  • For internal company services, a single sign-on point is required with users connected from the existing Active Directory.

  • It is required that the user can have access to one or more services (in each of the services he has one or more roles). If there is no access to a particular service, inform him about it.

Requirements for interaction with the CS

Sweep Requirements

KC version: 25.0.2, also tested on version 26.0.0 (also normal)

Well, we’ve sorted out the introductions, now let’s get down to implementation.

Build and deploy

Dockerfile

FROM keycloak/keycloak:25.0.2
COPY keycloak-kafka-1.1.5.jar /opt/keycloak/providers/

ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

To connect Kafka to CK we use keycloak-kafka-1.1.5

Dokcer-compose.yaml

version: "3.9"

services:
  keycloak:
    image: my_docker_hub/keycloak:latest
    volumes:
      - ./themes:/opt/keycloak/themes
      - ./cert/cert.jks:/etc/x509/https/truststore.jks
    container_name: keycloak
    ports:
      - "8443:8443"
    env_file: ./.env
    command: start
    depends_on:
      keycloak-postgres:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "exec 3<>/dev/tcp/127.0.0.1/9000;echo -e 'GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n' >&3;if [ $? -eq 0 ]; then echo 'Healthcheck Successful';exit 0;else echo 'Healthcheck Failed';exit 1;fi;",
        ]
      start_period: 10s
      interval: 30s
      retries: 3
      timeout: 5s

  keycloak-postgres:
    container_name: keycloak-postgres
    image: postgres
    volumes:
      - ./db/data:/var/lib/postgresql/data
    env_file: ./.env
    healthcheck:
      test: pg_isready -d postgres
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s

.env

KC_FEATURES: preview
KC_HEALTH_ENABLED: true
KC_METRICS_ENABLED: true

KC_HOSTNAME: host_keycloak
KC_HTTPS_PORT: 8443
KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD
KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks
KC_PROXY_HEADERS: xforwarded

KEYCLOAK_ADMIN: admin 
KEYCLOAK_ADMIN_PASSWORD: admin

KAFKA_TOPIC: user.event.user
KAFKA_ADMIN_TOPIC: user.event.admin
KAFKA_CLIENT_ID: keycloak
KAFKA_BOOTSTRAP_SERVERS: BOOTSTRAP_SERVERS
KAFKA_EVENTS: LOGIN,LOGOUT

KC_DB: postgres
KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak
KC_DB_SCHEMA: public

POSTGRES_DB: keycloak
PGUSER: keycloak
POSTGRES_USER : keycloak
POSTGRES_PASSWORD : keycloak
PGPASSWORD: password

I omit the assembly and deployment process, because… there's nothing interesting there.

Configuring HTTPS (In my case, I have a root cert)

At this point I was really stuck, because… There are tons of variations on the Internet, but unfortunately many are outdated or not suitable for the type of certificate

  1. Copy the root certificate to /opt/keycloak/cert

  2. Execute commands

    keytool -importkeystore -srckeystore cert.pfx -srcstoretype pkcs12 -destkeystore ./cert.jks -deststoretype pkcs12
    cd /opt/keycloak/cert

    Specify the password for the cert.pfx certificate and assign a password for the keystore

  1. In docker-compose we pass cert.jks

volumes:
      - ./cert/cert.jks:/etc/x509/https/truststore.jks
  1. In the .env file

    We set the following variables

KC_HOSTNAME: host_keycloak
KC_HTTPS_PORT: 8443
KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD
KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks
KC_PROXY_HEADERS: xforwarded

After deployment via Docker compose, we can open KC.
https://host_name:8443/

KC home page

KC home page

Login as admin / admin

Connecting AD

Creating your own Realm
Go to User federation and create Ldap providers

Then I followed the description in the article

https://habr.com/ru/companies/swordfish_security/articles/533264/

Thank you guys and Swordfish Security for the article.

As usual, when I took on the KC task, I wanted to spend another day on blogs and forums looking for a solution/guide, as everything is already written here 🙂

Connecting Kafka

Go to Realm settings, go to Events, select kafka

Now all events generated in the admin will be sent to the topic specified in .env admin, and user events login/logout to the user topic

Client setup

Creating a Client

Leave all settings as they are

Leave all settings as they are

After creation, in the settings, be sure to indicate the allowed URLs from which we can access, etc.

We create a client scope audience to add the client’s audience to the token.

add client scope

add client scope

create client scope

create client scope

Go to the Mappers tab, click on Configure a new mapper

Audience

Audience

add mapper

add mapper

Name – specify as convenient
Included Client Audience – select our client

Save.

Adding our scope to our client

Go to the Clients menu – Client scopes tab – Add client scope

Add client scope

Add client scope

Select our scope and add it with the Default attribute

Creating an access role for our client

Realm roles

Realm roles

Menu – Realm roles – Create role

You can fill it out like this

You can fill it out like this

Creating a user group to access our client

Groups

Groups

Menu – Groups – Create group

I prefer to set the name for access groups = client id

Role mapping

Role mapping

We associate the access role with our group on the Role mapping tab

Assign roles to my-client account

Assign roles to my-client account

Creating a Browser Authentication Flow for our Client

Menu – authentication – Flows – duplicate browser

Next we form the following structure

It’s strange, but I didn’t think that the situation that I needed to resolve was so rare and there was very little information on this topic on the Internet (well, very little). I found the solution on stackoverflow, which was presented in the form of a screenshot, which was eventually modified 🙂

Flow - Required: {  
    name: Login: <Название клиента> 
}
Step - Alternative: Cookie
Step - Alternative: Identity Provider Redirector config 
Flow - Alternative: {  
    name: gated browser form: <Название клиента> 
}

Step - Required: Username Form
Flow - Conditional: {  
    name: gated browser form - Conditional OTP Form config: <Название клиента> 
}
Condition - Required: Condition - user configured
Step - Required: OTP Form
Flow: {  
    name: RBA - Conditiona: <Название клиента> 
}
Condition - Required: Condition - user role
{
    Alias: user role <Название клиента>,
    User role: Выбираем роль созданную для нашего Client,
    Negate output: On
}
Step - Required: Deny access  
{
    Alias: Deny access config <Название клиента>,
    Error message: Доступ в приложение <Название приложения> запрещен
}
Condition - user role config

Condition – user role config

Deny access config

Deny access config

It should look like this

It should look like this

Next, we indicate our created Flow as the main one for the Client

advanced

advanced

If we now try to authenticate in KC through the keycloak-js adapter, we will get the following result

You do not have access to my-client

You do not have access to my-client

Adding a user to the my-client access group

Menu – members – Add member – select a user from the local database or imported from AD

Next, we try to pass authorization through the keycloak-js adapter, we get the result “Successful authorization” with receiving a token and a redirect to the page specified in the Client settings.

Bottom line

In this article, we looked at the implementation of authorization in our application through the popular SSO software KeyCloak. I hope it was written exhaustively, because… I experienced a lot of pain while setting everything up and maybe this will help someone.

Thanks for reading.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *