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
- Log in to dashboard.xident.io
- Navigate to Settings → OAuth Clients
- Click "Create OAuth Client"
- Enter a name (e.g., "My Website")
- 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
- Authorization Flow — Deep dive into the authorization code flow with PKCE
- Scopes & Claims — Request additional claims beyond age verification
- Token Management — Refresh tokens, revocation, and JWT verification
- Security Best Practices — Harden your OAuth integration
- API Reference — Complete endpoint documentation