TILLAUTH · FLOWS

OIDC SSO — bring your issuer.

GA

On every plan, on day one. Hand us an OIDC issuer URL and we discover the endpoints, fetch the JWKS, verify the id_token, optionally gate by allowed email domain. Apple, Microsoft, Okta, Auth0, your own — all the same flow.

01Configure

What you configure

In the per-app Providers tab, add an OIDC provider with:

  • Issuer URL — e.g. https://accounts.google.com, https://login.microsoftonline.com/<tenant>/v2.0, https://<your-domain>.okta.com.
  • Client ID + secret — from your IdP's app registration.
  • Allowed domain (optional) — only emails matching this domain can sign in via this provider. Use this for B2B SSO where only @acme.com users should land in the Acme app.
  • Scope override (optional) — default is openid email profile; some IdPs need more.

On save we hit <issuer>/.well-known/openid-configuration and cache the authorization, token, userinfo, and JWKS endpoints. JWKS is re-fetched lazily — keys are revalidated on a kid miss, not on every signin.

02Sign-in

The flow

  1. Client calls GET /v1/oidc/start?redirect_uri=…. We mint a PKCE pair + a state parameter, redirect to the issuer's authorization endpoint.
  2. Issuer authenticates the user (their UI, their MFA, their device trust), bounces back to /v1/oidc/callback?code=…&state=….
  3. We verify the signed, short-lived state parameter, then exchange the code for tokens at the token endpoint with PKCE.
  4. We verify the id_token:
    • iss must equal the configured issuer.
    • aud must include the configured client ID.
    • Signature must verify against a key in the cached JWKS (refresh on kid miss).
    • exp not past, iat not unreasonably future, nonce matches.
  5. If allowed_domain is set, the email claim's domain must match — case-insensitive. Mismatch returns 403 with audit signin.oauth.error + reason domain_not_allowed.
  6. Look up (app_id, provider='oidc', subject=id_token.sub) in our identity records. Found → session for the linked user. Not found + verified email matches an existing user → link onto them. Else → JIT-create the user with the claims (email, name).
03B2B

Allowed-domain gate

This is the killer feature for B2B. You stand up TillAuth for the Acme-Admin app, point it at Acme's Okta, set allowed_domain = "acme.com". Now:

  • An @acme.com user who signs in with Okta → user created (or linked), session minted.
  • A @gmail.com user who somehow gets through Okta → rejected at the TillAuth boundary. No account is created.
04Operate

What you can do once it's wired

  • Restrict signup methods: turn off password + magic-link per-app so the only way in is your SSO.
  • JIT provisioning: the first time an Okta user signs in for a given app, the user row is created — no manual seeding.
  • Deprovision via session revoke: when someone leaves Acme, revoke their sessions via DELETE /v1/admin/apps/<appId>/users/<userId>/sessions — they can't sign in again until Okta removes them.
05Non-goals

What we don't do

SAML. TillAuth is OIDC-only. Most modern IdPs (Okta, Azure AD, Google Workspace, Auth0, Keycloak) speak OIDC; if the only path you have is SAML, you'll need to front it with an OIDC bridge first.


Next: webhooks to react to signin events · audit log for the record of every flow.