Browser transaction
Register https://example.com/auth/callback exactly. Generate a new verifier, S256 challenge, and random state for every attempt. Keep the transaction short-lived and tied to the same browser; validate state before exchanging the code. Never include a client secret in browser code.
type Pending = { state: string; verifier: string };
const random = (): string => {
const bytes = crypto.getRandomValues(new Uint8Array(32));
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
};
const verifier = random();
const state = random();
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const pending: Pending = { state, verifier };
sessionStorage.setItem('bhauu_pending', JSON.stringify(pending));
const url = new URL('https://auth.bhauu.online/oauth/authorize');
for (const [key, value] of Object.entries({ client_id: 'YOUR_CLIENT_ID', redirect_uri: 'https://example.com/auth/callback', response_type: 'code', scope: 'profile email', state, code_challenge: challenge, code_challenge_method: 'S256' })) url.searchParams.set(key, value);
location.assign(url);Handle the callback
Parse the pending transaction as Pending, validate the returned state, consume it once, and submit grant_type=authorization_code, code, client ID, exact redirect URI, and original verifier to /oauth/token. The JavaScript callback example shows the matching HTTP request.
After the exchange
Call https://auth.bhauu.online/oauth/userinfo using the Bearer access token when you need scoped identity claims. Avoid long-term token storage in the browser; prefer a backend-owned session for durable sign-in. A refresh-grant walkthrough is not part of this stable public contract.
See security and session boundaries.