Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Add Auth to a React App

This guide walks through adding Aero2 authentication to your application using the Authorization Code flow with PKCE.

Prerequisites

  • Your application's Aero2 URL (e.g., https://your-app.aero2.dev)
  • Admin access to register an OAuth client

1. Register an OAuth Client

First, create an OAuth client for your application:

curl -X POST https://your-app.aero2.dev/api/clients \
  -H "Authorization: Bearer <admin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Application",
    "redirect_uris": ["https://myapp.com/callback"]
  }'

Save the client_id and client_secret from the response. The secret is only shown once.

2. Implement the Login Flow

Generate PKCE values and redirect

// Generate PKCE
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = base64UrlEncode(array);
 
const hash = await crypto.subtle.digest(
  'SHA-256',
  new TextEncoder().encode(codeVerifier)
);
const codeChallenge = base64UrlEncode(hash);
 
// Store code_verifier in session (you'll need it later)
sessionStorage.setItem('code_verifier', codeVerifier);
 
// Generate state for CSRF protection
const state = crypto.randomUUID();
sessionStorage.setItem('oauth_state', state);
 
// Redirect to Aero2
const params = new URLSearchParams({
  client_id: 'your-client-id',
  redirect_uri: 'https://myapp.com/callback',
  response_type: 'code',
  scope: 'openid profile email',
  state,
  code_challenge: codeChallenge,
  code_challenge_method: 'S256',
});
 
window.location.href =
  `https://your-app.aero2.dev/oauth2/authorize?${params}`;

Handle the callback

// On your /callback page
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
 
// Verify state matches
const savedState = sessionStorage.getItem('oauth_state');
if (state !== savedState) {
  throw new Error('State mismatch — possible CSRF attack');
}
 
// Exchange code for tokens (do this server-side!)
const codeVerifier = sessionStorage.getItem('code_verifier');
 
const response = await fetch('https://your-app.aero2.dev/oauth2/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    redirect_uri: 'https://myapp.com/callback',
    client_id: 'your-client-id',
    client_secret: 'your-client-secret',
    code_verifier: codeVerifier,
  }),
});
 
const tokens = await response.json();
// tokens.access_token, tokens.id_token, tokens.refresh_token

Fetch user info

const userResponse = await fetch(
  'https://your-app.aero2.dev/oauth2/userinfo',
  {
    headers: {
      Authorization: `Bearer ${tokens.access_token}`,
    },
  }
);
 
const user = await userResponse.json();
// { sub: "...", name: "Jane Doe", email: "jane@example.com" }

Refresh tokens when expired

const refreshResponse = await fetch(
  'https://your-app.aero2.dev/oauth2/token',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: tokens.refresh_token,
      client_id: 'your-client-id',
      client_secret: 'your-client-secret',
    }),
  }
);
 
const newTokens = await refreshResponse.json();
// Store the new refresh_token — the old one is invalidated

3. Verify Tokens

For server-side token verification, use the JWKS endpoint:

jose
import * as jose from 'jose';
 
const JWKS = jose.createRemoteJWKSet(
  new URL('https://your-app.aero2.dev/oauth2/jwks.json')
);
 
const { payload } = await jose.jwtVerify(accessToken, JWKS, {
  issuer: 'https://your-app.aero2.dev',
  audience: 'your-client-id',
});

Security Checklist

  • Always use PKCE with S256
  • Verify the state parameter on callback
  • Exchange codes server-side (never expose client_secret in the browser)
  • Store refresh tokens securely
  • Always save the new refresh token after refresh (rotation is enforced)
  • Verify token signatures using JWKS
  • Check iss and aud claims