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 photoemail— See your email addressprofile.read— Read your profile: name, email and photoorders.read— See your ordersseller.orders.read— See your store's ordersseller.orders.write— Mark your store's orders as shipped