Reference

Configuration, clients, endpoints, and behaviors — the lookup reference for everything tinyidp does.

Sections

Terminology & Glossary
📖 Documentation
Navigation
11 sectionsv0.1
📄 Reference — glaze help reference
reference

Reference

Configuration, clients, endpoints, and behaviors — the lookup reference for everything tinyidp does.

Topicoidcconfigreferenceclientsserveprint-configissueraddrclient-idclient-secret+3

This is the lookup reference for tinyidp: how it is configured, which clients it ships, which endpoints it exposes, and the behaviors those endpoints implement. It is organized for finding a specific fact, not for reading top to bottom. For a guided introduction, start with tinyidp help getting-started; for the scenario catalog, see tinyidp help scenarios.

Configuration

tinyidp is configured through the Glazed command framework. A reusable oidc field section defines the provider settings; it is composed into the serve-dev and print-config commands so flags, environment variables, and config-file schema never drift between them.

Fields

FlagEnvDefaultPurpose
--issuerTINYIDP_ISSUERhttp://localhost:5556Issuer URL; endpoints are derived from it. Path-based issuers such as http://localhost:5556/realms/demo are supported.
--addrTINYIDP_ADDR127.0.0.1:5556Listen address (loopback by default; set 0.0.0.0:5556 for LAN).
--client-idTINYIDP_CLIENT_IDdev-clientClient ID. If it matches a builtin, the config is merged into it.
--client-secretTINYIDP_CLIENT_SECRET(empty)If set, /token enforces it; if empty, the client is public.
--redirect-urisTINYIDP_REDIRECT_URIShttp://localhost:3000/callback,http://127.0.0.1:3000/callbackAllowlist of redirect URIs (repeat the flag or pass a list).
--users-fileTINYIDP_USERS_FILE(empty)Optional YAML/JSON file with seeded users and claims.

Precedence

From lowest to highest:

  1. Section defaults
  2. Profiles
  3. Config files (--config-file)
  4. Environment variables (TINYIDP_*)
  5. Positional arguments
  6. CLI flags

A local override always wins. Use tinyidp serve-dev --print-parsed-fields to see which source won each field, or tinyidp print-config to print the resolved configuration without starting the server.

Config files

A YAML config file layers above profiles and below env/flags. The fields live under the oidc section slug:

oidc:
  issuer: http://localhost:5556
  addr: 127.0.0.1:5556
  client-id: dev-client
  client-secret: dev-secret
  redirect-uris:
    - http://localhost:8080/callback
  users-file: ./users.yaml

Pass it with tinyidp serve-dev --config-file config.yaml.

Portable examples are checked in under examples/configs/:

FilePurpose
dev-root.yamlBasic root-issuer setup for local development.
personal-inbox-root.yamlxgoja personal-inbox setup with a root issuer.
personal-inbox-realm.yamlxgoja personal-inbox setup with a path-based issuer URL.
public-spa-pkce.yamlBuiltin public SPA client while preserving PKCE-required behavior.
confidential-web-app.yamlBuiltin confidential web-app client with local dev-secret.

oidc.users-file is resolved relative to the process working directory. For the checked-in examples, run from the tinyidp repository root or pass an absolute users-file path.

Profiles

Profiles are named bundles of overrides stored in a YAML file, selected with --profile. They sit above defaults and below config/env/flags, so they are a convenient baseline that local overrides always win against.

The file is a YAML map: profile name, then section slug, then fields.

dev:
  oidc:
    client-id: dev-profile-client
    addr: 127.0.0.1:6600
ci:
  oidc:
    client-id: ci-runner
    addr: 127.0.0.1:6601

tinyidp looks for a default file at ~/.config/tinyidp/profiles.yaml ($XDG_CONFIG_HOME/tinyidp/profiles.yaml on Linux). If the default file is missing and the requested profile is default, loading is skipped silently — tinyidp works out of the box with no profiles.yaml. A non-default profile with no file is an error, never a silent fallback.

tinyidp serve-dev --profile dev
TINYIDP_PROFILE=ci tinyidp serve-dev

Seeded users

By default, tinyidp derives a stable synthetic user from whatever login is typed. For integration tests that need fixed subjects or app-specific claims, pass --users-file (or oidc.users-file in config). The file may be YAML or JSON:

users:
  - login: alice
    sub: user-alice-fixed
    email: alice@example.test
    name: Alice Inbox
    password: alice-password
    email_verified: true
    groups: [inbox-users]
    roles: [writer]
    tenant: personal
    preferred_username: alice
    locale: en-US
  - login: bob
    sub: user-bob-fixed
    email: bob@example.test
    name: Bob Inbox
    password: bob-password
    email-verified: true
    groups: [inbox-users]
    roles: [reader]
    tenant: personal
    claims:
      feature_flags: [compact-inbox]

Seeded users are registered as normal scenarios. They override builtins with the same login and appear on the login page under "Seeded users" by default.

password is optional. If it is omitted or empty, the seeded user remains permissive and any submitted password is accepted. If it is set, authorize POST must submit the exact fixture password; wrong or missing passwords return 401 invalid login or password and no session or authorization code is created. Passwords are plain local test fixture values, not production credentials.

Generic top-level claim helpers are available for common authorization fixtures: groups, roles, tenant, preferred_username, and locale. These expand into ordinary top-level claims in both the ID token and userinfo response. The raw claims map remains available for provider-specific or unusual shapes; explicit claims entries override generic helper fields with the same claim name. Use omit_claims when a seeded user should deliberately omit a base claim such as email.

Seeded-user fields

FieldTypeMeaning
loginstringRequired login/scenario name. Normalized before registration.
substringOptional fixed subject. Defaults to deterministic synthetic subject derived from login.
emailstringOptional fixed email. Defaults to login or login@example.test.
namestringOptional display name. Defaults to login local part.
passwordstringOptional fixture password. Empty means any submitted password is accepted.
email_verified / email-verifiedboolOptional email verification claim. Both spellings are accepted.
groupslist[string]Generic top-level groups claim. Whitespace-only entries are dropped.
roleslist[string]Generic top-level roles claim. Whitespace-only entries are dropped.
tenantstringGeneric top-level tenant claim.
preferred_usernamestringGeneric top-level preferred username claim.
localestringGeneric top-level locale claim.
claimsobjectRaw extra claims. Values here override generic helper fields with the same name.
omit_claimslist[string]Claims to delete from ID token and userinfo after extra claims are merged.
categorystringLogin-page quick-pick category. Defaults to Seeded users.

Clients

tinyidp ships a client registry with three builtins, so one running instance can serve a public SPA, a confidential web app, and a permissive dev client simultaneously.

ClientTypePKCESecretPost-logout redirect
dev-clientpublicoptional(none)http://localhost:3000
public-spapublicrequired(none)http://localhost:8080
web-appconfidentialoptionaldev-secrethttp://localhost:8080

Each client owns its own redirect-URI allowlist, PKCE requirement, scope allowlist, and post-logout redirect allowlist. A redirect URI is valid for a specific client, not globally, and an authorization code issued to one client cannot be redeemed by another.

When the configured --client-id matches a builtin, the configuration is merged into the builtin rather than replacing it: the builtin's class-defining properties (RequirePKCE, Secret, AllowedScopes) are preserved, and redirect URIs are unioned. A non-matching --client-id registers a new permissive client.

Endpoints

EndpointMethodPurpose
/.well-known/openid-configurationGETDiscovery; advertises every endpoint and supported grant/scope/claim.
/jwksGETPublic signing keys (three kids). See JWKS rotation below.
/authorizeGET/POSTAuthorization endpoint; GET decides silent vs interactive login, POST submits credentials.
/device_authorizationPOSTOAuth Device Authorization Grant start endpoint; returns a device_code, user_code, verification URIs, expiry, and polling interval.
/deviceGET/POSTBrowser approval form for device grants; approves or denies a user_code using normal scenario/seeded-user login semantics.
/tokenPOSTToken endpoint; authorization_code, refresh_token, and urn:ietf:params:oauth:grant-type:device_code grants.
/userinfoGETUserInfo; bearer-token-protected claims.
/end-sessionGETRP-initiated logout. See Logout below.
/healthzGETLiveness (ok).
/debug/*GET/POSTLoopback-only introspection. See Debug UI below.

If --issuer includes a path component, tinyidp registers the same endpoints under that prefix as well as at the root. For example, issuer http://localhost:5556/realms/demo serves discovery at /realms/demo/.well-known/openid-configuration and advertises endpoint URLs under /realms/demo. This includes /device_authorization and /device for device-flow tests. Path-based issuers are URL-shape compatibility only; seeded-user claims remain generic and provider-neutral.

Behaviors

DPoP sender-constrained tokens

tinyidp implements opt-in DPoP sender-constrained access tokens. If a token request includes a valid DPoP proof JWT, tinyidp computes the proof JWK thumbprint, stores it beside the opaque access token, and returns token_type: DPoP. If the request omits DPoP, tinyidp returns the existing bearer response.

Discovery advertises:

"dpop_signing_alg_values_supported": ["ES256", "RS256"]

DPoP behavior:

  • Token endpoint proofs must use typ: dpop+jwt, alg: ES256 or RS256, a public JWK, jti, htm, htu, and fresh iat.
  • /userinfo calls for DPoP-bound tokens must use Authorization: DPoP <token> and a fresh DPoP proof whose key matches the token binding.
  • /userinfo proofs must include ath, the base64url SHA-256 hash of the access token.
  • Reusing a proof jti with the same key is rejected by the in-memory replay cache.
  • Refresh tokens issued from DPoP-bound flows are bound to the same key and require matching DPoP proofs during rotation.
  • Nonce support is not implemented in the first DPoP release.

For a guided explanation, see tinyidp help tutorial-dpop.

Device Authorization Grant

tinyidp implements the OAuth 2.0 Device Authorization Grant for local and integration-test clients. A device starts with POST /device_authorization, then polls /token with grant_type=urn:ietf:params:oauth:grant-type:device_code while the user approves or denies the request in a browser at /device.

Important behavior:

  • client_id is required and must identify a registered client.
  • Scope must include openid; client scope allowlists are enforced when configured.
  • User codes are eight characters displayed as XXXX-XXXX; approval normalizes case, spaces, and hyphens.
  • Polling before approval returns authorization_pending.
  • Polling faster than the response interval returns slow_down.
  • Denial returns access_denied.
  • Expiry returns expired_token.
  • Unknown, mismatched, or already-used device codes return invalid_grant.
  • Approved grants issue access tokens, ID tokens for openid, and refresh tokens for offline_access.
  • A successful device-code token exchange deletes the grant, making the device code one-time use.

For a runnable walkthrough, see tinyidp help tutorial-device-authorization.

Sessions

After a login, tinyidp sets an IdP session cookie and remembers the authentication time. The authorize GET endpoint implements the OIDC session rules:

  • prompt=none forbids any UI. If a valid, fresh-enough session exists, a code is issued silently; otherwise the RP receives login_required.
  • prompt=login forces the login form even when a session exists.
  • max_age requires the session's auth_time to be within the given seconds; otherwise re-authentication is forced.
  • login_hint prefills the login form.

The auth_time claim in the ID token is the original login time, not the token-issuance time. This is what makes max_age checks honest: a silent re-issue minutes after login still reports the true age of the session. The cookie is HttpOnly and SameSite=Lax but not Secure, because tinyidp serve-devs plain HTTP on loopback and a Secure cookie would never be sent.

Refresh tokens

When the RP requests the offline_access scope, the token response includes a refresh_token. Refresh tokens rotate: each use deletes the presented token and issues a new one. Reusing a rotated token fails with invalid_grant — the standard reuse signal. A refresh token cannot be redeemed by a different client than the one it was issued to.

JWKS rotation

/jwks publishes three keys: dev-key-1 (the active signing key), rotated-key-2, and bad-sig-key. The SignKey scenario field selects which key signs the ID token (see tinyidp help scenarios). Because /jwks is global rather than tied to a login, its failure modes are server-level, toggled through the debug UI:

curl -X POST http://localhost:5556/debug/jwks-mode \
  -H 'Content-Type: application/json' -d '{"mode":"500"}'

Valid modes are normal (default), 500, slow (sleeps 10s), and empty (returns {"keys":[]}). The debug reset restores normal.

Logout

/end-session implements RP-initiated logout. Parameters:

  • id_token_hint — a previously issued ID token. Its payload is decoded (signature not re-verified) to find the subject, and any session for that subject is deleted — even from a client that holds no cookie.
  • post_logout_redirect_uri — a URI to redirect to after logout. It must be registered for the client; when client_id is given, it is checked against that client only, otherwise against all clients.
  • state — forwarded on the redirect as a query parameter.

The session cookie is always cleared. Without a post_logout_redirect_uri, tinyidp returns a logged-out page.

Debug UI

The /debug/* endpoints are read-only views of in-memory state plus a reset, guarded to loopback so a LAN bind does not leak state.

EndpointMethodReturns
/debugGETCounts and the current JWKS mode.
/debug/sessionsGETActive sessions (login, sub, auth_time, expires).
/debug/codesGETOutstanding authorization codes.
/debug/tokensGETIssued access tokens.
/debug/device-grantsGETPending/approved/denied device grants with redacted device-code prefixes.
/debug/jwks-modeGET/POSTRead or set the JWKS failure mode.
/debug/resetPOSTClear all sessions, codes, tokens, refresh tokens, and device grants.

Secrets are shown as 8-character prefixes — enough to correlate a flow against a log without exposing the full token in a listing.

Troubleshooting

ProblemCauseSolution
prompt=none always returns login_required.The session cookie is not being sent.tinyidp serve-devs plain HTTP; ensure the cookie is not marked Secure by a proxy, and that the RP and IdP share a context.
Config flag has no effect.A higher-precedence source overrode it.Run tinyidp serve-dev --print-parsed-fields to see which source won.
--redirect-uris replaced the builtin's URIs.It does not — it unions them.The merge preserves builtin properties; the configured URI is added, not swapped.
Debug endpoints return 403.The request is not from loopback.Call them from the same host; they are loopback-only by design.
Users file cannot be read.oidc.users-file was relative to a different working directory.Run from the tinyidp repository root or pass an absolute users-file path.
xgoja login redirects but callback fails.The app base URL does not match the configured redirect URI.Match TINYIDP_BASE_URL and oidc.redirect-uris, for example http://127.0.0.1:19794/auth/callback.

See also

  • tinyidp help getting-started — install and first login.
  • tinyidp help user-guide — operational guide for running tinyidp against relying parties.
  • tinyidp help developer-guide — implementation guide for extending tinyidp.
  • tinyidp help tutorial-first-rp-login — first relying-party login walkthrough.
  • tinyidp help tutorial-seeded-users-and-claims — deterministic users and claim fixtures.
  • tinyidp help tutorial-xgoja-personal-inbox — xgoja personal-inbox smoke workflow.
  • tinyidp help tutorial — a guided walkthrough of scenarios.
  • tinyidp help scenarios — the full scenario catalog and model.