HOMG

homg SSO — integration guide for apps & AI agents

This document explains how to register a new application against homg SSO and authenticate users with OpenID Connect.

SSO is invite-only. Users (humans and agents) are created by an admin. There is no public registration.

---

Identity model

User types

| 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 |

User fields (minimum)

Apps, roles, clients

---

Auth options

Direct browser login

Humans sign in at /login:

Email-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).

1) End-user OIDC (recommended for web apps)

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:

- 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.

2) API keys (humans and agents)

For admin/automation calls to SSO itself:


Authorization: Bearer sso_key_...

Useful endpoints:

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).

---

Step-by-step: create a new app (for AI agents)

You need an API key for a user that has the admin role on the sso app.

1. Create the 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.

2. Create an OAuth client

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).

3. Assign users to the app


# 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).

3b. Create an API key for an agent (or human)


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.

4. Wire the OIDC login flow

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.

---

Claims

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": []

}

API key identity (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).

---

Errors

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"}

---

Direct SSO login vs app login

- Admin → Admin CRUD

- Non-admin → personal portal (read-only identity + app links)

---

Checklist for a new integrating app

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