Skip to main content

Keycloak Integration Guide

Overview

Keycloak is our identity provider (IdP) and single source of truth for authentication and authorization across all systems.

Architecture

┌─────────────┐
│ Keycloak │ (Identity Source of Truth)
│ (SSO/MFA) │
└──────┬──────┘

├─→ GitHub (via SAML/SCIM)
├─→ Rocket.Chat (via OIDC)
├─→ Docusaurus (via oauth2-proxy)
├─→ DigitalOcean (via SAML SSO)
├─→ Netbird (via OIDC)
└─→ Custom Apps (via OIDC/SAML)

Keycloak Configuration

Instance URL: https://auth.company.com Realm: company-realm Admin Console: https://auth.company.com/admin

Group Structure

Groups in Keycloak map to roles and permissions across all systems:

company-realm/
├── employees (all employees)
│ ├── engineering
│ │ ├── engineering-frontend
│ │ ├── engineering-backend
│ │ ├── engineering-devops
│ │ └── engineering-mobile
│ ├── product
│ ├── design
│ └── operations
├── managers
├── access-elevated
│ ├── production-read
│ ├── production-write
│ └── infrastructure-admin
└── system-admins

Principle: Groups are hierarchical - being in engineering-backend automatically includes you in engineering and employees.

User Attributes

Each user has:

  • username - Primary identifier (firstname.lastname)
  • email - Company email
  • firstName, lastName
  • department - For access grouping
  • manager - For approval workflows
  • mfaEnforced - True for all users
  • sshPublicKey - For SSH access provisioning

Integration Configurations

1. GitHub (SAML + SCIM)

Purpose: Single sign-on for GitHub organization + automated team sync

Setup:

  1. GitHub Organization Settings → Security → Authentication
  2. Enable SAML SSO with Keycloak
  3. Configure SCIM for automated user provisioning

Group Mapping:

Keycloak Group          → GitHub Team
-----------------------------------------
engineering → @all-engineering
engineering-backend → @backend-team
engineering-frontend → @frontend-team
engineering-devops → @devops-team
engineering-mobile → @mobile-team

How it works:

  • User added to Keycloak group engineering-backend
  • SCIM sync runs (every 15 minutes)
  • User automatically added to GitHub team @backend-team
  • Repository access granted based on team membership

Configuration file: keycloak-configs/github-scim-client.json

2. Rocket.Chat (OIDC)

Purpose: SSO for internal team chat

Setup:

  • Rocket.Chat Admin → OAuth → Custom OAuth: Keycloak
  • Client ID: rocketchat-client
  • Authorization URL: https://auth.company.com/realms/company-realm/protocol/openid-connect/auth
  • Token URL: https://auth.company.com/realms/company-realm/protocol/openid-connect/token

Attribute Mapping:

Keycloak Attribute → Rocket.Chat Field
----------------------------------------
username → username
email → email
groups → roles (for #channel auto-join)

Auto-join Channels:

  • All employees → #general, #announcements
  • engineering#engineering, #dev
  • engineering-backend#backend

Configuration: ansible/roles/rocketchat/templates/oauth-config.json.j2

3. Docusaurus (oauth2-proxy)

Purpose: Protect internal documentation (this site) with SSO

Setup:

  • oauth2-proxy sits in front of Docusaurus
  • Uses Keycloak OIDC for authentication
  • Nginx reverse proxy configuration

Access Control:

Keycloak Group    → Docusaurus Access
--------------------------------------
employees → Full read access
engineering → Can edit docs (via Git)
system-admins → Admin access

Configuration: ansible/roles/docusaurus/templates/oauth2-proxy.cfg.j2

How it works:

  1. User visits docs.company.com
  2. oauth2-proxy redirects to Keycloak login
  3. User authenticates with Keycloak (+ MFA)
  4. Keycloak redirects back with token
  5. oauth2-proxy validates token and proxies to Docusaurus

4. Netbird VPN (OIDC)

Purpose: VPN access control via Keycloak groups

Setup:

  • Netbird management interface configured with Keycloak OIDC
  • Network access policies based on Keycloak groups

Network Policies:

Keycloak Group         → Netbird Network Access
------------------------------------------------
engineering → dev-network, staging-network
production-read → prod-read-network
production-write → prod-write-network
infrastructure-admin → admin-network (all access)

Configuration: Netbird Dashboard → Settings → IdP

Example Policy:

{
"name": "Backend Production Read",
"source_groups": ["engineering-backend", "production-read"],
"destination": "prod-backend-servers",
"action": "accept",
"ports": ["22", "80", "443"]
}

5. DigitalOcean (SAML SSO)

Purpose: Cloud infrastructure access via SSO

Setup:

  • DigitalOcean Team Settings → Authentication → SAML
  • Configure Keycloak as SAML IdP

Role Mapping:

Keycloak Group        → DigitalOcean Role
-------------------------------------------
engineering → Viewer (read-only)
engineering-devops → Developer (manage resources)
infrastructure-admin → Owner (full access)

Configuration: keycloak-configs/digitalocean-saml-client.json

User Lifecycle

Onboarding

Triggered by: HR adds employee to HR system Automated flow:

  1. HR system calls Keycloak API to create user
  2. Keycloak creates user in employees group
  3. Sends welcome email with MFA setup instructions
  4. User sets up MFA (required before first login)
  5. SCIM sync provisions user to all connected systems
  6. User can now login to all company systems with one credential

Script: scripts/onboard-user.sh

#!/bin/bash
# Usage: ./onboard-user.sh firstname lastname email department
./scripts/onboard-user.sh John Doe john.doe@company.com engineering

Access Changes

Triggered by: Manager or automated process Flow:

  1. User added to/removed from Keycloak group
  2. Keycloak triggers webhooks to notify systems
  3. GitHub SCIM sync updates teams (15 min)
  4. Rocket.Chat updates roles (real-time)
  5. Netbird updates network policies (5 min)
  6. Ansible syncs SSH keys (30 min cron)

Offboarding

Triggered by: HR marks employee as terminated Automated flow:

  1. Keycloak account disabled (can be re-enabled for contractors)
  2. All SSO sessions immediately invalidated
  3. GitHub access removed via SCIM
  4. Rocket.Chat account deactivated
  5. Netbird VPN access revoked
  6. SSH keys removed from all servers (next Ansible run)
  7. Audit log generated and stored

Script: scripts/offboard-user.sh

#!/bin/bash
# Usage: ./offboard-user.sh username
./scripts/offboard-user.sh john.doe

Important: This disables but doesn't delete the account (for audit purposes).

MFA Configuration

MFA is mandatory for all users.

Supported Methods:

  1. TOTP (Google Authenticator, Authy, 1Password) - Recommended
  2. WebAuthn (YubiKey, Touch ID, Windows Hello)
  3. SMS (backup only, not recommended)

Enforcement:

  • New users must set up MFA before first login
  • Existing users: 30-day grace period to enable MFA
  • After grace period: Account locked until MFA enabled
  • MFA reset requires security team approval

User Setup:

  1. Login to Keycloak account: https://auth.company.com/realms/company-realm/account
  2. Go to "Signing in" → "Two-factor authentication"
  3. Add authenticator app (scan QR code)
  4. Save recovery codes (store securely!)

API Access

For programmatic access to Keycloak:

Service Accounts

Create service accounts for CI/CD, automation:

Example - GitHub Actions service account:

# In Keycloak Admin Console
Client ID: github-actions-bot
Access Type: confidential
Service Accounts Enabled: true
Valid Redirect URIs: https://github.com/*

Usage in GitHub Actions:

- name: Get Keycloak token
env:
CLIENT_ID: ${{ secrets.KEYCLOAK_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }}
run: |
TOKEN=$(curl -X POST "https://auth.company.com/realms/company-realm/protocol/openid-connect/token" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "grant_type=client_credentials")

Keycloak Admin CLI

For manual administration:

# Login
/opt/keycloak/bin/kcadm.sh config credentials \
--server https://auth.company.com \
--realm company-realm \
--user admin

# Create user
/opt/keycloak/bin/kcadm.sh create users \
-s username=john.doe \
-s enabled=true \
-s email=john.doe@company.com

# Add user to group
/opt/keycloak/bin/kcadm.sh update users/{user-id}/groups/{group-id} \
-r company-realm

Monitoring & Alerts

Metrics tracked:

  • Failed login attempts (alert if > 5 in 10 min)
  • MFA failures (alert if > 3 in 5 min)
  • Account lockouts
  • Group membership changes
  • Privilege escalations

Alerting:

  • High-severity events → #security Rocket.Chat channel
  • All events → Keycloak audit log
  • Weekly summary → Emailed to security team

Dashboard: https://monitoring.company.com/keycloak

Backup & Disaster Recovery

Keycloak database backed up:

  • Hourly snapshots (retained 24 hours)
  • Daily backups (retained 30 days)
  • Monthly backups (retained 1 year)

Stored in: DigitalOcean Spaces (encrypted)

Recovery procedure: See 06-runbooks/keycloak-disaster-recovery.md

Test frequency: Quarterly DR drills

Troubleshooting

User can't login:

  1. Check if account is enabled in Keycloak
  2. Verify MFA is set up
  3. Check for account lockout (too many failed attempts)
  4. Verify email/username is correct

SSO not working for a service:

  1. Check client configuration in Keycloak
  2. Verify redirect URIs match
  3. Check client secret hasn't expired
  4. Look at Keycloak server logs: /var/log/keycloak/

Group sync not working:

  1. Check SCIM/SAML client is active
  2. Verify webhook endpoints are reachable
  3. Check sync logs in Keycloak
  4. Manual trigger: POST /admin/realms/company-realm/client-scopes/{id}/sync

Security Best Practices

  1. Regular Audits: Review Keycloak access logs monthly
  2. Client Secret Rotation: Rotate client secrets every 90 days
  3. Session Timeouts: SSO sessions expire after 8 hours of inactivity
  4. IP Allowlisting: Restrict admin console to office IPs + VPN
  5. Admin Accounts: Separate admin accounts (not daily-use accounts)

References