SSO.so Docs
SSO.so is a self-hosted Single Sign-On service built on Keycloak 26, running on Ubuntu 24.04 behind a Caddy reverse proxy. It provides OpenID Connect, OAuth 2.0, and SAML 2.0 authentication for all your products from a single identity server at sso.sso.so.
The admin console is available at sso.sso.so/admin. Keep that URL out of your public docs — admin access should be restricted by IP or VPN where possible.
| Component | Details |
|---|---|
| Identity server | Keycloak 26 on Ubuntu 24.04 |
| Public URL | https://sso.sso.so |
| Reverse proxy | Caddy (auto TLS via Let's Encrypt) |
| Database | PostgreSQL 16 (localhost only) |
| Protocols | OIDC, OAuth 2.0, SAML 2.0 |
Quick start
Get your first app authenticating against SSO.so in three steps.
Fetch the discovery document
Every OIDC library needs this URL — it auto-configures all endpoints.
Register your client in the admin console
Go to your realm → Clients → Create. Copy the client ID and secret.
Configure your app's OIDC library
Point it at the discovery document and add your client credentials.
https://sso.sso.so/realms/{your-realm}/.well-known/openid-configuration
import { Issuer } from 'openid-client';
const issuer = await Issuer.discover(
'https://sso.sso.so/realms/myproducts'
);
const client = new issuer.Client({
client_id: 'myapp',
client_secret: 'your-client-secret',
redirect_uris: ['https://myapp.example.com/auth/callback'],
response_types: ['code'],
});
OpenID Connect
SSO.so speaks standard OIDC. Any library that supports OIDC discovery will work — openid-client (Node), authlib (Python), spring-security-oauth2 (Java), league/oauth2-client (PHP), etc.
Endpoints
Replace myproducts with your actual realm name throughout.
| Endpoint | URL |
|---|---|
| Discovery | /realms/myproducts/.well-known/openid-configuration |
| Authorization | /realms/myproducts/protocol/openid-connect/auth |
| Token | /realms/myproducts/protocol/openid-connect/token |
| Userinfo | /realms/myproducts/protocol/openid-connect/userinfo |
| JWKS | /realms/myproducts/protocol/openid-connect/certs |
| Logout | /realms/myproducts/protocol/openid-connect/logout |
Token reference
Keycloak issues standard JWT access tokens. Verify them locally using the JWKS endpoint — never send them to an external service for validation.
{
"iss": "https://sso.sso.so/realms/myproducts",
"sub": "a1b2c3d4-e5f6-...",
"aud": "myapp",
"exp": 1743700000,
"iat": 1743699700,
"email": "user@example.com",
"email_verified": true,
"realm_access": { "roles": ["app-user"] },
"preferred_username": "johndoe"
}
PKCE flow
All public clients (SPAs, mobile apps) must use PKCE. Confidential clients (server-side apps) should use it too — it's enabled by default on all clients registered through SSO.so.
# 1. Generate code verifier (random 43-128 char string)
code_verifier=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)
# 2. Derive code challenge (S256)
code_challenge=$(echo -n "$code_verifier" | sha256sum | cut -d' ' -f1 | xxd -r -p | base64 | tr -d '=' | tr '+/' '-_')
# 3. Authorization URL
https://sso.sso.so/realms/myproducts/protocol/openid-connect/auth
?client_id=myapp
&response_type=code
&redirect_uri=https://myapp.example.com/callback
&scope=openid profile email
&code_challenge=$code_challenge
&code_challenge_method=S256
&state=$(openssl rand -hex 16)
Always validate the state parameter on return to prevent CSRF. Never store the code verifier in localStorage — use sessionStorage or server-side session only.
Registering clients
Each application that uses SSO.so needs its own client registration. Go to Admin → your realm → Clients → Create client.
| Setting | Recommended value |
|---|---|
| Client type | OpenID Connect |
| Client authentication | On (confidential) for server apps; Off (public) for SPAs |
| Standard flow | On |
| Direct access grants | Off (unless you need resource owner password flow) |
| Valid redirect URIs | Exact match — never use * wildcards in production |
| PKCE method | S256 (set in Client → Advanced → PKCE) |
Email / SMTP
Keycloak sends emails for account verification, password reset, and MFA enrollment. Configure your SMTP provider in Realm Settings → Email.
Host: smtp-relay.brevo.com
Port: 587
Encryption: STARTTLS
Auth: On
Username: your@email.com
Password: your-brevo-smtp-key
From: noreply@sso.so
From display: SSO.so Identity
Click Test connection after saving. Keycloak will send a test email to the admin address. If it fails, check that port 587 is open outbound on your VPS — some providers block it by default.
MFA setup
Enable TOTP (Google Authenticator, Authy) at the realm level under Authentication → Required Actions → Configure OTP → Default On. Users will be prompted to enroll on next login.
Brute force protection
Already configured in your realm JSON import. Verify it's active at Realm Settings → Security Defenses → Brute Force Detection. The defaults are: 10 failures → 60 second lockout, incrementing up to 15 minutes maximum.
Session policy
| Setting | Value | Where |
|---|---|---|
| SSO session idle | 30 minutes | Realm Settings → Sessions |
| SSO session max | 10 hours | Realm Settings → Sessions |
| Access token lifespan | 5 minutes | Realm Settings → Tokens |
| Refresh token lifespan | Tied to SSO session max | Inherited |