feat : add sudloh oidc skill

This commit is contained in:
2026-10-05 15:48:46 +07:00 Unverified
parent e2fa1aafb6
commit 261f0d717d
2 changed files with 112 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
---
name: sudloh-oidc
description: Integrate a Sudloh application or API with account.sudloh.com's OIDC sign-in and token validation. Use for guide.sudloh.com, buzz.sudloh.com, or another registered Sudloh client.
---
# Sudloh OIDC integration
Read [references/api.md](references/api.md) before implementing a client or protected API. It defines the issuer, endpoints, security checks, credentials, examples, and sign-out behavior. Treat OIDC discovery as the current source of endpoint URLs at runtime.
For Guide and Buzz, request the exact HTTPS callback URL and the assigned `client_id` and `client_secret` from the account service operator. Each app owns its own session. Do not send a browser's account service cookie to an app API or treat an ID token as an API access token.
@@ -0,0 +1,102 @@
# Sudloh Account OIDC API
## Service and identity
- Production issuer: `https://account.sudloh.com/api/auth`
- OIDC discovery: `https://account.sudloh.com/api/auth/.well-known/openid-configuration`
- OAuth metadata: `https://account.sudloh.com/api/auth/.well-known/oauth-authorization-server`
- Supported scopes for first-party web clients: `openid profile email`
- Stable user key: the validated ID token's `sub`. Store it as the app's external identity key; do not use email as a primary key.
- Public client registration is disabled. Guide and Buzz are pre-registered, confidential web clients with exact HTTPS redirect URIs, `client_secret_basic`, authorization-code grant, and S256 PKCE. Their consent screen is skipped.
The discovery document gives the actual authorization, token, UserInfo, JWKS, revocation, and end-session URLs. At the current version, the authorization endpoint is `/api/auth/oauth2/authorize`, token endpoint is `/api/auth/oauth2/token`, UserInfo endpoint is `/api/auth/oauth2/userinfo`, and keys are at `/api/auth/jwks`, all under `https://account.sudloh.com`. Read discovery rather than hard-coding those paths.
| Endpoint | Method | Caller and purpose |
| --- | --- | --- |
| `/.well-known/openid-configuration` under the issuer | GET | Public discovery document; fetch before configuring endpoints |
| `/oauth2/authorize` under the issuer | GET | Browser redirect; starts authorization-code sign-in |
| `/oauth2/token` under the issuer | POST | Server-to-server code exchange with HTTP Basic client authentication |
| `/jwks` under the issuer | GET | Public signing keys for ID-token and JWT verification |
| `/oauth2/userinfo` under the issuer | GET | Bearer access token; returns permitted user claims |
| `/oauth2/revoke` under the issuer | POST | Revokes a token; authenticate the client as advertised by discovery |
| `/oauth2/end-session` under the issuer | GET/POST | Optional OIDC session-end flow for that client |
These are protocol endpoints. Their exact supported methods, authentication methods, and URLs are advertised in discovery; use that document if it differs from this table. Account settings use Better Auth's own same-origin APIs and are not an integration API for Guide or Buzz.
## Client configuration
For each app, configure these server-side secrets and settings:
| Setting | Value |
| --- | --- |
| `SUDLOH_OIDC_ISSUER` | `https://account.sudloh.com/api/auth` |
| `SUDLOH_OIDC_CLIENT_ID` | Assigned during client registration |
| `SUDLOH_OIDC_CLIENT_SECRET` | Assigned once during client registration; server-side only |
| `SUDLOH_OIDC_REDIRECT_URI` | Exact registered HTTPS callback URL on that app |
| scopes | `openid profile email` |
| client authentication | HTTP Basic at token endpoint (`client_secret_basic`) |
Guide and Buzz callback URLs are intentionally not fixed in the account repository. The owner of each app must give its actual callback URL to the account operator before registration. Never use a wildcard callback URL. Keep the client secret out of browser code and logs.
## Browser sign-in sequence
1. The app creates a random `state`, OIDC `nonce`, and PKCE verifier. Store them in a short-lived, HTTP-only app cookie or server-side transaction store.
2. Redirect the browser to discovery's `authorization_endpoint` with `response_type=code`, assigned `client_id`, exact `redirect_uri`, `scope=openid profile email`, `state`, `nonce`, `code_challenge`, and `code_challenge_method=S256`.
3. Sudloh Account redirects the user to Google or Discord if needed and then returns to the registered app callback with `code`, `state`, and `iss`. The account service's session gives subsequent client apps single sign-on.
4. At the app callback, compare `state` and `iss` with the original values and reject errors or missing parameters. Exchange `code` once at the discovered `token_endpoint`, authenticating with client ID and secret via HTTP Basic and including the original `code_verifier` and `redirect_uri`.
5. Validate the ID token signature with the discovered JWKS and check `iss`, `aud`, `exp`, and `nonce`. Read `sub`, email, name, and picture from validated claims or UserInfo. Create the app's own HTTP-only session.
6. On later requests, authorize using the app session or a properly validated access token. Do not pass the account cookie between services. Request only the scopes this service needs.
The code exchange is a `POST` with `Content-Type: application/x-www-form-urlencoded`, an `Authorization: Basic <base64(client_id:client_secret)>` header, and a body like:
```text
grant_type=authorization_code&code=<returned-code>&redirect_uri=<exact-registered-uri>&code_verifier=<original-verifier>
```
The successful JSON response contains `access_token`, `token_type`, `expires_in`, `id_token`, and granted `scope`. Do not assume a refresh token: these clients request no `offline_access`. Call UserInfo with `Authorization: Bearer <access_token>` only when fresh claims are needed. The ID token contains standard claims including `iss`, `sub`, `aud`, `exp`, `iat`, and the request's `nonce`; profile and email claims depend on the granted scopes.
The provider requires exact redirect URI matching and S256 PKCE. Clients should use a maintained OIDC library for state, nonce, PKCE, token exchange, and ID-token verification.
## Better Auth client app example
If Guide or Buzz uses Better Auth, configure its own Better Auth instance and local database with the Generic OAuth plugin:
```ts
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";
export const auth = betterAuth({
// Configure this app's own database, baseURL, and secret.
plugins: [genericOAuth({
config: [{
providerId: "sudloh",
clientId: process.env.SUDLOH_OIDC_CLIENT_ID!,
clientSecret: process.env.SUDLOH_OIDC_CLIENT_SECRET!,
discoveryUrl: "https://account.sudloh.com/api/auth/.well-known/openid-configuration",
scopes: ["openid", "profile", "email"],
}],
})],
});
```
Call `authClient.signIn.social({ provider: "sudloh" })` in the client app. Confirm that its actual Better Auth callback URL matches the URL registered by the account operator. Its Better Auth instance creates that app's independent session.
## Calling a protected API
An ID token is for the OAuth client; do not use it to authorize API requests. If an API accepts Sudloh access tokens, validate a JWT against discovery's JWKS and require the expected issuer, expiry, audience/resource, and scope. Register that API as an OAuth protected resource before requesting resource-bound tokens. For the initial Guide and Buzz sign-in integration, use app-owned sessions and do not expose an account access token to the browser API layer.
## Profile updates and sign-out
Email change completes only after the user verifies the new address. Existing app sessions can have older profile claims until their next sign-in or profile refresh. Use `sub` to correlate the user when email changes.
Account sign-out or account-session revocation prevents that account session from initiating future OIDC flows. It does not immediately end Guide or Buzz sessions. Each client app must provide its own sign-out. The provider advertises an OIDC end-session endpoint, but there is no cross-app back-channel logout in this version.
## Errors and checks
- `invalid_client`: confirm client ID, secret, and registered token auth method.
- `invalid_request` or redirect rejection: confirm the exact callback URI, requested scopes, state, and PKCE fields.
- `invalid_grant`: code was expired, already used, or exchanged with a different verifier or callback URI. Restart sign-in.
- `access_denied`: user canceled the authorization. Return to the app's sign-in screen.
- Failed signature or issuer/audience/nonce check: discard tokens and fail closed.
Do not log authorization codes, client secrets, tokens, cookie values, or verification links. Use the discovery endpoint and account service `/health` and `/ready` endpoints for diagnostics.