OAuth and OIDC Authentication Flow#
Overview#
Tinyauth handles OAuth and OIDC through two distinct entry points β forward auth (proxy mode) and OIDC server mode β each with a different user experience. The frontend passes context between pages using a small set of URL screen params (login_for, redirect_uri, oidc_ticket, oidc_scope, oidc_name, oidc_prompt) defined in screen-params.ts.
OAuth Auto-Redirect (Forward Auth Mode)#
When Tinyauth is used as a forward auth proxy and OAuth.AutoRedirect is set to a provider ID, the login page checks at mount time whether the named provider exists and a redirect_uri is present. If so, it skips the login UI and triggers an automatic OAuth redirect.
The auto-redirect fires exactly once (guarded by hasAutoRedirectedRef) by calling GET /api/oauth/url/:provider with the compiled screen params. On success, the browser navigates to the provider after a 500 ms delay; if the redirect stalls beyond 5 seconds, a manual "redirect" button appears. On error, auto-redirect mode is cancelled and the full login UI is shown.
The oauthURLHandler validates the redirect_uri against the app URL using isRedirectSafe() before creating a pending session β unsafe URIs are silently dropped. isRedirectSafe requires matching scheme and port, and optionally permits subdomains of the configured cookie domain when SubdomainsEnabled is true.
OAuth Callback and Session Creation#
After the provider redirects back, oauthCallbackHandler verifies the PKCE state, exchanges the authorization code for tokens, fetches userinfo, enforces the email whitelist, and creates a session cookie.
The callback then branches on login_for:
login_for=app(forward auth): redirects to/continue?redirect_uri=...to return the user to the originally requested URL.login_for=oidc(OIDC server): redirects to/oidc/authorizewith the original OIDC params re-attached, resuming the two-stage OIDC flow.
Ticket-Based Two-Stage OIDC Authorization Flow#
When Tinyauth acts as an OIDC provider, the authorization flow is split into two stages to avoid exposing full OIDC request parameters in browser redirects.
Stage 1: /authorize β Ticket Creation#
GET/POST /authorize validates the OIDC request (client ID, redirect URI, prompt, PKCE), then creates a ticket β storing the full AuthorizeRequest in a 10-minute in-memory cache keyed by a random 32-character string. The handler then redirects to the frontend at /oidc/authorize?login_for=oidc&oidc_ticket=...&oidc_scope=...&oidc_name=...; the actual OIDC parameters are never in the redirect URL itself.
Prompt handling at this stage:
prompt=nonewith no active session β immediatelogin_requirederror, no login redirect.prompt=loginor expiredmax_ageβ ticket carriesoidc_prompt=login, forcing re-authentication on the frontend.
Stage 2: Frontend Consent β /api/oidc/authorize-complete#
AuthorizePage reads the ticket and scope from URL params. If the user is unauthenticated or oidc_prompt=login, it redirects to /login with all screen params intact so the login page can complete authentication and return. When oidc_prompt=none and the user is already authenticated, consent is automatically submitted without showing the UI.
On consent (explicit or automatic), the frontend POSTs the ticket to /api/oidc/authorize-complete. The backend retrieves and deletes the cached AuthorizeRequest, then issues an authorization code. The redirect URI with code and state is returned as JSON for the frontend to navigate to.
Token Exchange#
POST /api/oidc/token supports authorization_code and refresh_token grant types. Client credentials are accepted via form params or HTTP Basic auth. Code reuse is actively detected: if a previously-issued code hash is re-submitted, the associated session is immediately revoked.
Forward Auth Entry Point (Proxy Controller)#
Unauthenticated requests reaching the proxy endpoint (/api/auth/:proxy) are redirected to /login?redirect_uri=...&login_for=app. Browser detection uses a User-Agent regex (Chrome|Gecko|AppleWebKit|Opera|Edge); non-browser clients and Nginx always receive a 401 JSON response with an x-tinyauth-location header rather than a redirect.
On successful authentication, the proxy controller forwards Remote-User, Remote-Name, Remote-Email, Remote-Groups, and Remote-Sub headers to the upstream service.
Key Files#
| File | Role |
|---|---|
frontend/src/pages/login-page.tsx | OAuth auto-redirect logic, provider buttons |
frontend/src/pages/authorize-page.tsx | OIDC consent screen and auto-authorize |
frontend/src/lib/hooks/screen-params.ts | URL params schema shared across pages |
internal/controller/oauth_controller.go | OAuth URL generation, callback, redirect branching |
internal/controller/oidc_controller.go | /authorize, authorize-complete, token, userinfo |
internal/service/oidc_service.go | Ticket cache, code issuance, token generation |
internal/controller/proxy_controller.go | Forward auth entry point, header injection |