oauthsonas configuration
YAML configuration is decoded strictly: unknown fields fail startup rather than being ignored. Validate without starting the server:
go run ./cmd/oauthsonas --config config.yaml --check-config
The repository ships config.example.yaml
with a public dashboard client and platform, staff, and customer personas.
Top-level fields
| Field | Purpose |
|---|---|
issuer | Exact OIDC issuer URL (default example: http://127.0.0.1:8181) |
api_audience | Audience stamped on access tokens |
token_ttl | Access (and related) token lifetime |
authorization_code_ttl | Authorization code lifetime |
refresh_token_ttl | Refresh token lifetime |
claims | Optional claim name overrides (see below) |
clients | Registered OAuth clients |
personas | Selectable identities on the authorize page |
allowed_roles | Optional vocabulary; when set, persona roles must be members |
Clients
clients:
- id: dashboard
name: Optimi Dashboard
public: true
redirect_uris:
- http://127.0.0.1:5173/auth/callback
post_logout_redirect_uris:
- http://127.0.0.1:5173/
allowed_origins:
- http://127.0.0.1:5173
Rules:
redirect_urisandpost_logout_redirect_urisare exact-match values. Fixed query parameters are allowed; fragments are not.allowed_originspermits browser CORS only for the listed origins.- Token and userinfo responses apply client-specific CORS; discovery and JWKS
apply CORS only to the union of registered origins — never
*. - Public clients (as in the example) require S256 PKCE.
Personas
Personas are the identities a developer picks on the authorize page. Each needs
a unique id and at least one role:
personas:
- id: acme-admin
subject: oauthsonas|acme-admin
email: admin@acme.dev.optimi.test
name: Acme Administrator
organization_id: org_acme
roles: [customer-admin]
- id: platform-admin
subject: oauthsonas|platform-admin
email: platform-admin@dev.optimi.test
name: Platform Admin
roles: [platform-admin]
| Field | Required | Notes |
|---|---|---|
id | yes | Stable selector / form value |
subject | yes | JWT sub |
email | yes | Emitted when email scope is granted |
name | yes | Emitted when profile scope is granted |
roles | yes | At least one role name |
organization_id | no | Becomes org_id (or configured claim) when set |
memberships | no | Optional multi-org membership list |
Roles are carried as names only. oauthsonas never expands roles into application permissions — that stays in the relying party.
Add a persona by appending a unique entry under personas. Optionally set a
top-level allowed_roles list if a project wants configuration-time vocabulary
validation; otherwise any non-empty role name is valid.
Application claims
Persona attributes are emitted as JWT and userinfo claims. Defaults:
| Persona field | Default claim name | When emitted |
|---|---|---|
roles | roles | Always (at least one role required) |
organization_id | org_id | Only when set on the persona |
memberships | memberships | Only when set on the persona |
Example access-token payload with defaults:
{
"roles": ["customer-admin"],
"org_id": "org_acme"
}
org_id is omitted for staff personas without organization_id.
memberships is omitted unless the persona lists them.
Overriding claim names
claims:
roles: roles
memberships: memberships
org_id: org_id
Use namespaced URLs when a consumer requires Auth0-style custom claims:
claims:
roles: https://example.com/roles
memberships: https://example.com/memberships
org_id: org_id
Rules:
- Claim names must be non-empty and must not contain whitespace.
- The three configured names must be distinct from each other.
- The same names appear in access tokens, ID tokens, userinfo, and discovery
claims_supported. - Persona YAML fields (
roles,organization_id,memberships) stay the same; only the emitted JWT claim keys change. - Custom claim names must not collide with standard JWT/OIDC claim names
(for example
sub,email,scope,client_id).
Scope-gated profile claims
emailandemail_verifiedrequire theemailscope.namerequiresprofile.- OAuth scopes remain protocol scopes and do not encode application permissions.
Keys and token shape
The server generates a 2048-bit RSA key when it starts and publishes the
public component at /.well-known/jwks.json. Access and ID tokens are signed
with RS256 and include a process-lifetime kid. Neither unsigned nor
symmetric JWTs are produced.
JWT aud values are serialized as JSON arrays by Fosite (valid JWT/OIDC):
- Access tokens target the configured
api_audience. - ID tokens target the client ID.
Example: full minimal config
issuer: http://127.0.0.1:8181
api_audience: https://api.example.test
token_ttl: 15m
authorization_code_ttl: 5m
refresh_token_ttl: 8h
clients:
- id: dashboard
name: Local Dashboard
public: true
redirect_uris:
- http://127.0.0.1:5173/auth/callback
post_logout_redirect_uris:
- http://127.0.0.1:5173/
allowed_origins:
- http://127.0.0.1:5173
personas:
- id: developer
subject: oauthsonas|developer
email: dev@example.test
name: Local Developer
roles: [customer-admin]
organization_id: org_local
Next
- Back to oauthsonas for protocol, containers, and automation.
- Open source overview for other projects.
