Note
This software is experiemental. If you'd like to try it out, find bugs, security flaws and improvements, please do.
A lightweight, self-hosted identity & SSO / IpD portal
Clinch gives you one place to manage users and lets any web app authenticate against it without maintaining its own user table.
I've completed all planned features:
- Create Admin user on first login
- TOTP ( QR Code ) 2FA, with backup codes ( encrypted at rest )
- Passkey generation and login, with detection of Passkey during login
- Forward Auth configured and working
- OIDC provider with auto discovery, refresh tokens, and token revocation
- Configurable token expiry per application (access, refresh, ID tokens)
- Invite users by email, assign to groups
- Self managed password reset by email
- Use Groups to assign Applications ( Family group can access Kavita, Developers can access Gitea )
- Configurable Group and User custom claims for OIDC token
- Display all Applications available to the user on their Dashboard
- Display all logged in sessions and OIDC logged in sessions
What remains now is ensure test coverage,
Do you host your own web apps? MeTube, Kavita, Audiobookshelf, Gitea? Rather than managing all those separate user accounts, set everyone up on Clinch and let it do the authentication and user management.
Clinch sits in a sweet spot between two excellent open-source identity solutions:
Authelia is a fantastic choice for those who prefer external user management through LDAP and enjoy comprehensive YAML-based configuration. It's lightweight, secure, and works beautifully with reverse proxies.
Authentik is an enterprise-grade powerhouse offering extensive protocol support (OAuth2, SAML, LDAP, RADIUS), advanced policy engines, and distributed "outpost" architecture for complex deployments.
Clinch offers a middle ground with built-in user management, a modern web interface, and focused SSO capabilities (OIDC + ForwardAuth). It's perfect for users who want self-hosted simplicity without external dependencies or enterprise complexity.
- First-run wizard - Initial user automatically becomes admin
- Admin dashboard - Create, disable, and delete users
- Group-based organization - Organize users into groups (admin, family, friends, etc.)
- User statuses - Active, disabled, or pending invitation
- WebAuthn/Passkeys - Modern passwordless authentication using FIDO2 standards
- Password authentication - Secure bcrypt-based password storage
- TOTP 2FA - Optional time-based one-time passwords with QR code setup
- Backup codes - 10 single-use recovery codes per user
- Configurable 2FA enforcement - Admins can require TOTP for specific users
Standard OAuth2/OIDC provider with endpoints:
/.well-known/openid-configuration- Discovery endpoint/authorize- Authorization endpoint with PKCE support/token- Token endpoint (authorization_code and refresh_token grants)/userinfo- User info endpoint/revoke- Token revocation endpoint (RFC 7009)
Features:
- Refresh tokens - Long-lived tokens (30 days default) with automatic rotation and revocation
- Token family tracking - Advanced security detects token replay attacks and revokes compromised token families
- Configurable token expiry - Set access token (5min-24hr), refresh token (1-90 days), and ID token TTL per application
- Token security - BCrypt-hashed tokens, automatic cleanup of expired tokens
- Pairwise subject identifiers - Each user gets a unique, stable
subclaim per application for enhanced privacy
Client apps (Audiobookshelf, Kavita, Grafana, etc.) redirect to Clinch for login and receive ID tokens, access tokens, and refresh tokens.
Works with reverse proxies (Caddy, Traefik, Nginx):
- Proxy sends every request to
/api/verify - 200 OK → Proxy injects headers (
Remote-User,Remote-Groups,Remote-Email) and forwards to app - 401/403 → Proxy redirects to Clinch login; after login, user returns to original URL
Apps that speak OIDC use the OIDC flow; apps that only need "who is it?" headers use ForwardAuth.
Note: ForwardAuth requires applications to run on the same domain as Clinch (e.g., app.yourdomain.com with Clinch at auth.yourdomain.com) for secure session cookie sharing. Take a look at Authentik if you need multi domain support.
Send emails for:
- Invitation links (one-time token, 7-day expiry)
- Password reset links (one-time token, 1-hour expiry)
- 2FA backup codes
- Device tracking - See all active sessions with device names and IPs
- Remember me - Long-lived sessions (30 days) for trusted devices
- Session revocation - Users and admins can revoke individual sessions
Clinch uses groups to control which users can access which applications:
- Create groups - Organize users into logical groups (readers, editors, family, developers, etc.)
- Assign groups to applications - Each app defines which groups are allowed to access it
- Example: Kavita app allows the "readers" group → only users in the "readers" group can sign in
- If no groups are assigned to an app → all active users can access it
- Automatic enforcement - Access checks happen automatically:
- During OIDC authorization flow (before consent)
- During ForwardAuth verification (before proxying requests)
- Users not in allowed groups receive a "You do not have permission" error
- OIDC tokens include group membership - ID tokens contain a
groupsclaim with all user's groups - Custom claims - Add arbitrary key-value pairs to tokens via groups and users
- Group claims apply to all members (e.g.,
{"role": "viewer"}) - User claims override group claims for fine-grained control
- Perfect for app-specific authorization (e.g., admin vs. read-only roles)
- Group claims apply to all members (e.g.,
Custom claims from groups and users are merged into OIDC ID tokens with the following precedence:
- Default OIDC claims - Standard claims (
iss,sub,aud,exp,email, etc.) - Standard Clinch claims -
groupsarray (list of user's group names) - Group custom claims - Merged in order; later groups override earlier ones
- User custom claims - Override all group claims
- Application-specific claims - Highest priority; override all other claims
Example:
- Group "readers" has
{"role": "viewer", "max_items": 10} - Group "premium" has
{"role": "subscriber", "max_items": 100} - User (in both groups) has
{"max_items": 500} - Result:
{"role": "subscriber", "max_items": 500}(user overrides max_items, premium overrides role)
Configure different claims for different applications on a per-user basis:
- Per-app customization - Each application can have unique claims for each user
- Highest precedence - App-specific claims override group and user global claims
- Use case - Different roles in different apps (e.g., admin in Kavita, user in Audiobookshelf)
- Admin UI - Configure via Admin → Users → Edit User → App-Specific Claim Overrides
Example:
- User Alice, global claims:
{"theme": "dark"} - Kavita app-specific:
{"kavita_groups": ["admin"]} - Audiobookshelf app-specific:
{"abs_groups": ["user"]} - Result: Kavita receives
{"theme": "dark", "kavita_groups": ["admin"]}, Audiobookshelf receives{"theme": "dark", "abs_groups": ["user"]}
User
- Email address (unique, normalized to lowercase)
- Password (bcrypt hashed)
- Admin flag
- TOTP secret and backup codes (encrypted)
- TOTP enforcement flag
- Status (active, disabled, pending_invitation)
- Custom claims (JSON) - arbitrary key-value pairs added to OIDC tokens
- Token generation for invitations, password resets, and magic logins
Group
- Name (unique, normalized to lowercase)
- Description
- Custom claims (JSON) - shared claims for all members (merged with user claims)
- Many-to-many with Users and Applications
Session
- User reference
- IP address and user agent
- Device name (parsed from user agent)
- Remember me flag
- Expiry (24 hours or 30 days if remembered)
- Last activity timestamp
Application
- Name and slug (URL-safe identifier)
- Type (oidc or forward_auth)
- Client ID and secret (for OIDC apps)
- Redirect URIs (for OIDC apps)
- Domain pattern (for ForwardAuth apps, supports wildcards like *.example.com)
- Headers config (for ForwardAuth apps, JSON configuration for custom header names)
- Token TTL configuration (access_token_ttl, refresh_token_ttl, id_token_ttl)
- Metadata (flexible JSON storage)
- Active flag
- Many-to-many with Groups (allowlist)
OIDC Tokens
- Authorization codes (10-minute expiry, one-time use, PKCE support)
- Access tokens (opaque, BCrypt-hashed, configurable expiry 5min-24hr, revocable)
- Refresh tokens (opaque, BCrypt-hashed, configurable expiry 1-90 days, single-use with rotation)
- ID tokens (JWT, signed with RS256, configurable expiry 5min-24hr)
- Client redirects user to
/authorizewith client_id, redirect_uri, scope (optional PKCE) - User authenticates with Clinch (username/password + optional TOTP)
- Access control check: Is user in an allowed group for this app?
- If allowed, generate authorization code and redirect to client
- Client exchanges code at
/tokenfor ID token, access token, and refresh token - Client uses access token to fetch fresh user info from
/userinfo - When access token expires, client uses refresh token to get new tokens (no re-authentication)
- User requests protected resource at
https://app.example.com/dashboard - Reverse proxy sends request to Clinch at
/api/verify - Clinch checks for valid session cookie
- If valid session and user allowed:
- Return 200 with
Remote-User,Remote-Groups,Remote-Emailheaders - Proxy forwards request to app with injected headers
- Return 200 with
- If no session or not allowed:
- Return 401/403
- Proxy redirects to Clinch login page
- After login, redirect back to original URL
- Ruby 3.3+
- SQLite 3.8+
- SMTP server (for sending emails)
# Install dependencies
bundle install
# Setup database
bin/rails db:setup
# Run migrations
bin/rails db:migrate
# Start server
bin/dev# Build image
docker build -t clinch .
# Run container
docker run -p 3000:3000 \
-v clinch-storage:/rails/storage \
-e SECRET_KEY_BASE=your-secret-key \
-e SMTP_ADDRESS=smtp.example.com \
-e SMTP_PORT=587 \
-e SMTP_USERNAME=your-username \
-e SMTP_PASSWORD=your-password \
clinchCreate a .env file (see .env.example):
# Rails
SECRET_KEY_BASE=generate-with-bin-rails-secret
RAILS_ENV=production
# Database
# SQLite database stored in storage/ directory (Docker volume mount point)
# SMTP (for sending emails)
SMTP_ADDRESS=smtp.example.com
SMTP_PORT=587
SMTP_DOMAIN=example.com
SMTP_USERNAME=your-username
SMTP_PASSWORD=your-password
SMTP_AUTHENTICATION=plain
SMTP_ENABLE_STARTTLS=true
# Application
CLINCH_HOST=https://auth.example.com
CLINCH_FROM_EMAIL=noreply@example.com
# OIDC (optional - generates temporary key in development)
# Generate with: openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
OIDC_PRIVATE_KEY=<contents-of-private-key.pem>- Visit Clinch at
http://localhost:3000(or your configured domain) - First-run wizard creates initial admin user
- Admin can then:
- Create groups
- Invite users
- Register applications
- Configure access control
- OIDC provider implementation
- ForwardAuth endpoint
- Admin UI for user/group/app management
- First-run wizard
- Audit logging - Track all authentication events
- WebAuthn/Passkeys - Hardware key support
- SAML support - SAML 2.0 identity provider
- Policy engine - Rule-based access control
- Example:
IF user.email =~ "*@gmail.com" AND app.slug == "kavita" THEN DENY - Stored as JSON, evaluated after auth but before consent
- Example:
- LDAP sync - Import users from LDAP/Active Directory
- Rails 8.1 - Modern Rails with authentication generator
- SQLite - Lightweight database (production-ready with Rails 8)
- Tailwind CSS - Utility-first styling
- Hotwire - Turbo and Stimulus for reactive UI
- ROTP - TOTP implementation for 2FA
- bcrypt - Password hashing
MIT