Quick Start

This guide walks you through adding "Login with Xident" to your site in 5 steps. By the end, your users will be able to sign in with their Xident ID and you will receive their verified age bracket — no PII, no document handling, no friction.

Prerequisites

  • A Xident account — sign up here
  • A project with an OAuth client configured in the dashboard
  • Node.js 18+ (for the backend example)

Step 1: Register an OAuth Client

  1. Log in to dashboard.xident.io
  2. Navigate to Settings → OAuth Clients
  3. Click "Create OAuth Client"
  4. Enter a name (e.g., "My Website")
  5. Copy your Client ID and Client Secret

Keep your Client Secret safe

Store the client secret as an environment variable on your server. Never expose it in client-side code or commit it to version control.

Step 2: Configure Redirect URIs

In the same OAuth client settings page, add your redirect URI:

  • Production: https://yoursite.com/callback
  • Development: http://localhost:3000/callback

Redirect URIs must be exact matches — no wildcards. HTTPS is required in production. Only http://localhost is permitted for local development.

Step 3: Add the Login Button

<!-- Add this wherever you want the login button -->
<a
  id="xident-login"
  href="#"
  style="display:inline-flex;align-items:center;gap:8px;padding:10px 20px;
         background:#14b8a6;color:#fff;border-radius:8px;font-weight:600;
         text-decoration:none;font-family:sans-serif;"
>
  <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
    <path d="M12 2L2 7v10l10 5 10-5V7L12 2z"/>
  </svg>
  Login with Xident
</a>

Then generate PKCE parameters and redirect to the authorization endpoint:

// Step 1: Generate PKCE code verifier and challenge

// Generate a random code verifier (43-128 chars, URL-safe)
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return btoa(String.fromCharCode(...array))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

// Derive the code challenge from the verifier (SHA-256, base64url)
async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}
// Step 2: Build the authorization URL and redirect

async function loginWithXident() {
  const codeVerifier = generateCodeVerifier();
  const codeChallenge = await generateCodeChallenge(codeVerifier);

  // Store verifier in session — you'll need it for token exchange
  sessionStorage.setItem('xident_code_verifier', codeVerifier);

  // Also store a random state parameter for CSRF protection
  const state = generateCodeVerifier(); // reuse the same random function
  sessionStorage.setItem('xident_oauth_state', state);

  const params = new URLSearchParams({
    client_id: 'YOUR_CLIENT_ID',
    redirect_uri: 'https://yoursite.com/callback',
    response_type: 'code',
    scope: 'openid age_verification',
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
    state: state,
  });

  window.location.href =
    `https://api.xident.io/oauth/authorize?${params.toString()}`;
}

// Wire up the button
document.getElementById('xident-login')
  .addEventListener('click', (e) => {
    e.preventDefault();
    loginWithXident();
  });

Step 4: Handle the Callback

After the user approves, Xident redirects back to your redirect_uri with a code and state parameter. Handle this on your callback page:

// Step 3: Handle the callback (on your /callback page)

async function handleCallback() {
  const params = new URLSearchParams(window.location.search);
  const code = params.get('code');
  const state = params.get('state');
  const error = params.get('error');

  // Check for errors
  if (error) {
    console.error('OAuth error:', error, params.get('error_description'));
    return;
  }

  // Verify state matches (CSRF protection)
  const savedState = sessionStorage.getItem('xident_oauth_state');
  if (state !== savedState) {
    console.error('State mismatch — possible CSRF attack');
    return;
  }

  // Send the code to your backend for token exchange
  const response = await fetch('/api/auth/xident/callback', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      code,
      code_verifier: sessionStorage.getItem('xident_code_verifier'),
    }),
  });

  const result = await response.json();

  // Clean up
  sessionStorage.removeItem('xident_code_verifier');
  sessionStorage.removeItem('xident_oauth_state');

  if (result.success) {
    window.location.href = '/dashboard';
  }
}

handleCallback();

Step 5: Exchange Code for Tokens

On your backend, exchange the authorization code for an access token, then fetch the user's verified age information:

// Step 4: Exchange the code for tokens (Node.js backend)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/api/auth/xident/callback', async (req, res) => {
  const { code, code_verifier } = req.body;

  try {
    // Exchange authorization code for tokens
    const tokenResponse = await fetch('https://api.xident.io/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: process.env.XIDENT_CLIENT_ID,
        client_secret: process.env.XIDENT_CLIENT_SECRET,
        code: code,
        redirect_uri: 'https://yoursite.com/callback',
        code_verifier: code_verifier,
      }),
    });

    if (!tokenResponse.ok) {
      const error = await tokenResponse.json();
      return res.status(400).json({ success: false, error: error.error });
    }

    const tokens = await tokenResponse.json();
    // tokens = {
    //   access_token: "eyJhbG...",
    //   token_type: "Bearer",
    //   expires_in: 3600,
    //   refresh_token: "xrt_abc123...",
    //   id_token: "eyJhbG...",
    //   scope: "openid age_verification"
    // }

    // Step 5: Fetch verified user info
    const userInfoResponse = await fetch('https://api.xident.io/oauth/userinfo', {
      headers: { Authorization: `Bearer ${tokens.access_token}` },
    });

    const userInfo = await userInfoResponse.json();
    // userInfo = {
    //   sub: "xid_abc123def456",
    //   age_verified: true,
    //   age_bracket: "18+",
    //   verification_level: "ml"
    // }

    // Create a session for the user in your app
    // ... your session logic here ...

    res.json({
      success: true,
      age_verified: userInfo.age_verified,
      age_bracket: userInfo.age_bracket,
    });
  } catch (error) {
    console.error('Token exchange error:', error);
    res.status(500).json({ success: false, error: 'Token exchange failed' });
  }
});

That's it!

Your integration is complete. Users can now log in with their Xident ID and you receive their verified age bracket without handling any PII or documents.

Next steps