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

FieldPurpose
issuerExact OIDC issuer URL (default example: http://127.0.0.1:8181)
api_audienceAudience stamped on access tokens
token_ttlAccess (and related) token lifetime
authorization_code_ttlAuthorization code lifetime
refresh_token_ttlRefresh token lifetime
claimsOptional claim name overrides (see below)
clientsRegistered OAuth clients
personasSelectable identities on the authorize page
allowed_rolesOptional 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_uris and post_logout_redirect_uris are exact-match values. Fixed query parameters are allowed; fragments are not.
  • allowed_origins permits 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]
FieldRequiredNotes
idyesStable selector / form value
subjectyesJWT sub
emailyesEmitted when email scope is granted
nameyesEmitted when profile scope is granted
rolesyesAt least one role name
organization_idnoBecomes org_id (or configured claim) when set
membershipsnoOptional 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 fieldDefault claim nameWhen emitted
rolesrolesAlways (at least one role required)
organization_idorg_idOnly when set on the persona
membershipsmembershipsOnly 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

  • email and email_verified require the email scope.
  • name requires profile.
  • 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