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.
import { useState } from 'react';
export function BhauuSignIn() {
const [busy, setBusy] = useState(false);
async function signIn() {
setBusy(true);
const bytes = crypto.getRandomValues(new Uint8Array(32));
const encode = b => btoa(String.fromCharCode(...b)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const verifier = encode(bytes);
const state = encode(crypto.getRandomValues(new Uint8Array(32)));
const challenge = encode(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))));
sessionStorage.setItem('bhauu_pending', JSON.stringify({ verifier, state }));
const url = new URL('https://auth.bhauu.online/oauth/authorize');
for (const [k, v] 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(k, v);
location.assign(url);
}
return <button type="button" disabled={busy} onClick={signIn}>Sign in</button>;
}Callback route
On your callback route, verify state against the stored transaction before calling /oauth/token with the original verifier. Consume the transaction and remove the code from browser history. The plain JavaScript callback provides the request fields. A backend-for-frontend is preferable for durable sessions.
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.