Ghost Keycloak Bridge - Native SSO/OIDC Integration for Self-Hosted Ghost
Hey everyone! ![]()
I know SSO/OIDC integration has been a highly requested feature for years on this forum. After struggling to find a working solution for my self-hosted Ghost instance, I decided to build my own bridge. It’s now stable and production-ready (but need somes feedbacks), so I’m sharing it with the community.
The Problem
If you’re self-hosting Ghost and want to integrate it with an identity provider (Keycloak, Authentik, or any OIDC provider), you’ve probably noticed that:
-
Native SSO is only available on Ghost(Pro) hosted plans
-
Existing third-party solutions are either abandoned or half-working
-
There’s virtually no documentation on how Ghost handles authentication internally
This bridge solves that problem by creating native Ghost sessions through what I call “Cookie Forgery” : essentially reverse-engineering how Ghost authenticates users and replicating that process after successful OIDC authentication.
Features
Member SSO (Blog Subscribers)
-
Auto-provisioning: New users are automatically created in Ghost on first login
-
Magic link integration: Uses Ghost’s native token system for seamless session establishment
-
Single Logout (SLO): Logging out clears both Ghost and Keycloak sessions
-
Signup support: Can redirect to Keycloak registration page
Staff SSO (Admin Panel)
-
Direct session injection: Creates valid admin sessions in Ghost’s database
-
User validation: Only allows login for existing Ghost staff members
-
Native cookie signing: Uses Ghost’s internal
admin_session_secret -
Optional UI injection: Adds a “Login with OIDC” button directly on
/ghost/login page
Production-Ready (v1.1.0)
-
Health check endpoints:
/health,/ready,/startupfor Kubernetes/Podman -
Structured logging: Winston-based with JSON output for log aggregators
-
Comprehensive test suite: 102 tests, 84% code coverage
-
Lightweight Docker image: ~50MB (Node 22 Alpine)
How It Works
The bridge acts as a middleware between your reverse proxy and Ghost:
User → Nginx → Ghost Keycloak Bridge → Keycloak
↓
Ghost (API + Database)
Member Authentication Flow:
-
User clicks “Login” → Redirected to Keycloak
-
User authenticates → Keycloak redirects to bridge callback
-
Bridge queries Ghost Admin API to find/create member
-
Bridge generates a magic token and inserts it into Ghost’s
tokenstable -
User is redirected to
/members/?token=xxx→ Ghost establishes native session
Staff Authentication Flow:
-
Staff clicks “Login with OIDC” → Redirected to Keycloak
-
Staff authenticates → Keycloak redirects to bridge callback
-
Bridge verifies user exists in Ghost’s
userstable -
Bridge creates a session record in Ghost’s
sessionstable -
Bridge signs the session cookie using Ghost’s secret
-
Staff is redirected to
/ghost/with a valid admin session
Requirements
-
Ghost: v6.0+ (self-hosted with MySQL/MariaDB)
-
Keycloak (or any OIDC-compatible provider with some adjustments)
-
Shared domain: Ghost and the bridge must be on the same root domain (for cookie sharing)
-
Reverse proxy: Nginx, Traefik, Caddy, etc.
Quick Start
Docker Compose:
services:
ghost-bridge:
image: ghcr.io/astocanthus/ghost-keycloak-bridge:1.1.0
environment:
- BLOG_PUBLIC_URL=https://blog.example.com
- GHOST_INTERNAL_URL=http://ghost:2368
- DB_HOST=ghost-db
- DB_USER=ghost
- DB_PASSWORD=${GHOST_DB_PASSWORD}
- MEMBER_KEYCLOAK_ISSUER=https://auth.example.com/realms/members
- MEMBER_CLIENT_ID=ghost-members
- MEMBER_CLIENT_SECRET=${MEMBER_CLIENT_SECRET}
- MEMBER_CALLBACK_URL=https://blog.example.com/auth/member/callback
- STAFF_KEYCLOAK_ISSUER=https://auth.example.com/realms/staff
- STAFF_CLIENT_ID=ghost-admin
- STAFF_CLIENT_SECRET=${STAFF_CLIENT_SECRET}
- STAFF_CALLBACK_URL=https://blog.example.com/auth/admin/callback
- GHOST_ADMIN_API_KEY=${GHOST_ADMIN_API_KEY}
networks:
- ghost-network
Nginx Configuration:
# Member authentication
location /auth/member/ {
proxy_pass http://ghost-bridge:3000/auth/member/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Staff authentication
location /auth/admin/ {
proxy_pass http://ghost-bridge:3000/auth/admin/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
API Endpoints
| Endpoint | Description |
|---|---|
/health |
Liveness probe (always 200) |
/ready |
Readiness probe (checks DB connection) |
/auth/member/login |
Initiates member OIDC flow |
/auth/member/login?action=signup |
Redirects to Keycloak registration |
/auth/member/logout |
Clears cookies + Keycloak SLO |
/auth/member/callback |
OIDC callback handler |
/auth/member/debug |
Diagnostic endpoint (API connectivity test) |
/auth/admin/login |
Initiates staff OIDC flow |
/auth/admin/callback |
OIDC callback, creates admin session |
Optional: Admin Login Button Injection
The bridge includes a script that patches Ghost’s admin UI to add a “Login with OIDC (Staff)” button:
services:
ghost:
image: ghost:6-alpine
volumes:
- ./ghost-sso/6.x/custom-start.sh:/var/lib/ghost/custom-start.js:ro
command: ["node", "/var/lib/ghost/custom-start.js"]
```
Result:
```
┌─────────────────────────────────────┐
│ Ghost Admin Login │
├─────────────────────────────────────┤
│ Email: [________________] │
│ Password: [________________] │
│ │
│ [ Sign in ] │
│ [ Login with OIDC (Staff) ] ← NEW │
└─────────────────────────────────────┘
Limitations & Caveats
-
Keycloak-focused: Built and tested with Keycloak. Other OIDC providers may require adjustments.
-
Database access required: The bridge needs direct access to Ghost’s MySQL database (for session/token management).
-
No role sync: Staff roles are not synchronized from Keycloak. Users must exist in Ghost first (sécurity first).
-
Same domain required: Cookie-based authentication requires Ghost and the bridge to share the same root domain.
Roadmap
-
v1.2.0: Prometheus metrics (
/metrics), rate limiting, ISO 27001 compliance improvements -
Future: Generic OIDC support (beyond Keycloak), role mapping from OIDC claims
Links
-
GitHub: https://github.com/Astocanthus/ghost-keycloak-bridge
-
Documentation: Full README with architecture diagrams, troubleshooting guide
-
Docker Image:
ghcr.io/astocanthus/ghost-keycloak-bridge:1.1.0(soon)
Feedback Welcome!
This is my first major open-source contribution since years, and I’d love to hear your feedback. If you’re using it, having issues, or want to contribute, feel free to open an issue or PR on GitHub.
Happy self-hosting! ![]()