**Purpose**: Keycloak is an open source identity and access management system for modern applications and services. 

- [Original Reference Compose File](https://github.com/JamesTurland/JimsGarage/blob/main/Keycloak/docker-compose.yaml)
- [Original Reference Deployment Video](https://www.youtube.com/watch?v=6ye4lP9EA2Y)
- [Theme Customization Documentation](https://www.baeldung.com/spring-keycloak-custom-themes)

## Keycloak Authentication Sequence
``` mermaid
sequenceDiagram
    participant User
    participant Traefik as Traefik Reverse Proxy
    participant Keycloak
    participant Services

    User->>Traefik: Access service URL
    Traefik->>Keycloak: Redirect to Keycloak for authentication
    User->>Keycloak: Provide credentials for authentication
    Keycloak->>User: Return authorization token/cookie
    User->>Traefik: Send request with authorization token/cookie
    Traefik->>Keycloak: Validate token/cookie
    Keycloak->>Traefik: Token/cookie is valid
    Traefik->>Services: Forward request to services
    Services->>Traefik: Response back to Traefik
    Traefik->>User: Return service response
```
## Docker Configuration

=== "docker-compose.yml"

    ``` yaml
    version: '3.7'

    services:
      postgres:
        image: postgres:16.2
        volumes:
          - /srv/containers/keycloak/db:/var/lib/postgresql/data
        environment:
          POSTGRES_DB: ${POSTGRES_DB}
          POSTGRES_USER: ${POSTGRES_USER}
          POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U keycloak"]
          interval: 10s
          timeout: 5s
          retries: 5
        networks:
          keycloak_internal_network: # Network for internal communication
            ipv4_address: 172.16.238.3 # Static IP for PostgreSQL in internal network

      keycloak:
        image: quay.io/keycloak/keycloak:23.0.6
        command: start
        volumes:
          - /srv/containers/keycloak/themes:/opt/keycloak/themes
          - /srv/containers/keycloak/base-theme:/opt/keycloak/themes/base
        environment:
          TZ: America/Denver # (1)
          KC_PROXY_ADDRESS_FORWARDING: true # (2)
          KC_HOSTNAME_STRICT: false
          KC_HOSTNAME: auth.bunny-lab.io # (3)
          KC_PROXY: edge # (4)
          KC_HTTP_ENABLED: true
          KC_DB: postgres
          KC_DB_USERNAME: ${POSTGRES_USER}
          KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
          KC_DB_URL_HOST: postgres
          KC_DB_URL_PORT: 5432
          KC_DB_URL_DATABASE: ${POSTGRES_DB}
          KC_TRANSACTION_RECOVERY: true
          KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
          KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
          KC_HEALTH_ENABLED: true
          DB_POOL_MAX_SIZE: 20 # (5)
          DB_POOL_MIN_SIZE: 5 # (6)
          DB_POOL_ACQUISITION_TIMEOUT: 30 # (7)
          DB_POOL_IDLE_TIMEOUT: 300 # (8)
          JDBC_PARAMS: "connectTimeout=30"
          KC_HOSTNAME_DEBUG: false # (9)
        ports:
          - 8080:8080
        restart: always
        depends_on:
          postgres:
            condition: service_healthy
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:8080/auth"] # Health check for Keycloak
          interval: 30s # Health check interval
          timeout: 10s # Health check timeout
          retries: 3 # Health check retries
        networks:
          docker_network:
            ipv4_address: 192.168.5.2
          keycloak_internal_network: # Network for internal communication
            ipv4_address: 172.16.238.2 # Static IP for Keycloak in internal network

    networks:
      default:
        external:
          name: docker_network
      docker_network:
        external: true
      keycloak_internal_network: # Internal network for private communication
        driver: bridge # Network driver
        ipam: # IP address management
          config:
            - subnet: 172.16.238.0/24 # Subnet for internal network

    ```
    
    1.  This sets the timezone of the Keycloak server to your timezone.  This is not really necessary according to the official documentation, however I just like to add it to all of my containers as a baseline environment variable to add
    2.  This assumes you are running Keycloak behind a reverse proxy, in my particular case, Traefik
    3.  Set this to the FQDN that you are expecting to reach the Keycloak server at behind your reverse proxy
    4.  This assumes you are running Keycloak behind a reverse proxy, in my particular case, Traefik
    5.  Maximum connections in the database pool
    6.  Minimum idle connections in the database pool
    7.  Timeout for acquiring a connection from the database pool
    8.  Timeout for closing idle connections to the database
    9.  If this is enabled, Navigate to https://auth.bunny-lab.io/realms/master/hostname-debug to troubleshoot issues with the deployment if you experience any issues logging into the web portal or admin UI

=== ".env"

    ``` yaml
    POSTGRES_DB=keycloak
    POSTGRES_USER=keycloak
    POSTGRES_PASSWORD=SomethingSecure # (1)
    KEYCLOAK_ADMIN=admin
    KEYCLOAK_ADMIN_PASSWORD=SomethingSuperSecureToLoginAsAdmin # (2)
    ```
    
    1.  This is used internally by Keycloak to interact with the PostgreSQL database server
    2.  This is used to log into the web admin portal at https://auth.bunny-lab.io

## Traefik Reverse Proxy Configuration
If the container does not run on the same host as Traefik, you will need to manually add configuration to Traefik's dynamic config file, outlined below.
``` yaml
http:
  routers:
    auth:
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
      service: auth
      rule: Host(`auth.bunny-lab.io`)
      middlewares:
        - auth-headers

  services:
    auth:
      loadBalancer:
        servers:
          - url: http://192.168.5.2:8080
        passHostHeader: true

  middlewares:
    auth-headers:
      headers:
        sslRedirect: true
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        stsPreload: true
        forceSTSHeader: true
        customRequestHeaders:
          X-Forwarded-Proto: https
          X-Forwarded-Port: "443"
```

# Traefik Keycloak Middleware
At this point, we need to add the official Keycloak plugin to Traefik's main configuration.  In this example, it will be assumed you need to configure this in Portainer/Docker Compose, and not via a static yml/toml file.  Assume you follow the [Docker Compose based Traefik Deployment](https://docs.bunny-lab.io/Docker%20%26%20Kubernetes/Docker/Docker%20Compose/Traefik/).

## Install Keycloak Plugin
If you do not already have the following added to the end of your `command:` section of the docker-compose.yml file in Portainer, go ahead and add it:
``` yaml
      # Keycloak plugin configuration
      - "--experimental.plugins.keycloakopenid.moduleName=github.com/Gwojda/keycloakopenid"
      - "--experimental.plugins.keycloakopenid.version=v0.1.34"
```

## Add Middleware to Traefik Dynamic Configuration
You will want to ensure the following exists in the dynamically-loaded config file folder, you can name the file whatever you want, but it will be a one-all middleware for any services you want to have communicating as a specific OAuth2 `Client ID`.  For example, you might want to have some services exist in a particular realm of Keycloak, or to have different client rules apply to certain services.  If this is the case, you can create multiple middlewares in this single yaml file, each handling a different service / realm.  It can get pretty complicated if you want to handle a multi-tenant environment, such as one seen in an enterprise environment.

```jsx title="keycloak-middleware.yml"
http:
  middlewares:
    auth-bunny-lab-io:
      plugin:
        keycloakopenid:
          KeycloakURL: "https://auth.bunny-lab.io" # <- Also supports complete URL, e.g. https://my-keycloak-url.com/auth
          ClientID: "traefik-reverse-proxy"
          ClientSecret: "https://auth.bunny-lab.io > Clients > traefik-reverse-proxy > Credentials > Client Secret"
          KeycloakRealm: "master"
          Scope: "openid profile email"
          TokenCookieName: "AUTH_TOKEN"
          UseAuthHeader: "false"
#          IgnorePathPrefixes: "/api,/favicon.ico [comma deliminated] (optional)"
```

## Configure Valid Redirect URLs
At this point, within Keycloak, you need to configure domains that you are allowed to visit after authenticating.  You can do this with wildcards, but generally you navigate to "**https://auth.bunny-lab.io > Clients > traefik-reverse-proxy > Valid redirect URIs**"  A simple example is adding `https://tools.bunny-lab.io/*` to the list of valid redirect URLs.  If the site is not in this list, even if it has the middleware configured in Traefik, it will fail to authenticate and not let the user proceed to the website being protected behind Keycloak.

## Adding Middleware to Dynamic Traefik Service Config Files
At this point, you are in the final stretch, you just need to add the middleware to the Traefik dynamic config files to ensure that it routes the traffic to Keycloak when someone attempts to access that service.  Put the following middleware section under the `routers:` section of the config file.

``` yaml
      middlewares:
        - auth-bunny-lab-io  # Referencing the Keycloak Server
```

A full example config file would look like the following:
``` yaml
http:
  routers:
    example:
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
      service: example
      rule: Host(`example.bunny-lab.io`)
      middlewares:
        - auth-bunny-lab-io  # Referencing the Keycloak Server Traefik Middleware

  services:
    example:
      loadBalancer:
        servers:
          - url: http://192.168.5.16:80
        passHostHeader: true
```