vaultaris /docs

OAuth 2.0 & OpenID Connect

How to integrate Vaultaris as an authorization server — authorization code, client credentials, password flows, DPoP sender-constrained tokens, and hosted login.

Vaultaris implements RFC 6749 (OAuth 2.0), OpenID Connect Core 1.0, and RFC 9449 (DPoP). OIDC discovery document at GET /.well-known/openid-configuration. JWK set at GET /.well-known/jwks.json.

Supported grant types

GrantUse case
Authorization code + PKCEWeb apps, mobile apps, SPAs (recommended)
Client credentialsServer-to-server (machine auth)
Password (ROPC)Legacy integrations, CLIs, trusted first-party apps
Refresh tokenObtaining new access tokens without re-authentication

Step 1 — Redirect user to /oauth/authorize

GET /oauth/authorize
  ?response_type=code
  &client_id={client_id}
  &redirect_uri={uri}
  &scope=openid profile email
  &state={random_state}
  &code_challenge={S256_challenge}
  &code_challenge_method=S256

PKCE is mandatory for public clients (client_type=public). Generate a 43–128 character random code_verifier; the code_challenge is its SHA-256 base64url encoding.

Vaultaris redirects the user to {EXTERNAL_URL}/consent/{request_id}. Retrieve details: GET /oauth/consent/{request_id}. Approve: POST /oauth/consent/{request_id}/approve with { "approved_scopes": ["openid","profile"] }. Deny: POST /oauth/consent/{request_id}/deny.

Step 3 — Exchange code for tokens

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={code}
&redirect_uri={uri}
&client_id={client_id}
&client_secret={client_secret}   # confidential clients only
&code_verifier={verifier}         # public clients

Response:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJ...",
  "id_token": "eyJ...",
  "scope": "openid profile email"
}

Client credentials flow

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id={client_id}
&client_secret={client_secret}
&scope=api:read

Refresh token flow

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token={token}
&client_id={client_id}
&client_secret={client_secret}

Token introspection

POST /oauth/introspect (form-encoded token, token_type_hint, client credentials). Returns { active, sub, client_id, scope, exp, tenant_id }.

Token revocation

POST /oauth/revoke (form-encoded token, token_type_hint, client credentials).

UserInfo endpoint

GET /oauth/userinfo with Authorization: Bearer {access_token}. Returns sub, email, email_verified, name, preferred_username, and other claims based on granted scopes.

Scopes

ScopeClaims included
openidsub, iss, aud, exp, iat
profilename, given_name, family_name, preferred_username, locale, zoneinfo
emailemail, email_verified
phonephone_number, phone_number_verified

DPoP — Sender-Constrained Tokens (RFC 9449)

DPoP binds an access token to a specific client key pair, making stolen tokens useless without the private key.

How it works

  1. The client generates an asymmetric key pair (ed25519 or P-256) at startup and retains the private key.
  2. On every token request, the client sends a DPoP header containing a signed JWT (dpop_proof) that includes:
    • jwk — the public key
    • htm / htu — HTTP method and URL
    • iat / jti — timestamp and unique nonce
  3. Vaultaris binds the issued token to the public key's thumbprint (cnf.jkt claim).
  4. On every resource request, the client presents both the access token and a fresh DPoP proof. Vaultaris verifies the proof signature and rejects requests where the key doesn't match.

SDK support

The Vaultaris Rust and Node.js SDKs include transparent DPoP support — pass a DpopSigner when constructing the client and the SDK handles proof generation automatically. Browser apps can use the sdk-web package which implements DPoP via the Web Crypto API.

Manual integration

Add the DPoP header to the token endpoint request:

POST /oauth/token
DPoP: eyJhbGciOiJFZERTQSIsInR5cCI6ImRwb3Arand0IiwiandrIjp7ImNydiI6IkVkMjU1MTkiLCJ4IjoiLi4uIn19.eyJqdGkiOiJ1bmlxdWUiLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tL29hdXRoL3Rva2VuIiwiaWF0IjoxNzE2MDAwMDAwfQ.{signature}

Subsequent API calls require a fresh DPoP proof for each request (different jti and iat).

Hosted Login

Vaultaris provides a white-label login page for each tenant at /hosted/{tenant_slug}/login. This page is configurable with tenant branding (logo, colors, custom CSS).

Config endpoint

GET /api/v1/hosted/{tenant_slug}/config — returns tenant branding, enabled auth methods, IdP list, MFA settings, and default_client_id.

Supported methods in hosted login

  • Username + password
  • TOTP MFA (via /api/v1/hosted/{slug}/mfa/verify)
  • MFA backup codes
  • Federated IdP buttons (OAuth/OIDC providers)

After successful auth, the token is delivered in the URL hash (never the query string — the hash is never sent to the server):

/hosted/{slug}/login#access_token={token}&token_type=Bearer&return_to={url}

Cross-domain SSO (Global Sessions)

When a user belongs to a group with global_session_enabled = true, the token response includes a global_session_token.

{
  "access_token": "...",
  "global_session_token": "gst_64randombytes..."
}

To transfer the session to a different domain:

  1. POST /api/v1/sessions/global/{token}/transfer with { "target_domain": "app2.example.com" } → returns a one-time transfer_token (60-second lifetime).
  2. On the target domain: POST /api/v1/sessions/transfer/redeem with { "token": "...", "domain": "app2.example.com" } → returns the GlobalSession object.

Validate an existing session: GET /api/v1/sessions/global/{token}?domain=app2.example.com&require_mfa=false.

Registering an OAuth client

POST /api/v1/tenants/{tenant_id}/clients
{
  "name": "My App",
  "client_type": "public",
  "redirect_uris": ["https://app.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "allowed_scopes": ["openid", "profile", "email"],
  "pkce_required": true,
  "consent_required": false
}

The response includes client_id and client_secret (shown once only for confidential clients).