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 emailfirstName,lastNamedepartment- For access groupingmanager- For approval workflowsmfaEnforced- True for all userssshPublicKey- For SSH access provisioning
Integration Configurations
1. GitHub (SAML + SCIM)
Purpose: Single sign-on for GitHub organization + automated team sync
Setup:
- GitHub Organization Settings → Security → Authentication
- Enable SAML SSO with Keycloak
- 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,#devengineering-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:
- User visits
docs.company.com - oauth2-proxy redirects to Keycloak login
- User authenticates with Keycloak (+ MFA)
- Keycloak redirects back with token
- 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:
- HR system calls Keycloak API to create user
- Keycloak creates user in
employeesgroup - Sends welcome email with MFA setup instructions
- User sets up MFA (required before first login)
- SCIM sync provisions user to all connected systems
- 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:
- User added to/removed from Keycloak group
- Keycloak triggers webhooks to notify systems
- GitHub SCIM sync updates teams (15 min)
- Rocket.Chat updates roles (real-time)
- Netbird updates network policies (5 min)
- Ansible syncs SSH keys (30 min cron)
Offboarding
Triggered by: HR marks employee as terminated Automated flow:
- Keycloak account disabled (can be re-enabled for contractors)
- All SSO sessions immediately invalidated
- GitHub access removed via SCIM
- Rocket.Chat account deactivated
- Netbird VPN access revoked
- SSH keys removed from all servers (next Ansible run)
- 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:
- TOTP (Google Authenticator, Authy, 1Password) - Recommended
- WebAuthn (YubiKey, Touch ID, Windows Hello)
- 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:
- Login to Keycloak account:
https://auth.company.com/realms/company-realm/account - Go to "Signing in" → "Two-factor authentication"
- Add authenticator app (scan QR code)
- 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:
- Check if account is enabled in Keycloak
- Verify MFA is set up
- Check for account lockout (too many failed attempts)
- Verify email/username is correct
SSO not working for a service:
- Check client configuration in Keycloak
- Verify redirect URIs match
- Check client secret hasn't expired
- Look at Keycloak server logs:
/var/log/keycloak/
Group sync not working:
- Check SCIM/SAML client is active
- Verify webhook endpoints are reachable
- Check sync logs in Keycloak
- Manual trigger:
POST /admin/realms/company-realm/client-scopes/{id}/sync
Security Best Practices
- Regular Audits: Review Keycloak access logs monthly
- Client Secret Rotation: Rotate client secrets every 90 days
- Session Timeouts: SSO sessions expire after 8 hours of inactivity
- IP Allowlisting: Restrict admin console to office IPs + VPN
- Admin Accounts: Separate admin accounts (not daily-use accounts)
References
- Keycloak Documentation: https://www.keycloak.org/docs/
- OIDC Spec: https://openid.net/connect/
- SAML 2.0 Spec: http://docs.oasis-open.org/security/saml/
- OAuth2 Proxy: https://oauth2-proxy.github.io/oauth2-proxy/