Frontend (React)

PKCE helpers

export function randomString(len = 64) {
  const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
  const buf = new Uint8Array(len);
  crypto.getRandomValues(buf);
  return Array.from(buf, x => charset[x % charset.length]).join('');
}
export async function sha256Base64Url(input: string) {
  const data = new TextEncoder().encode(input);
  const digest = await crypto.subtle.digest('SHA-256', data);
  const b64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
  return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

Login button

const BASE_URL = 'https://<BASE_URL>';
async function loginWithGoogle() {
  const state = crypto.randomUUID();
  const codeVerifier = randomString(64);
  localStorage.setItem('code_verifier', codeVerifier);
  localStorage.setItem('oauth_state', state);
  const codeChallenge = await sha256Base64Url(codeVerifier);

  const res = await fetch(`${BASE_URL}/oauth/init`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
    body: JSON.stringify({
      provider: 'google',
      redirect_uri: `${location.origin}/oauth/callback`,
      state,
      code_challenge: codeChallenge
    })
  });
  const { url } = await res.json();
  location.href = url;
}

Callback

const BASE_URL = 'https://<BASE_URL>';
export async function handleOAuthCallback() {
  const p = new URLSearchParams(location.search);
  const state = p.get('wallet_oauth_state');
  const code = p.get('wallet_oauth_code');
  const verifier = localStorage.getItem('code_verifier');

  await fetch(`${BASE_URL}/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
    body: JSON.stringify({ type: 'GoogleOAuth', data: { state, code, code_verifier: verifier } })
  });

  const me = await fetch(`${BASE_URL}/session`, { credentials: 'include' }).then(r => r.json());
  console.log(me);
}

Last updated