OAuth 2.0 & sign in

Authorization code flow with PKCE, tokens, refresh and OpenID Connect.

Endpoints

Discovery document
https://account.vaxhost.com/.well-known/openid-configuration
Authorization URL
https://account.vaxhost.com/oauth/authorize
Token URL
https://account.vaxhost.com/oauth/token
UserInfo
https://account.vaxhost.com/oauth/userinfo
Revocation
https://account.vaxhost.com/oauth/revoke
JWKS
https://account.vaxhost.com/oauth/jwks

Rules

  • PKCE with S256 is required for public clients and recommended for everyone.
  • redirect_uri must exactly match one registered on your app.
  • Authorization codes expire after 10 minutes and work once. Reusing a code revokes the tokens issued from it.
  • Access tokens last 1 hour; refresh tokens 30 days.
  • Every refresh returns a new refresh token. Presenting an old one revokes all of that person's tokens for your app.
  • With the openid scope you also get an RS256-signed ID token; verify it with the JWKS keys.
  • New apps are reviewed by our team before people can sign in.

Browser or mobile app (public client, PKCE)

// 1. Create a PKCE verifier and its S256 challenge (keep the verifier secret).
const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = b64url(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))));
const state = b64url(crypto.getRandomValues(new Uint8Array(16)));
sessionStorage.setItem('mithila_pkce', JSON.stringify({ verifier, state }));

// 2. Send the person to Mithila to sign in and approve.
location.href = 'https://account.vaxhost.com/oauth/authorize?' + new URLSearchParams({
  response_type: 'code',
  client_id: 'mth_cl_your_client_id',
  redirect_uri: 'https://app.example.com/callback',
  scope: 'openid profile email',
  state,
  code_challenge: challenge,
  code_challenge_method: 'S256',
});

// 3. On https://app.example.com/callback?code=…&state=… exchange the code.
const saved = JSON.parse(sessionStorage.getItem('mithila_pkce'));
const params = new URLSearchParams(location.search);
if (params.get('state') !== saved.state) throw new Error('State mismatch');
const res = await fetch('https://account.vaxhost.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: params.get('code'),
    redirect_uri: 'https://app.example.com/callback',
    client_id: 'mth_cl_your_client_id',
    code_verifier: saved.verifier,
  }),
});
const tokens = await res.json(); // { access_token, refresh_token, expires_in, id_token? }

function b64url(bytes) {
  return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

Server-side app (confidential client)

# Confidential (server-side) apps authenticate with their secret (HTTP Basic or form fields).
curl -X POST https://account.vaxhost.com/oauth/token \
  -u "mth_cl_your_client_id:mth_cs_your_client_secret" \
  -d grant_type=authorization_code \
  -d code="CODE_FROM_CALLBACK" \
  -d redirect_uri="https://app.example.com/callback" \
  -d code_verifier="YOUR_PKCE_VERIFIER"

# Refresh (the old refresh token stops working — store the new one).
curl -X POST https://account.vaxhost.com/oauth/token \
  -u "mth_cl_your_client_id:mth_cs_your_client_secret" \
  -d grant_type=refresh_token -d refresh_token="mth_rt_…"

# Who signed in?
curl https://account.vaxhost.com/oauth/userinfo -H "Authorization: Bearer mth_at_…"

# Sign out of your app: revoke the refresh token.
curl -X POST https://account.vaxhost.com/oauth/revoke \
  -u "mth_cl_your_client_id:mth_cs_your_client_secret" \
  -d token="mth_rt_…" -d token_type_hint=refresh_token

Scopes

  • openid — Know who you are (your Mithila account ID)
  • profile — See your name and profile photo
  • email — See your email address
  • profile.read — Read your profile: name, email and photo
  • orders.read — See your orders
  • seller.orders.read — See your store's orders
  • seller.orders.write — Mark your store's orders as shipped