This document explains how to register a new application against homg SSO and authenticate users with OpenID Connect.
https://sso.homg.czhttps://sso.homg.cz/.well-known/openid-configurationhttps://sso.homg.cz/docshttps://sso.homg.cz/docs/integration.mdSSO is invite-only. Users (humans and agents) are created by an admin. There is no public registration.
---
| Type | Browser login | API access |
|---|---|---|
| human | Password (default) or optional email magic link | Optional API keys (Authorization: Bearer) |
| agent | No password / no UI login | API keys only |
id, email (optional for agents), usernametype (human \| agent)avatar_url, emoji, notestatus (active \| disabled)friday, sso). Has slug, name, home_url, status.admin, member), assigned to a user per app.client_id, optional client_secret, redirect URIs).---
Humans sign in at /login:
SSO_RESEND_API_KEY and SSO_MAIL_FROM on the SSO serverEmail-link tokens expire in 15 minutes and are single-use. The login form always shows a generic “check your email” message after a send request (no account enumeration).
Use Authorization Code + PKCE.
Endpoints:
| Endpoint | URL |
|---|---|
| Authorize | https://sso.homg.cz/oauth/authorize |
| Token | https://sso.homg.cz/oauth/token |
| UserInfo | https://sso.homg.cz/oauth/userinfo |
| JWKS | https://sso.homg.cz/oauth/jwks |
| Logout | https://sso.homg.cz/logout |
Supported:
response_type=codegrant_types: authorization_code, refresh_tokenS256 (required for public clients)/logout: - post_logout_redirect_uri — where to send the browser after ending the SSO session
- client_id — OAuth client (required with redirect, unless id_token_hint is sent)
- id_token_hint — optional; aud used as client_id when omitted
- state — optional; appended to the redirect URI
- Redirect is allowed only if the URI matches a registered redirect_uri, the app home_url, or the same origin as either
Example:
https://sso.homg.cz/logout
?client_id=CLIENT_ID
&post_logout_redirect_uri=https://my-app.example.com/
&state=RANDOM
Without post_logout_redirect_uri, logout still ends the SSO session and redirects to /login.
For admin/automation calls to SSO itself:
Authorization: Bearer sso_key_...
Useful endpoints:
GET /api/me — current user; optional ?app=slug scopes roles / permissions (default sso)GET /api/apps / POST /api/appsPOST /api/clientsGET /api/users / POST /api/usersGET /api/roles — list role slugs + their permissionsGET|POST|PUT|DELETE /api/users/{id}/roles — read / add / replace / remove app-role assignmentsGET|POST /api/users/{id}/api-keys — list / create API keys (plain key returned once on create)POST /api/api-keys/{id}/revoke / DELETE /api/api-keys/{id}GET /api/me only needs a valid API key. Other /api/* admin routes require the caller to have the admin role on the sso app (not fine-grained permissions).
---
You need an API key for a user that has the admin role on the sso app.
curl -sS -X POST "https://sso.homg.cz/api/apps" \
-H "Authorization: Bearer $SSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "my-app",
"name": "My App",
"home_url": "https://my-app.example.com",
"status": "active"
}'
Save the returned id.
Public SPA / mobile (PKCE, no secret):
curl -sS -X POST "https://sso.homg.cz/api/clients" \
-H "Authorization: Bearer $SSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"app_id": 2,
"name": "My App Web",
"redirect_uris": ["https://my-app.example.com/oauth/callback"],
"is_confidential": false
}'
Confidential server app:
curl -sS -X POST "https://sso.homg.cz/api/clients" \
-H "Authorization: Bearer $SSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"app_id": 2,
"name": "My App Backend",
"redirect_uris": ["https://my-app.example.com/oauth/callback"],
"is_confidential": true
}'
Copy client_id and client_secret (secret shown once).
# Add one role (does not clear other apps/roles)
curl -sS -X POST "https://sso.homg.cz/api/users/2/roles" \
-H "Authorization: Bearer $SSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"app":"my-app","role":"member"}'
# Or replace the user's full role set
curl -sS -X PUT "https://sso.homg.cz/api/users/2/roles" \
-H "Authorization: Bearer $SSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"roles":[{"app":"my-app","role":"member"},{"app":"sso","role":"admin"}]}'
You can also assign roles in Admin UI: Users → edit → App roles.
Users with an app role see that app on their SSO portal (home_url link).
curl -sS -X POST "https://sso.homg.cz/api/users/3/api-keys" \
-H "Authorization: Bearer $SSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"friday-mcp"}'
Response includes api_key (sso_key_...) once. Store it for the agent / client app.
Or create users via API (POST /api/users) first, then assign roles and keys as above.
1. Redirect the browser to authorize:
https://sso.homg.cz/oauth/authorize
?client_id=CLIENT_ID
&redirect_uri=https://my-app.example.com/oauth/callback
&response_type=code
&scope=openid%20profile%20email
&state=RANDOM
&nonce=RANDOM
&code_challenge=BASE64URL_SHA256(verifier)
&code_challenge_method=S256
2. User signs in on SSO (and consents).
3. SSO redirects back with ?code=...&state=....
4. Exchange the code:
curl -sS -X POST "https://sso.homg.cz/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=CODE" \
-d "redirect_uri=https://my-app.example.com/oauth/callback" \
-d "client_id=CLIENT_ID" \
-d "code_verifier=VERIFIER"
# confidential clients also send client_secret
5. Validate id_token (RS256 via JWKS) and/or call UserInfo with access_token.
---
ID token / UserInfo include:
| Claim | Meaning |
|---|---|
| sub | User id (string) |
| email | Email (may be null for agents) |
| preferred_username | Username |
| picture | Avatar URL |
| emoji | Emoji |
| type | human or agent |
| status | active or disabled |
| locale | Preferred language (cs / en) |
| app | App slug of the OAuth client (e.g. friday) |
| roles | Role slugs assigned to the user for that app (e.g. ["member"]) |
| permissions | Permission slugs granted via those roles for that app |
app, roles, and permissions are scoped to the app that owns the OAuth client used in the login. A user with no role on that app still receives tokens, but roles / permissions are empty arrays — the client app should treat that as unauthorized if membership is required.
Example UserInfo / ID token profile claims:
{
"sub": "2",
"email": "[email protected]",
"preferred_username": "nicol",
"picture": null,
"emoji": "🌋",
"type": "human",
"status": "active",
"locale": "cs",
"app": "friday",
"roles": ["member"],
"permissions": []
}
GET /api/me)
GET /api/me?app=friday
Authorization: Bearer sso_key_...
Returns the same identity fields plus app, roles, permissions for the requested app (default app=sso), and apps (apps the user can access).
---
OAuth token/authorize errors use standard OAuth2 shapes, e.g.:
{"error":"invalid_grant"}
{"error":"invalid_client"}
{"error":"unsupported_grant_type"}
JSON admin API errors:
{"error":"unauthorized"}
{"error":"forbidden","permission":"apps.create"}
{"error":"invalid_api_key"}
---
https://sso.homg.cz directly:- Admin → Admin CRUD
- Non-admin → personal portal (read-only identity + app links)
/oauth/authorize continues the OIDC flow after login (does not dump the user on the portal mid-flow).---
1. Create app with home_url
2. Create oauth_client with exact redirect URIs
3. Assign users app roles
4. Implement Authorization Code + PKCE
5. Verify ID token signature against /oauth/jwks
6. Map sub as the stable user id in your app; use roles / permissions (scoped to your app) for authorization