Add comprehensive Keycloak setup guide and external service support
KEYCLOAK-SETUP-GUIDE.md: Complete manual explaining Keycloak concepts, manual setup, and external services WHAT'S A REALM: - Isolated container for users/clients/config - Like a "company" or "organization" - master realm = admin only - homelab realm = your actual users - Fully isolated from each other WHAT'S AN OAUTH2 CLIENT: - Each service (ActualBudget, etc.) is a "client" - Needs Client ID, Secret, and Redirect URIs - Redirect URIs must match EXACTLY - Guide explains the authentication flow MANUAL SETUP INSTRUCTIONS: - Step-by-step via web UI - Create realm manually - Create OAuth clients manually - Configure redirect URIs - Create users and set passwords - Test the setup EXTERNAL SERVICE SUPPORT (Pikapod, etc.): Script now asks about setup type: 1. Local only (http://localhost) 2. Public domain (https://yourdomain.com) 3. Both local and public For external services: - Prompts for your public domain - Warns that Keycloak MUST be accessible at https://auth.yourdomain.com - Checks if Caddy/DNS are configured - Asks for external service URL (e.g., Pikapod) - Configures redirect URIs for all scenarios REDIRECT URIS NOW INCLUDE: - http://localhost:5006/* (local dev) - https://budget.yourdomain.com/* (self-hosted) - https://actualbudget-abc.pikapod.net/* (external) - Multiple patterns for flexibility SAVED CONFIG FILES UPDATED: - Shows LOCAL DEVELOPMENT URLs - Shows PRODUCTION URLs (if public domain set) - Shows EXTERNAL SERVICE URLs (if external service set) - Lists all configured redirect URIs - Clear instructions for each scenario CADDY CONFIGURATION GUIDE: - How to configure DNS A/CNAME records - Caddyfile example for Keycloak - Security headers included - Step-by-step setup for external access RECONFIGURATION SUPPORT: - Guide explains how to add realms manually - Guide explains how to add clients manually - CLI examples for adding realms/users/clients - Can re-run script to configure additional realms COMMON USE CASES: 1. All local services 2. Self-hosted with domain 3. Mixed (local + external like Pikapod) Each use case explained with complete examples TROUBLESHOOTING: - Invalid redirect URI - Client not found - Invalid client secret - External service can't reach Keycloak - CORS/redirect failures - Admin console login issues With this update, users can: ✅ Understand what Keycloak is and how it works ✅ Configure it manually if they prefer ✅ Use it with external services like Pikapod ✅ Set up proper DNS/Caddy for production ✅ Troubleshoot common issues ✅ Add realms and clients later
This commit is contained in:
@@ -0,0 +1,678 @@
|
|||||||
|
# Keycloak Setup Guide
|
||||||
|
## Complete Manual and Automated Configuration Guide
|
||||||
|
|
||||||
|
This guide explains Keycloak concepts and how to configure it both automatically (via the script) and manually (via the web UI).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
1. [What is Keycloak?](#what-is-keycloak)
|
||||||
|
2. [Key Concepts](#key-concepts)
|
||||||
|
3. [Automated Setup (via Script)](#automated-setup)
|
||||||
|
4. [Manual Setup (via Web UI)](#manual-setup)
|
||||||
|
5. [Configuring External Services](#configuring-external-services)
|
||||||
|
6. [Reconfiguration & Adding Realms](#reconfiguration)
|
||||||
|
7. [Common Use Cases](#common-use-cases)
|
||||||
|
8. [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is Keycloak?
|
||||||
|
|
||||||
|
Keycloak is an **Identity and Access Management (IAM)** system that provides:
|
||||||
|
- **Single Sign-On (SSO)**: Log in once, access all your services
|
||||||
|
- **User Management**: Create, manage, and authenticate users in one place
|
||||||
|
- **OAuth2/OIDC**: Industry-standard authentication for web apps
|
||||||
|
- **Social Login**: Allow login via Google, GitHub, etc.
|
||||||
|
- **Multi-Factor Authentication (MFA)**: Add extra security with 2FA/TOTP
|
||||||
|
- **LDAP/Active Directory Integration**: Connect to existing user directories
|
||||||
|
|
||||||
|
**Think of Keycloak as:** A centralized login system for all your self-hosted services.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
### 1. **Realm**
|
||||||
|
A **realm** is an isolated container for users, clients, and configuration.
|
||||||
|
|
||||||
|
**Analogy:** Think of a realm like a "company" or "organization" in Keycloak.
|
||||||
|
|
||||||
|
**Why you need it:**
|
||||||
|
- The default `master` realm is for Keycloak admin only
|
||||||
|
- You create a separate realm (e.g., `homelab`) for your actual users and applications
|
||||||
|
- Realms are completely isolated - users in one realm can't access another
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
- `master` realm: Only for Keycloak administrators
|
||||||
|
- `homelab` realm: For your personal services (ActualBudget, Jellyfin, etc.)
|
||||||
|
- `family` realm: Separate realm for family members (optional)
|
||||||
|
|
||||||
|
### 2. **OAuth2/OpenID Connect (OIDC) Client**
|
||||||
|
A **client** is an application that uses Keycloak for authentication.
|
||||||
|
|
||||||
|
**Analogy:** Each service (ActualBudget, Jellyfin, etc.) is a "client" that asks Keycloak "Is this user allowed to log in?"
|
||||||
|
|
||||||
|
**Required information:**
|
||||||
|
- **Client ID**: Name of the application (e.g., `actualbudget`)
|
||||||
|
- **Client Secret**: Password for the application (auto-generated, 64-char hex)
|
||||||
|
- **Redirect URIs**: Where Keycloak sends users after login
|
||||||
|
- Example: `https://budget.yourdomain.com/*`
|
||||||
|
- Must match EXACTLY or login will fail
|
||||||
|
|
||||||
|
**Flow:**
|
||||||
|
1. User clicks "Login" in ActualBudget
|
||||||
|
2. ActualBudget redirects to Keycloak: `https://auth.yourdomain.com/login`
|
||||||
|
3. User logs in with username/password
|
||||||
|
4. Keycloak redirects back to ActualBudget: `https://budget.yourdomain.com/callback`
|
||||||
|
5. ActualBudget gets user info and logs them in
|
||||||
|
|
||||||
|
### 3. **Users**
|
||||||
|
A **user** is a person who can log in to your services.
|
||||||
|
|
||||||
|
**User attributes:**
|
||||||
|
- Username (required, unique)
|
||||||
|
- Email (optional but recommended)
|
||||||
|
- First name / Last name (optional)
|
||||||
|
- Password (set via Credentials tab)
|
||||||
|
- Email verified (set to true to skip verification)
|
||||||
|
- Enabled (must be true for user to log in)
|
||||||
|
|
||||||
|
### 4. **Redirect URIs**
|
||||||
|
**Critical concept:** The redirect URI is where Keycloak sends the user after successful login.
|
||||||
|
|
||||||
|
**Common mistakes:**
|
||||||
|
- ❌ `http://localhost:5006` (won't work for external services)
|
||||||
|
- ❌ `https://budget.example.com` (missing wildcard or path)
|
||||||
|
- ✅ `https://budget.example.com/*` (correct - allows all paths)
|
||||||
|
|
||||||
|
**For external services (like Pikapod):**
|
||||||
|
- Pikapod gives you a URL like: `https://actualbudget-abc123.pikapod.net`
|
||||||
|
- Your redirect URI: `https://actualbudget-abc123.pikapod.net/*`
|
||||||
|
- Your Keycloak URL: `https://auth.yourdomain.com` (must be publicly accessible)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Automated Setup (via Script)
|
||||||
|
|
||||||
|
The script automates everything for you. Here's what it does:
|
||||||
|
|
||||||
|
### Step 1: Install Keycloak
|
||||||
|
```bash
|
||||||
|
./ubuntu-post-install.sh
|
||||||
|
# Select KEYCLOAK in whiptail menu
|
||||||
|
```
|
||||||
|
|
||||||
|
Prompts:
|
||||||
|
- Admin password (for Keycloak admin console)
|
||||||
|
- Database password (for PostgreSQL)
|
||||||
|
|
||||||
|
### Step 2: Automated Configuration
|
||||||
|
```
|
||||||
|
Configure Keycloak with initial realm and clients? (y/n): y
|
||||||
|
```
|
||||||
|
|
||||||
|
This automatically:
|
||||||
|
1. ✅ Waits for Keycloak to start (health check)
|
||||||
|
2. ✅ Logs in using admin CLI (`kcadm.sh`)
|
||||||
|
3. ✅ Creates a realm (e.g., `homelab`)
|
||||||
|
4. ✅ Creates OAuth client for ActualBudget (if selected)
|
||||||
|
5. ✅ Creates generic OAuth client template
|
||||||
|
6. ✅ Saves all credentials to `~/docker/keycloak/*.txt`
|
||||||
|
7. ✅ Optionally creates initial user
|
||||||
|
|
||||||
|
### Step 3: What Gets Created
|
||||||
|
|
||||||
|
**Realm:** `homelab` (or your custom name)
|
||||||
|
|
||||||
|
**ActualBudget OAuth Client:**
|
||||||
|
- Client ID: `actualbudget`
|
||||||
|
- Client Secret: (saved to `actualbudget-oauth.txt`)
|
||||||
|
- Redirect URIs:
|
||||||
|
- `http://localhost:5006/*` (local development)
|
||||||
|
- `http://yourdomain.com:5006/*` (local with domain)
|
||||||
|
- `https://yourdomain.com/*` (production - any subdomain)
|
||||||
|
- `https://budget.yourdomain.com/*` (specific subdomain)
|
||||||
|
|
||||||
|
**Generic OAuth Client:**
|
||||||
|
- Client ID: `generic-app`
|
||||||
|
- Client Secret: (saved to `generic-oauth.txt`)
|
||||||
|
- Can be cloned for other services
|
||||||
|
|
||||||
|
**Initial User:**
|
||||||
|
- Username, email, password you provide
|
||||||
|
- Immediately active
|
||||||
|
- Can log in to all services
|
||||||
|
|
||||||
|
### Step 4: Configuration Files
|
||||||
|
|
||||||
|
All credentials saved to:
|
||||||
|
```
|
||||||
|
~/docker/keycloak/actualbudget-oauth.txt
|
||||||
|
~/docker/keycloak/generic-oauth.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
These files contain:
|
||||||
|
- Client ID
|
||||||
|
- Client Secret
|
||||||
|
- Authorization URL
|
||||||
|
- Token URL
|
||||||
|
- User Info URL
|
||||||
|
- Instructions for configuring each service
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Setup (via Web UI)
|
||||||
|
|
||||||
|
If you prefer to configure Keycloak manually, or want to add services later:
|
||||||
|
|
||||||
|
### Access Admin Console
|
||||||
|
```
|
||||||
|
URL: http://localhost:8180/admin
|
||||||
|
Username: admin
|
||||||
|
Password: [your admin password]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1: Create a Realm
|
||||||
|
|
||||||
|
1. **Click dropdown** in top-left corner (shows "Master")
|
||||||
|
2. **Click "Create Realm"**
|
||||||
|
3. **Realm name:** `homelab` (or your choice)
|
||||||
|
4. **Click "Create"**
|
||||||
|
|
||||||
|
**Settings to configure:**
|
||||||
|
- **Login tab:**
|
||||||
|
- ✅ User registration: OFF (you create users manually)
|
||||||
|
- ✅ Forgot password: ON (allows password resets)
|
||||||
|
- ✅ Remember me: ON (convenience)
|
||||||
|
- ✅ Login with email: ON (users can use email instead of username)
|
||||||
|
|
||||||
|
- **Email tab:** (optional, for password resets)
|
||||||
|
- Configure SMTP settings if you want email features
|
||||||
|
|
||||||
|
### Step 2: Create an OAuth2 Client (for ActualBudget)
|
||||||
|
|
||||||
|
1. **Switch to your realm** (`homelab`) via dropdown
|
||||||
|
2. **Go to Clients** (left menu)
|
||||||
|
3. **Click "Create client"**
|
||||||
|
|
||||||
|
**General Settings:**
|
||||||
|
- **Client type:** OpenID Connect
|
||||||
|
- **Client ID:** `actualbudget`
|
||||||
|
- **Name:** `ActualBudget`
|
||||||
|
- **Description:** `Personal Finance Management`
|
||||||
|
- **Click "Next"**
|
||||||
|
|
||||||
|
**Capability config:**
|
||||||
|
- ✅ Client authentication: ON (creates a secret)
|
||||||
|
- ✅ Authorization: OFF (not needed)
|
||||||
|
- ✅ Standard flow: ON (authorization code flow)
|
||||||
|
- ✅ Direct access grants: ON (allows username/password)
|
||||||
|
- ❌ Implicit flow: OFF (deprecated)
|
||||||
|
- ❌ Service accounts: OFF (not needed for web apps)
|
||||||
|
- **Click "Next"**
|
||||||
|
|
||||||
|
**Login settings:**
|
||||||
|
|
||||||
|
**Important: Adjust these for your setup!**
|
||||||
|
|
||||||
|
**For local ActualBudget:**
|
||||||
|
```
|
||||||
|
Root URL: http://localhost:5006
|
||||||
|
Home URL: http://localhost:5006
|
||||||
|
Valid redirect URIs:
|
||||||
|
http://localhost:5006/*
|
||||||
|
http://localhost:5006/callback
|
||||||
|
|
||||||
|
Valid post logout redirect URIs: +
|
||||||
|
|
||||||
|
Web origins:
|
||||||
|
http://localhost:5006
|
||||||
|
```
|
||||||
|
|
||||||
|
**For external ActualBudget (Pikapod, etc.):**
|
||||||
|
```
|
||||||
|
Root URL: https://actualbudget-abc123.pikapod.net
|
||||||
|
Home URL: https://actualbudget-abc123.pikapod.net
|
||||||
|
Valid redirect URIs:
|
||||||
|
https://actualbudget-abc123.pikapod.net/*
|
||||||
|
https://actualbudget-abc123.pikapod.net/callback
|
||||||
|
|
||||||
|
Valid post logout redirect URIs: +
|
||||||
|
|
||||||
|
Web origins:
|
||||||
|
https://actualbudget-abc123.pikapod.net
|
||||||
|
```
|
||||||
|
|
||||||
|
**For self-hosted with domain:**
|
||||||
|
```
|
||||||
|
Root URL: https://budget.yourdomain.com
|
||||||
|
Home URL: https://budget.yourdomain.com
|
||||||
|
Valid redirect URIs:
|
||||||
|
https://budget.yourdomain.com/*
|
||||||
|
https://budget.yourdomain.com/callback
|
||||||
|
|
||||||
|
Valid post logout redirect URIs: +
|
||||||
|
|
||||||
|
Web origins:
|
||||||
|
https://budget.yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Click "Save"**
|
||||||
|
|
||||||
|
### Step 3: Get Client Secret
|
||||||
|
|
||||||
|
1. **Go to "Credentials" tab**
|
||||||
|
2. **Copy "Client secret"** (you'll need this for ActualBudget)
|
||||||
|
3. **Save it somewhere safe!**
|
||||||
|
|
||||||
|
### Step 4: Create a User
|
||||||
|
|
||||||
|
1. **Go to Users** (left menu)
|
||||||
|
2. **Click "Create user"**
|
||||||
|
|
||||||
|
**User details:**
|
||||||
|
- **Username:** `john` (required)
|
||||||
|
- **Email:** `john@example.com` (optional but recommended)
|
||||||
|
- **Email verified:** ✅ ON (skip email verification)
|
||||||
|
- **First name:** `John`
|
||||||
|
- **Last name:** `Doe`
|
||||||
|
- **Enabled:** ✅ ON (user can log in)
|
||||||
|
- **Click "Create"**
|
||||||
|
|
||||||
|
**Set password:**
|
||||||
|
1. **Go to "Credentials" tab**
|
||||||
|
2. **Click "Set password"**
|
||||||
|
3. **Enter password** (twice)
|
||||||
|
4. **Temporary:** ❌ OFF (user won't be forced to change it)
|
||||||
|
5. **Click "Save"**
|
||||||
|
6. **Confirm** in popup
|
||||||
|
|
||||||
|
### Step 5: Test Login
|
||||||
|
|
||||||
|
1. **Go to Realm Settings** → **Endpoints**
|
||||||
|
2. **Click "OpenID Endpoint Configuration"** (opens JSON)
|
||||||
|
3. **Find:** `authorization_endpoint`
|
||||||
|
4. **Copy URL** and open in browser
|
||||||
|
5. **Add:** `?client_id=actualbudget&response_type=code&redirect_uri=http://localhost:5006/callback`
|
||||||
|
6. **Log in** with your user
|
||||||
|
7. **You should see:** Redirect to callback URL (may error if ActualBudget not configured, but login works)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuring External Services
|
||||||
|
|
||||||
|
### Keycloak MUST be Publicly Accessible
|
||||||
|
|
||||||
|
**Critical:** For external services like Pikapod, your Keycloak must be accessible from the internet.
|
||||||
|
|
||||||
|
### Requirements:
|
||||||
|
1. ✅ **Domain name** (e.g., `yourdomain.com`)
|
||||||
|
2. ✅ **DNS A record** pointing to your server
|
||||||
|
3. ✅ **Caddy reverse proxy** with HTTPS
|
||||||
|
4. ✅ **Port 80/443 open** in firewall
|
||||||
|
5. ✅ **Keycloak accessible** at `https://auth.yourdomain.com`
|
||||||
|
|
||||||
|
### Setup Caddy for Keycloak
|
||||||
|
|
||||||
|
**Add to Caddyfile:**
|
||||||
|
```caddy
|
||||||
|
auth.yourdomain.com {
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/keycloak-access.log
|
||||||
|
format json
|
||||||
|
level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_proxy localhost:8180
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reload Caddy:**
|
||||||
|
```bash
|
||||||
|
cd ~/docker/caddy
|
||||||
|
docker exec -w /etc/caddy caddy caddy reload
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test:**
|
||||||
|
```
|
||||||
|
https://auth.yourdomain.com/admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure DNS
|
||||||
|
|
||||||
|
**Add A record:**
|
||||||
|
```
|
||||||
|
auth.yourdomain.com → [Your Server IP]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or use CNAME:**
|
||||||
|
```
|
||||||
|
auth → yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: ActualBudget on Pikapod
|
||||||
|
|
||||||
|
**Scenario:**
|
||||||
|
- Keycloak: `https://auth.yourdomain.com` (your server)
|
||||||
|
- ActualBudget: `https://actualbudget-abc123.pikapod.net` (Pikapod)
|
||||||
|
|
||||||
|
**In Keycloak:**
|
||||||
|
|
||||||
|
1. **Create client:** `actualbudget-pikapod`
|
||||||
|
2. **Redirect URIs:**
|
||||||
|
```
|
||||||
|
https://actualbudget-abc123.pikapod.net/*
|
||||||
|
https://actualbudget-abc123.pikapod.net/callback
|
||||||
|
```
|
||||||
|
3. **Web origins:**
|
||||||
|
```
|
||||||
|
https://actualbudget-abc123.pikapod.net
|
||||||
|
```
|
||||||
|
|
||||||
|
**In ActualBudget (Pikapod):**
|
||||||
|
|
||||||
|
Settings → Authentication:
|
||||||
|
```
|
||||||
|
Client ID: actualbudget-pikapod
|
||||||
|
Client Secret: [from Keycloak credentials tab]
|
||||||
|
|
||||||
|
Authorization URL: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/auth
|
||||||
|
Token URL: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/token
|
||||||
|
User Info URL: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/userinfo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flow:**
|
||||||
|
1. User visits `https://actualbudget-abc123.pikapod.net`
|
||||||
|
2. Clicks "Login"
|
||||||
|
3. Redirects to `https://auth.yourdomain.com/realms/homelab/...`
|
||||||
|
4. User logs in
|
||||||
|
5. Redirects back to `https://actualbudget-abc123.pikapod.net/callback`
|
||||||
|
6. User is logged in!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reconfiguration & Adding Realms
|
||||||
|
|
||||||
|
You can re-run the script to add more realms or clients!
|
||||||
|
|
||||||
|
### Option 1: Re-run the Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/docker/keycloak
|
||||||
|
docker compose down
|
||||||
|
cd ~
|
||||||
|
./ubuntu-post-install.sh
|
||||||
|
# Select KEYCLOAK again
|
||||||
|
# Choose "Configure Keycloak..." → Yes
|
||||||
|
# Enter new realm name: "family"
|
||||||
|
# Create new users
|
||||||
|
```
|
||||||
|
|
||||||
|
**This creates:**
|
||||||
|
- New realm with new users
|
||||||
|
- New OAuth clients for that realm
|
||||||
|
- Separate from your existing realm
|
||||||
|
|
||||||
|
### Option 2: Add Realm Manually
|
||||||
|
|
||||||
|
**Via Web UI:**
|
||||||
|
1. Go to admin console
|
||||||
|
2. Click realm dropdown
|
||||||
|
3. "Create Realm"
|
||||||
|
4. Name: `family`
|
||||||
|
5. Repeat client/user creation steps
|
||||||
|
|
||||||
|
### Option 3: Use Script Helper
|
||||||
|
|
||||||
|
The script can be extended to add a helper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/docker/keycloak
|
||||||
|
|
||||||
|
# Login to admin CLI
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \
|
||||||
|
--server http://localhost:8080 \
|
||||||
|
--realm master \
|
||||||
|
--user admin \
|
||||||
|
--password [YOUR_ADMIN_PASSWORD]
|
||||||
|
|
||||||
|
# Create new realm
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh create realms \
|
||||||
|
-s realm=family \
|
||||||
|
-s enabled=true
|
||||||
|
|
||||||
|
# Create new client
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r family \
|
||||||
|
-s clientId=my-new-service \
|
||||||
|
-s enabled=true \
|
||||||
|
-s clientAuthenticatorType=client-secret \
|
||||||
|
-s secret=$(openssl rand -hex 32) \
|
||||||
|
-s 'redirectUris=["https://service.yourdomain.com/*"]'
|
||||||
|
|
||||||
|
# Create new user
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh create users -r family \
|
||||||
|
-s username=alice \
|
||||||
|
-s email=alice@example.com \
|
||||||
|
-s enabled=true
|
||||||
|
|
||||||
|
# Set password
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh set-password -r family \
|
||||||
|
--username alice \
|
||||||
|
--new-password 'AlicePassword123!'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Use Case 1: All Local Services
|
||||||
|
**Setup:**
|
||||||
|
- Keycloak: `http://localhost:8180`
|
||||||
|
- ActualBudget: `http://localhost:5006`
|
||||||
|
- Jellyfin: `http://localhost:8096`
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- No domain needed
|
||||||
|
- Use `localhost` URLs everywhere
|
||||||
|
- Redirect URIs: `http://localhost:PORT/*`
|
||||||
|
|
||||||
|
### Use Case 2: Self-Hosted with Domain
|
||||||
|
**Setup:**
|
||||||
|
- Keycloak: `https://auth.yourdomain.com`
|
||||||
|
- ActualBudget: `https://budget.yourdomain.com`
|
||||||
|
- Jellyfin: `https://jellyfin.yourdomain.com`
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Requires domain + Caddy
|
||||||
|
- Use HTTPS URLs
|
||||||
|
- Redirect URIs: `https://service.yourdomain.com/*`
|
||||||
|
|
||||||
|
### Use Case 3: Mixed (Local + External)
|
||||||
|
**Setup:**
|
||||||
|
- Keycloak: `https://auth.yourdomain.com` (self-hosted)
|
||||||
|
- ActualBudget: `https://actualbudget-abc.pikapod.net` (Pikapod)
|
||||||
|
- Jellyfin: `https://jellyfin.yourdomain.com` (self-hosted)
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Keycloak MUST be publicly accessible
|
||||||
|
- Each service gets its own client
|
||||||
|
- ActualBudget redirect: `https://actualbudget-abc.pikapod.net/*`
|
||||||
|
- Jellyfin redirect: `https://jellyfin.yourdomain.com/*`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: "Invalid redirect URI"
|
||||||
|
**Cause:** Redirect URI in Keycloak doesn't match what the app is using.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
1. Check error message for actual redirect URI
|
||||||
|
2. Add EXACT URI to Keycloak client settings
|
||||||
|
3. Include wildcard: `https://domain.com/*`
|
||||||
|
|
||||||
|
### Issue: "Client not found"
|
||||||
|
**Cause:** Client ID doesn't match.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
1. Check client ID in Keycloak
|
||||||
|
2. Ensure it matches exactly in application
|
||||||
|
3. Case-sensitive!
|
||||||
|
|
||||||
|
### Issue: "Invalid client secret"
|
||||||
|
**Cause:** Wrong secret or expired.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
1. Go to Keycloak → Clients → Credentials
|
||||||
|
2. Copy secret again (or regenerate)
|
||||||
|
3. Update in application
|
||||||
|
|
||||||
|
### Issue: External service can't reach Keycloak
|
||||||
|
**Cause:** Keycloak not publicly accessible.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
1. Ensure Caddy is running: `docker ps | grep caddy`
|
||||||
|
2. Check DNS: `dig auth.yourdomain.com`
|
||||||
|
3. Test URL: `curl https://auth.yourdomain.com`
|
||||||
|
4. Check firewall: `sudo ufw status` (80/443 open?)
|
||||||
|
|
||||||
|
### Issue: Login succeeds but redirect fails
|
||||||
|
**Cause:** CORS or redirect URI mismatch.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
1. Add domain to "Web Origins" in client settings
|
||||||
|
2. Check redirect URI includes protocol (https://)
|
||||||
|
3. Check for typos in domain name
|
||||||
|
|
||||||
|
### Issue: Can't login to Keycloak admin console
|
||||||
|
**Cause:** Container not started or wrong password.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
```bash
|
||||||
|
# Check if running
|
||||||
|
docker ps | grep keycloak
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
docker logs keycloak --tail 50
|
||||||
|
|
||||||
|
# Restart
|
||||||
|
cd ~/docker/keycloak
|
||||||
|
docker compose restart
|
||||||
|
|
||||||
|
# Reset admin password (if needed)
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \
|
||||||
|
--server http://localhost:8080 \
|
||||||
|
--realm master \
|
||||||
|
--user admin \
|
||||||
|
--password NEW_PASSWORD_HERE
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Important URLs
|
||||||
|
|
||||||
|
**Local:**
|
||||||
|
```
|
||||||
|
Admin Console: http://localhost:8180/admin
|
||||||
|
Realm Endpoints: http://localhost:8180/realms/{realm-name}/.well-known/openid-configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
**Production:**
|
||||||
|
```
|
||||||
|
Admin Console: https://auth.yourdomain.com/admin
|
||||||
|
Realm Endpoints: https://auth.yourdomain.com/realms/{realm-name}/.well-known/openid-configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
### OAuth URLs (for realm "homelab")
|
||||||
|
|
||||||
|
**Local:**
|
||||||
|
```
|
||||||
|
Authorization: http://localhost:8180/realms/homelab/protocol/openid-connect/auth
|
||||||
|
Token: http://localhost:8180/realms/homelab/protocol/openid-connect/token
|
||||||
|
User Info: http://localhost:8180/realms/homelab/protocol/openid-connect/userinfo
|
||||||
|
Logout: http://localhost:8180/realms/homelab/protocol/openid-connect/logout
|
||||||
|
```
|
||||||
|
|
||||||
|
**Production:**
|
||||||
|
```
|
||||||
|
Authorization: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/auth
|
||||||
|
Token: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/token
|
||||||
|
User Info: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/userinfo
|
||||||
|
Logout: https://auth.yourdomain.com/realms/homelab/protocol/openid-connect/logout
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start Keycloak
|
||||||
|
cd ~/docker/keycloak
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Stop Keycloak
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker logs keycloak -f
|
||||||
|
|
||||||
|
# Access shell
|
||||||
|
docker exec -it keycloak bash
|
||||||
|
|
||||||
|
# Login to admin CLI
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \
|
||||||
|
--server http://localhost:8080 \
|
||||||
|
--realm master \
|
||||||
|
--user admin \
|
||||||
|
--password YOUR_PASSWORD
|
||||||
|
|
||||||
|
# Export realm configuration (backup)
|
||||||
|
docker exec keycloak /opt/keycloak/bin/kc.sh export \
|
||||||
|
--dir /opt/keycloak/data/export \
|
||||||
|
--realm homelab
|
||||||
|
|
||||||
|
# Copy export to host
|
||||||
|
docker cp keycloak:/opt/keycloak/data/export ./backup/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Keycloak provides:**
|
||||||
|
- ✅ Single Sign-On for all your services
|
||||||
|
- ✅ Centralized user management
|
||||||
|
- ✅ OAuth2/OIDC authentication
|
||||||
|
- ✅ Works with local and external services
|
||||||
|
- ✅ Professional-grade security
|
||||||
|
|
||||||
|
**Automated setup does:**
|
||||||
|
- ✅ Creates realm
|
||||||
|
- ✅ Creates OAuth clients
|
||||||
|
- ✅ Creates initial user
|
||||||
|
- ✅ Saves all credentials
|
||||||
|
- ✅ Ready to use immediately
|
||||||
|
|
||||||
|
**Manual setup allows:**
|
||||||
|
- ✅ Full control over configuration
|
||||||
|
- ✅ Multiple realms (family, work, etc.)
|
||||||
|
- ✅ Custom client settings
|
||||||
|
- ✅ Advanced features (LDAP, MFA, etc.)
|
||||||
|
|
||||||
|
**For external services:**
|
||||||
|
- ✅ Keycloak must be publicly accessible
|
||||||
|
- ✅ Use Caddy with HTTPS
|
||||||
|
- ✅ Configure proper redirect URIs
|
||||||
|
- ✅ Test OAuth flow before production
|
||||||
|
|
||||||
|
For questions or issues, check the Keycloak documentation: https://www.keycloak.org/documentation
|
||||||
+131
-11
@@ -3162,8 +3162,63 @@ KC_COMPOSE
|
|||||||
# Get realm name
|
# Get realm name
|
||||||
prompt_text " Realm name (e.g., homelab, services):" "homelab" KC_REALM
|
prompt_text " Realm name (e.g., homelab, services):" "homelab" KC_REALM
|
||||||
|
|
||||||
# Get domain for redirect URIs
|
# Get domain configuration for redirect URIs
|
||||||
prompt_text " Your domain (for OAuth callbacks, e.g., example.com):" "localhost" KC_DOMAIN
|
echo ""
|
||||||
|
echo " ──────────────────────────────────────────────────────────────"
|
||||||
|
echo " DOMAIN CONFIGURATION"
|
||||||
|
echo " ──────────────────────────────────────────────────────────────"
|
||||||
|
echo ""
|
||||||
|
echo " Keycloak needs to know where your services are hosted."
|
||||||
|
echo ""
|
||||||
|
echo " Options:"
|
||||||
|
echo " 1. Local only (http://localhost:PORT)"
|
||||||
|
echo " 2. Public domain (https://yourdomain.com)"
|
||||||
|
echo " 3. Both local and public"
|
||||||
|
echo ""
|
||||||
|
prompt_text " Enter your setup (1/2/3):" "1" KC_SETUP_TYPE
|
||||||
|
|
||||||
|
KC_DOMAIN="localhost"
|
||||||
|
KC_PUBLIC_DOMAIN=""
|
||||||
|
KC_EXTERNAL_SERVICE=""
|
||||||
|
|
||||||
|
if [ "$KC_SETUP_TYPE" = "2" ] || [ "$KC_SETUP_TYPE" = "3" ]; then
|
||||||
|
echo ""
|
||||||
|
prompt_text " Your public domain (e.g., example.com):" "" KC_PUBLIC_DOMAIN
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " ⚠ IMPORTANT: For Keycloak to work with external services,"
|
||||||
|
echo " it MUST be accessible at https://auth.$KC_PUBLIC_DOMAIN"
|
||||||
|
echo ""
|
||||||
|
echo " This requires:"
|
||||||
|
echo " ✓ DNS A record: auth.$KC_PUBLIC_DOMAIN → Your Server IP"
|
||||||
|
echo " ✓ Caddy reverse proxy configured"
|
||||||
|
echo " ✓ Ports 80/443 open in firewall"
|
||||||
|
echo ""
|
||||||
|
prompt_yn " Is Keycloak accessible at https://auth.$KC_PUBLIC_DOMAIN? (y/n):" "n" KC_DOMAIN_READY
|
||||||
|
|
||||||
|
if [ "$KC_DOMAIN_READY" != "y" ] && [ "$KC_DOMAIN_READY" != "Y" ]; then
|
||||||
|
echo ""
|
||||||
|
echo " ⚠ WARNING: Keycloak won't work with external services until"
|
||||||
|
echo " you configure Caddy and DNS. See KEYCLOAK-SETUP-GUIDE.md"
|
||||||
|
echo ""
|
||||||
|
echo " You can still proceed and configure Caddy later."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ask about external services (like Pikapod)
|
||||||
|
echo ""
|
||||||
|
prompt_yn " Are you using external hosted services (e.g., Pikapod)? (y/n):" "n" KC_HAS_EXTERNAL
|
||||||
|
|
||||||
|
if [ "$KC_HAS_EXTERNAL" = "y" ] || [ "$KC_HAS_EXTERNAL" = "Y" ]; then
|
||||||
|
echo ""
|
||||||
|
echo " Enter the URL of your external service (e.g., https://actualbudget-abc.pikapod.net)"
|
||||||
|
prompt_text " External service URL:" "" KC_EXTERNAL_SERVICE
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$KC_SETUP_TYPE" = "1" ] || [ "$KC_SETUP_TYPE" = "3" ]; then
|
||||||
|
KC_DOMAIN="localhost"
|
||||||
|
fi
|
||||||
|
|
||||||
# Wait for Keycloak to be fully ready (can take 30-60 seconds)
|
# Wait for Keycloak to be fully ready (can take 30-60 seconds)
|
||||||
echo ""
|
echo ""
|
||||||
@@ -3214,6 +3269,33 @@ KC_COMPOSE
|
|||||||
echo " Creating OAuth2 client for ActualBudget..."
|
echo " Creating OAuth2 client for ActualBudget..."
|
||||||
AB_CLIENT_SECRET=$(openssl rand -hex 32)
|
AB_CLIENT_SECRET=$(openssl rand -hex 32)
|
||||||
|
|
||||||
|
# Build redirect URIs based on configuration
|
||||||
|
AB_REDIRECT_URIS='["http://localhost:5006/*","http://localhost:5006/callback"'
|
||||||
|
|
||||||
|
if [ -n "$KC_PUBLIC_DOMAIN" ]; then
|
||||||
|
AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://budget.'$KC_PUBLIC_DOMAIN'/*","https://budget.'$KC_PUBLIC_DOMAIN'/callback"'
|
||||||
|
AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://'$KC_PUBLIC_DOMAIN':5006/*","https://'$KC_PUBLIC_DOMAIN':5006/callback"'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KC_EXTERNAL_SERVICE" ]; then
|
||||||
|
AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"'$KC_EXTERNAL_SERVICE'/*","'$KC_EXTERNAL_SERVICE'/callback"'
|
||||||
|
fi
|
||||||
|
|
||||||
|
AB_REDIRECT_URIS="$AB_REDIRECT_URIS"']'
|
||||||
|
|
||||||
|
# Build web origins
|
||||||
|
AB_WEB_ORIGINS='["http://localhost:5006"'
|
||||||
|
|
||||||
|
if [ -n "$KC_PUBLIC_DOMAIN" ]; then
|
||||||
|
AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"https://budget.'$KC_PUBLIC_DOMAIN'","https://'$KC_PUBLIC_DOMAIN':5006"'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KC_EXTERNAL_SERVICE" ]; then
|
||||||
|
AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"'$KC_EXTERNAL_SERVICE'"'
|
||||||
|
fi
|
||||||
|
|
||||||
|
AB_WEB_ORIGINS="$AB_WEB_ORIGINS"']'
|
||||||
|
|
||||||
docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \
|
docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \
|
||||||
-s clientId=actualbudget \
|
-s clientId=actualbudget \
|
||||||
-s name="ActualBudget" \
|
-s name="ActualBudget" \
|
||||||
@@ -3225,8 +3307,8 @@ KC_COMPOSE
|
|||||||
-s standardFlowEnabled=true \
|
-s standardFlowEnabled=true \
|
||||||
-s directAccessGrantsEnabled=true \
|
-s directAccessGrantsEnabled=true \
|
||||||
-s serviceAccountsEnabled=false \
|
-s serviceAccountsEnabled=false \
|
||||||
-s 'redirectUris=["http://localhost:5006/*","http://'$KC_DOMAIN':5006/*","https://'$KC_DOMAIN'/*","https://budget.'$KC_DOMAIN'/*"]' \
|
-s "redirectUris=$AB_REDIRECT_URIS" \
|
||||||
-s 'webOrigins=["http://localhost:5006","http://'$KC_DOMAIN':5006","https://'$KC_DOMAIN'","https://budget.'$KC_DOMAIN'"]' \
|
-s "webOrigins=$AB_WEB_ORIGINS" \
|
||||||
-s protocol=openid-connect > /dev/null 2>&1
|
-s protocol=openid-connect > /dev/null 2>&1
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
@@ -3235,7 +3317,12 @@ KC_COMPOSE
|
|||||||
echo " Client Secret: $AB_CLIENT_SECRET"
|
echo " Client Secret: $AB_CLIENT_SECRET"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Save to file
|
# Save to file with appropriate URLs
|
||||||
|
KC_AUTH_URL="http://localhost:8180"
|
||||||
|
if [ -n "$KC_PUBLIC_DOMAIN" ]; then
|
||||||
|
KC_AUTH_URL="https://auth.$KC_PUBLIC_DOMAIN"
|
||||||
|
fi
|
||||||
|
|
||||||
cat > "$KC_DIR/actualbudget-oauth.txt" << EOF
|
cat > "$KC_DIR/actualbudget-oauth.txt" << EOF
|
||||||
ActualBudget OAuth2 Configuration
|
ActualBudget OAuth2 Configuration
|
||||||
==================================
|
==================================
|
||||||
@@ -3243,18 +3330,51 @@ ActualBudget OAuth2 Configuration
|
|||||||
Client ID: actualbudget
|
Client ID: actualbudget
|
||||||
Client Secret: $AB_CLIENT_SECRET
|
Client Secret: $AB_CLIENT_SECRET
|
||||||
|
|
||||||
|
LOCAL DEVELOPMENT:
|
||||||
Authorization URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth
|
Authorization URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth
|
||||||
Token URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token
|
Token URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token
|
||||||
User Info URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo
|
User Info URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo
|
||||||
|
EOF
|
||||||
|
|
||||||
For production (with Caddy):
|
if [ -n "$KC_PUBLIC_DOMAIN" ]; then
|
||||||
Authorization URL: https://auth.$KC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/auth
|
cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
|
||||||
Token URL: https://auth.$KC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/token
|
|
||||||
User Info URL: https://auth.$KC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/userinfo
|
PRODUCTION (with Caddy at https://auth.$KC_PUBLIC_DOMAIN):
|
||||||
|
Authorization URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/auth
|
||||||
|
Token URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/token
|
||||||
|
User Info URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/userinfo
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KC_EXTERNAL_SERVICE" ]; then
|
||||||
|
cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
|
||||||
|
|
||||||
|
EXTERNAL SERVICE ($KC_EXTERNAL_SERVICE):
|
||||||
|
- Use PRODUCTION URLs above
|
||||||
|
- Keycloak MUST be accessible at: https://auth.$KC_PUBLIC_DOMAIN
|
||||||
|
- Redirect URI configured: $KC_EXTERNAL_SERVICE/*
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
|
||||||
|
|
||||||
Redirect URIs configured:
|
Redirect URIs configured:
|
||||||
- http://localhost:5006/*
|
- http://localhost:5006/* (local)
|
||||||
- https://budget.$KC_DOMAIN/*
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$KC_PUBLIC_DOMAIN" ]; then
|
||||||
|
cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
|
||||||
|
- https://budget.$KC_PUBLIC_DOMAIN/* (self-hosted)
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KC_EXTERNAL_SERVICE" ]; then
|
||||||
|
cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
|
||||||
|
- $KC_EXTERNAL_SERVICE/* (external)
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
|
||||||
|
|
||||||
To configure ActualBudget:
|
To configure ActualBudget:
|
||||||
1. Go to ActualBudget settings
|
1. Go to ActualBudget settings
|
||||||
|
|||||||
Reference in New Issue
Block a user