Skip to content

Keycloak Configuration

This guide walks through setting up a Keycloak realm, client, user, roles, scopes, and mappers needed for TQPro authentication.

Two client configurations are documented: - oauth2-proxy mode (legacy): Confidential client with client secret, used with oauth2-proxy - Native OIDC mode (TQ-51): Public client with PKCE, used with oidc-client-ts in the frontend

Setup of other realms is similar.


🔧 1. Create Realm tqpro-adm

  1. Log in to Keycloak Admin Console
  2. From the top-left dropdown, click "Add Realm"
  3. Name it: tqpro-adm
  4. Click Create

🧩 2. Create Client tqweb-adm

  • Go to Clients → Create
  • Client ID: tqweb-adm
  • Client Type: OpenID Connect
  • Root URL: https://tqadm-dev.vanevski.net
  • Click Save

Client Settings (oauth2-proxy mode):

  • Access Type: Confidential
  • Standard Flow Enabled: Yes
  • Valid Redirect URIs:
    https://tqadm-dev.vanevski.net/oauth2/callback
    
  • Web Origins:
    https://tqadm-dev.vanevski.net (or *)
    
  • Post Logout Redirect URIs:
    https://tqadm-dev.vanevski.net/loggedout.html
    

Credentials (oauth2-proxy mode):

  • Go to the Credentials tab
  • Copy the Secret value — this goes into oauth2-proxy config

Client Settings (Native OIDC mode — TQ-51):

For native OIDC authentication where the frontend handles the OIDC flow directly:

  • Access Type: Public (no client secret)
  • Client Authentication: Off
  • Standard Flow Enabled: Yes
  • Proof Key for Code Exchange (PKCE): S256
  • Valid Redirect URIs:
    https://tqadm-dev.vanevski.net/callback.html
    https://tqadm-dev.vanevski.net/silent-renew.html
    
  • Web Origins:
    https://tqadm-dev.vanevski.net
    
  • Post Logout Redirect URIs:
    https://tqadm-dev.vanevski.net/logout.html
    

No client secret is needed since PKCE provides the security for public clients. Configure the auth-mode property in tlinqapi.properties to native-oidc or hybrid to enable JWT validation on the backend.


👤 3. Create User nvanevski

  • Go to Users → Add User
    • Username: nvanevski
    • Email: nvanevski@example.com
    • First Name: Nikola
    • Last Name: Vanevski
    • Email Verified: ✅ Yes
  • Click Create

Set Password

  • Go to Users → nvanevski → Credentials
    • Set password: e.g., changeme
    • Temporary: ❌ Off
    • Click Set Password

🎭 4. Create Roles agent and admin

  • Go to Roles → Add Role
    • Name: agent
  • Repeat to add admin

Assign Roles to User

  • Go to Users → nvanevski → Role Mappings
    • Select agent and admin from Available Roles
    • Click Add Selected

📚 5. Create Client Scope audience-tqweb-adm

This ensures the access token contains aud: tqweb-adm

  • Go to Client Scopes → Create
    • Name: audience-tqweb-adm
    • Protocol: openid-connect
  • Click Save

Add Mapper

  • Go to Client Scopes → audience-tqweb-adm → Mappers → Create
    • Name: audience-mapper
    • Mapper Type: Audience
    • Included Client Audience: tqweb-adm
    • Token Type: Access token
  • Click Save

Assign Scope to Client

  • Go to Clients → tqweb-adm → Client Scopes
    • Under "Assigned Default Client Scopes", click Add
    • Choose audience-tqweb-adm
    • Click Add

🗺 6. Optional: Expose Roles in Token

If you want oauth2-proxy to access user roles via token:

Add a mapper to client:

  • Go to Clients → tqweb-adm → Mappers → Create
    • Name: user-roles
    • Mapper Type: User Realm Role
    • Token Claim Name: roles
    • Claim JSON Type: String
    • Multivalued: ✅
    • Add to ID token: ✅
    • Add to Access token: ✅

🎨 7. Custom Login Theme

Keycloak's default login page can be branded to match TQPro. The approach inherits all default Keycloak FreeMarker templates and only overrides the CSS — no login.ftl changes needed for basic branding.

Directory Structure

config/keycloak-theme/tqpro/
  login/
    theme.properties
    resources/
      css/login.css
      img/logo-full-horiz-1024.webp

Create the Theme

mkdir -p config/keycloak-theme/tqpro/login/resources/css
mkdir -p config/keycloak-theme/tqpro/login/resources/img

# Copy the company logo
cp tqweb-adm/adm/logo-full-horiz-1024.webp config/keycloak-theme/tqpro/login/resources/img/

theme.properties

At config/keycloak-theme/tqpro/login/theme.properties:

parent=keycloak
import=common/keycloak
styles=css/login.css

The parent=keycloak line inherits all default templates. Only the CSS is overridden.

login.css

At config/keycloak-theme/tqpro/login/resources/css/login.css:

/* TQPro / Perun Tours - Keycloak Login Theme */

/* Background */
body.kcBodyClass {
    background: linear-gradient(135deg, #362c5d 0%, #2a2149 50%, #1a1533 100%) !important;
    font-family: 'Inter', 'Noto Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* Hide the default Keycloak header/branding */
#kc-header-wrapper {
    display: none !important;
}

/* Login card */
#kc-form-wrapper,
.card-pf {
    border: none !important;
    border-radius: 12px !important;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3) !important;
}

#kc-login {
    max-width: 420px;
    margin: 0 auto;
}

/* Logo - injected via background image on the header area */
#kc-logo-wrapper {
    background: url('../img/logo-full-horiz-1024.webp') center center no-repeat !important;
    background-size: contain !important;
    height: 60px !important;
    max-width: 280px;
    margin: 0 auto 1.5rem auto;
}

#kc-logo-wrapper a {
    visibility: hidden;
}

/* Form inputs */
#kc-form-login input[type="text"],
#kc-form-login input[type="password"] {
    border-radius: 0.375rem;
    border: 1px solid #dee2e6;
    padding: 0.6rem 0.75rem;
    font-size: 1rem;
}

#kc-form-login input[type="text"]:focus,
#kc-form-login input[type="password"]:focus {
    box-shadow: 0 0 0 0.25rem rgba(54, 44, 93, 0.25);
    border-color: #362c5d;
    outline: none;
}

/* Login button */
#kc-login {
    background: #362c5d !important;
    border-color: #362c5d !important;
    padding: 0.6rem !important;
    font-weight: 600 !important;
    border-radius: 0.375rem !important;
    width: 100%;
}

#kc-login:hover {
    background: #2a2149 !important;
    border-color: #2a2149 !important;
}

/* Error messages */
.alert-error,
.kc-feedback-text {
    color: #842029;
    background-color: #f8d7da;
    border-color: #f5c2c7;
    border-radius: 0.375rem;
    padding: 0.75rem 1rem;
}

/* Hide elements not needed */
#kc-info-wrapper,
#kc-registration {
    display: none;
}

/* "Remember me" checkbox styling */
#kc-form-options label {
    color: #6c757d;
    font-size: 0.875rem;
}

/* Links */
a { color: #362c5d; }
a:hover { color: #2a2149; }

Deploy the Theme

Docker (volume mount in docker-compose.yaml):

Add to the keycloak service:

volumes:
  - ./keycloak-theme/tqpro:/opt/keycloak/themes/tqpro:ro

Docker (quick deploy without recomposing):

docker cp config/keycloak-theme/tqpro keycloak:/opt/keycloak/themes/tqpro

Bare-metal Keycloak:

cp -r config/keycloak-theme/tqpro /opt/keycloak/themes/

Activate the Theme

  1. Open Keycloak Admin Console
  2. Go to Realm SettingsThemes tab
  3. Set Login theme to tqpro
  4. Click Save

No container restart is needed — the theme is picked up immediately.

Customizing HTML Structure

If CSS-only branding is not enough and you need to change the HTML layout, add a FreeMarker template override at config/keycloak-theme/tqpro/login/login.ftl. Since parent=keycloak is set, you only need to override the specific templates you want to change. Copy the original from the Keycloak source and modify it.


🔑 8. Realm Settings (Optional)

Set Session & Token Timeout

  • Realm Settings → Tokens
    • Access Token Lifespan: 5 minutes
    • SSO Session Idle: 30 minutes

🌐 9. Test OAuth2 Flow

  • Visit: https://tqadm-dev.vanevski.net
  • Authenticate with nvanevski
  • Inspect token using developer tools or pass_access_token = true

You should see: - "preferred_username": "nvanevski" - "aud": "tqweb-adm" - "roles": ["agent", "admin"]