Cross-Domain Passkey Integration
This guide explains how third-party applications can enable Veridex passkey authentication, allowing users to sign in with passkeys they've already created on veridex.network.
Overview
Veridex supports a "Google-style" authentication flow that works across all domains. Users carry their identity (and assets) to your application without creating new accounts.
Two methods are supported, and the SDK handles both automatically:
- Related Origin Requests (ROR) — The modern WebAuthn Level 3 standard. If the user's browser supports ROR and your origin is listed in the
.well-known/webauthnfile, the passkey dialog appears natively. No popup needed. - Auth Portal Fallback — A popup/redirect flow via
auth.veridex.network. Works on all browsers. The user signs with their passkey at the portal, and a session is returned to your app.
You don't need to choose between these methods. The SDK detects browser support and picks the best flow automatically.
Quick Start
Step 1: Register Your App
Every third-party origin must be registered before it can use Veridex cross-domain passkeys. Registration is free and instant.
Option A: Developer Portal (UI)
- Go to the Developer Portal (opens in a new tab).
- Enter your Application Name, Origin (e.g.,
https://myapp.com), and optionally a Contact Email. - You'll receive an App ID and API Key. Save the API key — it's shown only once.
Option B: API
curl -X POST https://relayer.veridex.network/api/v1/apps/register \
-H "Content-Type: application/json" \
-d '{
"name": "My DeFi App",
"origin": "https://myapp.com",
"contactEmail": "dev@myapp.com"
}'Response:
{
"success": true,
"app": {
"id": "app_a1b2c3d4e5f6",
"name": "My DeFi App",
"origin": "https://myapp.com",
"apiKey": "vdx_sk_...",
"trustLevel": "registered",
"status": "active"
}
}Save your API Key immediately. It is only shown once during registration. You'll need it to manage your app or delete it later.
Step 2: Install the SDK
npm install @veridex/sdkStep 3: Implement Authentication
import { createCrossOriginAuth } from '@veridex/sdk';
const auth = createCrossOriginAuth();
async function signIn() {
try {
// The SDK automatically detects ROR support and falls back to Auth Portal
if (await auth.supportsRelatedOrigins()) {
// Seamless: native passkey dialog, no popup
const result = await auth.authenticate();
console.log('Signed in (ROR):', result.credential.keyHash);
return result;
}
// Fallback: popup to auth.veridex.network
const session = await auth.connectWithVeridex();
console.log('Signed in (Portal):', session.address);
return session;
} catch (error) {
console.error('Sign in failed:', error);
}
}Step 4: Create a Server Session (Recommended)
After authentication, create a server-validated session token via the relayer. This gives you a token that your backend can verify independently.
const auth = createCrossOriginAuth();
async function signInWithServerSession() {
// Full flow: authenticate + create server-validated session
const { session, serverSession } = await auth.authenticateAndCreateSession({
permissions: ['read', 'transfer'],
expiresInMs: 3600000, // 1 hour
});
console.log('Vault address:', session.address);
console.log('Server session ID:', serverSession.id);
console.log('Expires at:', new Date(serverSession.expiresAt));
// Store the session ID for subsequent API calls
localStorage.setItem('veridex_session_id', serverSession.id);
}You can validate this session from your backend at any time:
curl https://relayer.veridex.network/api/v1/session/ses_abc123How It Works
Architecture
The User Experience
- User clicks "Connect with Veridex" on your site.
- If ROR is supported: The browser's native passkey dialog appears immediately. User verifies with biometrics. Done.
- If ROR is not supported:
- A popup opens to
auth.veridex.network. - User sees a clean "Sign in to [Your App]" card (your app name from registration).
- User verifies with their passkey.
- Popup closes and returns the session to your app.
- A popup opens to
What You Get Back
After authentication, you receive a CrossOriginSession containing:
| Field | Type | Description |
|---|---|---|
address | string | User's vault address (same on all EVM chains) |
sessionPublicKey | string | Session key for signing transactions |
expiresAt | number | Session expiry timestamp (ms) |
signature | WebAuthnSignature | Proof of passkey ownership |
credential | PasskeyCredential | The credential used (keyHash, publicKey) |
serverSessionId | string? | Server-validated session ID (if created) |
Configuration Options
createCrossOriginAuth
import { createCrossOriginAuth } from '@veridex/sdk';
const auth = createCrossOriginAuth({
// The canonical Relying Party ID. Must be 'veridex.network' for cross-domain.
rpId: 'veridex.network', // default
// Auth Portal URL for popup/redirect flow.
authPortalUrl: 'https://auth.veridex.network', // default
// Relayer API URL for server-side session tokens.
relayerUrl: 'https://relayer.veridex.network/api/v1', // default
// Authentication mode: 'popup' (default) or 'redirect'.
// Use 'redirect' for mobile browsers where popups might be blocked.
mode: 'popup',
// URL to redirect back to (only for mode: 'redirect').
redirectUri: 'https://myapp.com/callback',
// Timeout for auth operations (ms). Default: 120000 (2 minutes).
timeout: 120000,
});Redirect Mode
If using mode: 'redirect', handle the callback on your redirectUri page:
// pages/callback.tsx
import { createCrossOriginAuth } from '@veridex/sdk';
import { useEffect } from 'react';
export default function Callback() {
useEffect(() => {
const auth = createCrossOriginAuth();
const session = auth.completeRedirectAuth();
if (session) {
localStorage.setItem('veridex_session', JSON.stringify(session));
window.location.href = '/dashboard';
}
}, []);
return <div>Completing sign in...</div>;
}Use mode: 'redirect' for mobile web apps. Many mobile browsers block popups by default.
Server-Side Session Tokens
Server session tokens let your backend verify a user's identity without trusting the client.
Create a Session
const serverSession = await auth.createServerSession(session, {
permissions: ['read', 'transfer'],
expiresInMs: 3600000, // 1 hour
});
// serverSession.id = "ses_abc123..."Validate a Session (from your backend)
curl https://relayer.veridex.network/api/v1/session/ses_abc123{
"valid": true,
"session": {
"id": "ses_abc123",
"keyHash": "0x1234...",
"appOrigin": "https://myapp.com",
"permissions": ["read", "transfer"],
"expiresAt": 1738900000000
}
}Revoke a Session
await auth.revokeServerSession('ses_abc123');Or via API:
curl -X DELETE https://relayer.veridex.network/api/v1/session/ses_abc123Managing Your App
Check Registration Status
Verify your origin is registered and active:
curl "https://relayer.veridex.network/api/v1/origins/validate?origin=https://myapp.com"{
"allowed": true,
"reason": "registered",
"origin": "https://myapp.com"
}Get App Details
curl https://relayer.veridex.network/api/v1/apps/app_a1b2c3d4e5f6 \
-H "X-API-Key: vdx_sk_..."Delete Your App
curl -X DELETE https://relayer.veridex.network/api/v1/apps/app_a1b2c3d4e5f6 \
-H "X-API-Key: vdx_sk_..."Deleting your app will immediately revoke all active user sessions for your origin.
Hosting .well-known/webauthn
If you want to enable the seamless ROR flow (no popup) on your domain, you can optionally host a .well-known/webauthn file. This is not required — the Auth Portal fallback works without it — but it provides the best user experience.
File Contents
Create public/.well-known/webauthn in your project:
{
"origins": [
"https://veridex.network",
"https://auth.veridex.network",
"https://myapp.com"
]
}Serving Requirements
The file must be served with these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
Access-Control-Allow-Origin | * |
Netlify (netlify.toml):
[[headers]]
for = "/.well-known/webauthn"
[headers.values]
Content-Type = "application/json"
Access-Control-Allow-Origin = "*"Vercel (vercel.json):
{
"headers": [
{
"source": "/.well-known/webauthn",
"headers": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
}
]
}Next.js (middleware or API route):
// app/.well-known/webauthn/route.ts
export async function GET() {
return Response.json(
{ origins: ['https://veridex.network', 'https://auth.veridex.network', 'https://myapp.com'] },
{ headers: { 'Access-Control-Allow-Origin': '*' } }
);
}The canonical .well-known/webauthn file at veridex.network is managed by the Veridex team. Your origin is automatically added when you register via the Developer Portal. You only need to host this file on your own domain if you want ROR to work in both directions.
Chrome eTLD+1 Limit
Chrome currently limits .well-known/webauthn to 5 unique eTLD+1 labels. This means only 5 different top-level domains can be listed (e.g., example.com, myapp.io, game.xyz = 3 labels). Subdomains of the same eTLD+1 share one label.
What This Means for You
- All
*.veridex.networksubdomains count as 1 label. - If the canonical file at
veridex.networkalready has 5 eTLD+1 labels, additional third-party domains will use the Auth Portal fallback instead of ROR. - The Auth Portal provides the same functionality — just with a popup instead of a native dialog.
Mitigation Strategies
| Strategy | Description |
|---|---|
| Auth Portal Fallback | Automatic. Origins beyond the 5-label limit use the popup flow. |
| Subdomain Proxying | High-value partners can be assigned partner.veridex.network subdomains (doesn't count against the limit). |
| Priority Rotation | The .well-known/webauthn file prioritizes the most active eTLD+1 labels by usage. |
This limit is a Chrome implementation detail, not a spec requirement. As the WebAuthn spec matures, this limit is expected to increase. The Auth Portal fallback ensures your integration works regardless.
React Integration Example
import { useState } from 'react';
import { createCrossOriginAuth } from '@veridex/sdk';
const auth = createCrossOriginAuth();
export function ConnectVeridex() {
const [session, setSession] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleConnect = async () => {
setLoading(true);
setError(null);
try {
const { session, serverSession } = await auth.authenticateAndCreateSession({
permissions: ['read', 'transfer'],
});
setSession({ ...session, serverSessionId: serverSession.id });
} catch (err) {
setError(err instanceof Error ? err.message : 'Connection failed');
} finally {
setLoading(false);
}
};
const handleDisconnect = async () => {
if (session?.serverSessionId) {
await auth.revokeServerSession(session.serverSessionId);
}
setSession(null);
};
if (session) {
return (
<div>
<p>Connected: {session.address.slice(0, 6)}...{session.address.slice(-4)}</p>
<button onClick={handleDisconnect}>Disconnect</button>
</div>
);
}
return (
<div>
<button onClick={handleConnect} disabled={loading}>
{loading ? 'Connecting...' : 'Connect with Veridex'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
}Security Best Practices
- Register your production domain. Only registered origins can receive sessions from the Auth Portal. Unregistered origins are blocked.
- Use server-side session tokens. Don't trust client-side session data alone. Create a server session and validate it from your backend.
- Always use HTTPS. WebAuthn requires a secure context. The only exception is
localhostfor development. - Validate sessions on your backend. Call
GET /api/v1/session/:idto verify a session is valid before granting access to sensitive operations. - Revoke sessions on logout. Call
DELETE /api/v1/session/:idwhen the user logs out to prevent session reuse. - Keep your API key secret. Your API key (
vdx_sk_...) is used to manage your app registration. Never expose it in client-side code.
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| "Origin not authorized" error | Your origin isn't registered | Register at developers.veridex.network (opens in a new tab) |
| Popup blocked | Browser blocking popups | Use mode: 'redirect' instead, or ask users to allow popups |
| ROR not working | Browser doesn't support Related Origin Requests | The SDK automatically falls back to Auth Portal. No action needed. |
| CORS error on relayer | Origin not in CORS allowlist | Registered origins are automatically added. Check your registration status. |
| Session expired | Server session token expired | Create a new session with authenticateAndCreateSession() |
| "Credential not found" on session create | User hasn't registered a passkey yet | User must create a passkey at veridex.network first |
API Reference
See the full Relayer API Reference for all cross-domain endpoints:
POST /api/v1/apps/register— Register your appGET /api/v1/apps— List registered appsGET /api/v1/origins/validate— Check if an origin is allowedPOST /api/v1/session/create— Create a server sessionGET /api/v1/session/:id— Validate a sessionDELETE /api/v1/session/:id— Revoke a session