OAuth sign-in for HTML documentation
The Ruby server can use an existing OAuth provider to sign readers into its HTML documentation. This is an optional confidential client: the server exchanges authorization codes and keeps the client secret out of the browser. Passwords, MFA, account recovery and optional SSO remain with the provider.
Configure the client
Register a dedicated confidential client with your OAuth provider. Use the exact
HTTPS callback <API mount>_documentation/oauth/callback, fixed access-token
expiry and no refresh token. Keep existing default OAuth clients unchanged.
Configure the documentation client before mounting the server:
api.documentation_oauth(
client_id: ENV.fetch('DOCUMENTATION_CLIENT_ID'),
client_secret: ENV.fetch('DOCUMENTATION_CLIENT_SECRET'),
redirect_uri: 'https://docs.example.test/api/_documentation/oauth/callback',
session_secret: File.binread('/run/secrets/documentation-session'),
scope: ['all'],
session_lifetime: 3600
)
api.mount('/api/')
Generate at least 64 bytes of cryptographically random session secret material once, keep it private, and supply the same value to every worker. Do not generate it on each startup. Changing it signs existing documentation sessions out. Client credentials and session secrets must not be committed or included in diagnostics.
scope defaults to []. Request only the scopes needed by the application.
vpsAdmin uses ['all'] to describe the actions available to the signed-in user's
role. Its resulting bearer token can also authorize API requests, so keeping the
documentation cookie out of API authentication is essential.
session_lifetime defaults to 3600 seconds and accepts integers from 1 to 86400.
The actual session ends at the earlier of this limit and the provider's access
token expiry. No refresh token is stored or used.
switch_user defaults to false and accepts only true or false. Enable it
only after verifying that a fresh authorization for this dedicated client lets
the reader enter different credentials. This option asserts that the operator
has checked that behavior; it does not discover account-selection support or
send prompt or select_account parameters.
switch_user_revoke_params defaults to {} and accepts a static hash of string
keys and string values. These fields are added to the revoke POST only for a
validated Switch user request. They are never sent during ordinary Sign out,
authorization or token exchange. The required client_id, client_secret,
token and token_type_hint fields cannot be configured through this option.
The server copies and freezes the hash and its strings at startup; browser
fields and later changes to the original hash cannot change the revoke payload.
The option has no effect when switch_user is false.
For example, a dedicated nondefault vpsAdmin client can allow SSO and use the
provider's close_sso revoke parameter when switching:
api.documentation_oauth(
# Include the client, callback, session and scope options shown above.
switch_user: true,
switch_user_revoke_params: { 'close_sso' => '1' }
)
Verify that the provider accepts these parameters and that the next authorization allows different credentials. Revoking the documentation token alone can leave SSO available when another active authorization shares it. Provider-specific SSO closure need not revoke that other authorization's access token. A dedicated client with SSO disabled is another way to permit account switching.
The callback must be an absolute HTTPS URL with the exact mounted callback path, without credentials, query or fragment. The server takes authorize, token and revoke endpoints from its default version's configured OAuth provider metadata at mount time. Those endpoints must also use HTTPS. It does not discover an issuer from a browser request or follow endpoint redirects. Missing providers, invalid configuration and repeated or late configuration fail startup.
HTTPS and worker requirements
Serve the documentation at the configured HTTPS origin. The Rack request scheme and host must reflect that origin. When using a reverse proxy, configure trusted forwarding in the server/proxy; do not trust arbitrary client-supplied forwarding headers. A private development CA must be trusted by both the reader's browser and the server's standard HTTPS trust store. Never disable peer verification.
Token and revoke exchanges use bounded HTTPS requests. If the provider shares the documentation server's public origin, the server needs enough concurrent request capacity to handle a token exchange while the callback is running. A single-request worker cannot serve both sides of that exchange.
Browser session and access checks
Sign-in uses state and S256 PKCE, with a ten-minute encrypted pending cookie. Return navigation contains validated documentation version/page/resource fields and an optional bounded fragment, rather than an arbitrary return URL. Callback errors offer a retry without exposing provider responses or exception details. Successful callbacks redirect promptly to a clean documentation URL. Failed, cancelled and replayed callbacks consume pending state and preserve an existing authenticated documentation session.
Authenticated cookies contain only the access token, expiry and logout CSRF nonce. ActiveSupport MessageEncryptor protects them with AES-256-GCM and JSON; separate purposes bind pending and authenticated cookies to the client, callback and API mount. Cookies are Secure, HttpOnly, SameSite=Lax, mount-scoped and have no Domain attribute. Complete cookies are limited to 3800 bytes; oversized token responses fail sign-in rather than producing truncated sessions. Browser storage used for documentation preferences never contains tokens.
HTML documentation GET and HEAD requests, including the versions index and search-index representations, use only the documentation cookie on its configured HTTPS origin. Without that configuration or a valid cookie, documentation is anonymous. Basic credentials, Bearer/custom token headers and query credentials do not select its identity. With API credentials for user A and a valid documentation cookie for user B, the reference and its account controls use B. Invalid or conflicting API credentials do not affect that selection.
Each network page request decrypts and validates the cookie, then validates its token through the requested version's OAuth2 provider and runs the existing post-authentication hook before describing resources. It passes the original request and the cookie token separately, without changing request headers or parameters. Identity and roles are never taken from a cached user ID. A revoked or expired session returns to the public view; an inaccessible selected resource is 404. Conditional search-index requests perform the same validation.
The standard OAuth2 provider delegates this explicit validation to its existing
config.find_user_by_access_token(request, token) callback. A custom provider
named oauth2 must implement authenticate_access_token(request, token) to
support documentation sessions. Unsupported versions or providers cannot fall
back to Basic, Token or another API authenticator. Ordinary API actions, OPTIONS
descriptions, mutations and provider endpoints retain their complete existing
authentication chain and credential-conflict rules. The documentation cookie
does not authenticate those operations.
Account sign-in, sign-out and switching belong to this documentation session.
The compatibility GET URLs _login and _logout redirect with HTTP 303 to the
default version overview without authenticating, challenging, clearing cookies or
revoking tokens. They ignore arbitrary return destinations. API Basic challenges
continue to work, and documentation switching does not erase cached Basic
credentials or sign out of an API client.
Personalized HTML and authentication responses use private, no-store caching.
Documentation pages use Referrer-Policy: strict-origin so native sign-out and
Switch user forms retain their same-origin Origin header. Referrers contain at
most the origin and are omitted on HTTPS-to-HTTP downgrades. Login, callback,
sign-out and authentication error responses use Referrer-Policy: no-referrer.
Configure access and error logs so callback query strings, tokens, passwords and
client credentials are not retained.
Sign-out and recovery
Sign-out is a same-origin POST with a session-bound CSRF nonce. The server attempts access-token revocation and clears local authenticated and pending cookies. If revocation fails, the response states that local sign-out succeeded but the server session may remain until expiry. It does not claim a successful revocation, queue tokens for retry, or request a whole-account SSO sign-out.
When switch_user: true, readers authenticated by a documentation cookie see
Switch user in Documentation settings. It submits the existing same-origin,
CSRF-protected logout POST with one URL-encoded scalar switch_user=1.
Disabled, duplicate, array and other invalid switch fields or invalid origin/CSRF
are rejected before sign-out or revocation. Ordinary Sign out sends no switch
field. API credentials on the request do not affect these cookie-session operations.
After successful local sign-out and revocation, switching returns 303 to the fixed documentation login route for the default version. That login creates new state and PKCE values; the old pending attempt and session are cleared. No return URL or scope supplied by the switch request changes that destination. Cancelling the new authorization leaves the old documentation session signed out. A revoke failure retains the 502 response described above and does not continue to login. Switching can request provider-specific SSO behavior through the configured revoke parameters; HaveAPI does not interpret them or guarantee whole-account sign-out. Ordinary Sign out still revokes only the documentation token without those parameters.
To disable this feature, remove its optional configuration before starting older
documentation code. In particular, remove switch_user_revoke_params before
rolling back to a HaveAPI version that does not support it; the unknown option
would prevent startup. Upgrade HaveAPI before enabling the option, including on
each worker that loads the configuration. Existing API data needs no migration
or rollback. Cookies expire or are ignored. Disable or revoke the dedicated
OAuth client through normal provider administration when needed; documentation
rollback does not delete clients automatically. Without this configuration, documentation remains
anonymous and offers no authentication controls; ordinary API authentication is
unchanged. Older documentation code may use API credentials and legacy browser
sign-in routes again. Rolling back requires no cookie or database migration.