Authentication Flow
- User sees a Circuit login interface embedded in your app
- User authenticates using their passkey (fingerprint, face ID, etc.)
- Circuit sends your app an API auth token via a secure message
- Your app receives the token and can now call Circuit’s API
- User is logged in with no redirects or popups

Implementation Guide
- HTML/JS Example
- React Example
<!DOCTYPE html>
<html>
<head>
<title>App + Circuit</title>
</head>
<body>
<h1>Welcome to App + Circuit</h1>
<!-- Status display -->
<div id="auth-status">Click the button below to authenticate...</div>
<!-- Circuit authentication iframe -->
<iframe
id="circuit-iframe"
src="https://app.circuit.org/embed/auth/basic"
width="400"
height="500"
style="border: none; border-radius: 12px; display: block; margin: 20px auto;">
</iframe>
<script>
// This will store the API auth token later on receipt
let circuitApiAuthToken = null;
// Listen for messages from the Circuit iframe
window.addEventListener('message', function(event) {
// SECURITY: Only accept messages from Circuit's domain
if (event.origin !== 'https://app.circuit.org') return;
const messageType = event.data.type;
const messagePayload = event.data.payload;
// When Circuit iframe finishes loading and is ready
if (messageType === 'ready') {
document.getElementById('auth-status').textContent = 'Please authenticate';
// Configure the iframe
document.getElementById('circuit-iframe').contentWindow.postMessage({
type: 'configure',
config: {
backgroundColor: '#ffffff',
buttonColor: '#007bff',
buttonTextColor: '#ffffff',
title: 'Login to App + Circuit'
},
actionType: 'signIn', // 'register' to register new user
username: 'username', // Replace with actual username
}, 'https://app.circuit.org');
}
// Getting the API auth token
if (messageType === 'signInResult' || messageType === 'registerResult') {
if (messagePayload.success) {
// SUCCESS! Get the API auth token
circuitApiAuthToken = messagePayload.sessionPermit;
// Store it for later use
localStorage.setItem('circuitApiAuthToken', circuitApiAuthToken);
// Update the UI
document.getElementById('auth-status').textContent = 'Authentication successful';
// Now you can call Circuit API
} else {
// Authentication failed
const errorMessage = messagePayload.error;
document.getElementById('auth-status').textContent = 'Authentication Failed: ' + errorMessage;
// Handle user not found error
if (errorMessage.includes('No matching passkey found')) {
// Try registration instead
document.getElementById('circuit-iframe').contentWindow.postMessage({
type: 'configure',
actionType: 'register',
username: 'username',
}, 'https://app.circuit.org');
}
}
}
});
</script>
</body>
</html>
import { useEffect, useRef, useState } from 'react';
export default function App() {
return (
<div>
<h1>My App</h1>
<CircuitAuth username="testuser" /> // Replace with actual username
</div>
);
}
function CircuitAuth({ username }: { username: string }) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [status, setStatus] = useState('Loading...');
const [tryingRegister, setTryingRegister] = useState(false);
const configure = (actionType: 'signIn' | 'register') => {
// Configure the iframe
iframeRef.current?.contentWindow?.postMessage({
type: 'configure',
config: {
backgroundColor: '#ffffff',
buttonColor: '#007bff',
buttonTextColor: '#ffffff',
title: 'Login to App + Circuit'
},
actionType: actionType,
username: username,
}, 'https://app.circuit.org');
};
useEffect(() => {
// Listen for messages from the Circuit iframe
const handleMessage = (event: MessageEvent) => {
// SECURITY: Only accept messages from Circuit's domain
if (event.origin !== 'https://app.circuit.org') return;
const messageType = event.data.type;
const messagePayload = event.data.payload;
// When Circuit iframe finishes loading and is ready
if (messageType === 'ready') {
setStatus('Please authenticate');
configure('signIn');
}
// Getting the API auth token
if (messageType === 'signInResult' || messageType === 'registerResult') {
if (messagePayload.success) {
// SUCCESS! Get the API auth token
const circuitApiAuthToken = messagePayload.sessionPermit;
// Store API auth token for later use
localStorage.setItem('circuitApiAuthToken', circuitApiAuthToken);
// Update the UI
setStatus('Authentication successful');
// Now you can call Circuit API
} else {
// Authentication failed
const errorMessage = messagePayload.error;
setStatus('Authentication failed: ' + errorMessage);
// Handle user not found error
if (errorMessage.includes("No matching passkey found") && !tryingRegister) {
// Try registration instead
setStatus("User not found, trying registration...");
setTryingRegister(true);
configure('register');
}
}
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [username, tryingRegister]);
return (
<div>
{/* Status display */}
<p>{status}</p>
{/* Circuit authentication iframe */}
<iframe
ref={iframeRef}
src="https://app.circuit.org/embed/auth/basic"
width="400"
height="500"
style={{ border: 'none', borderRadius: '12px' }}
/>
</div>
);
}
iframe Message Communication Protocol
| Direction | Message Type | When Sent | Payload |
|---|---|---|---|
| Circuit → App | ready | iframe loaded | { version: '1.0.0' } |
| App → Circuit | configure | After receiving ready | json { actionType, username, config? } |
| Circuit → App | signInResult | Sign-in completed | json { success: boolean, sessionPermit?: string, error?: string } |
| Circuit → App | registerResult | Registration completed | json { success: boolean, sessionPermit?: string, error?: string } |
Authentication Requirements
| Action Type | Required Fields | Optional Fields |
|---|---|---|
signIn | username (must exist) | config |
register | username (must be unique) | config |
iframe Structure
<iframe
src="https://app.circuit.org/embed/auth/basic"
width="400"
height="500"
style="border: none; border-radius: 12px;">
</iframe>
Configuration Options
| Property | Type | Description | Example |
|---|---|---|---|
title | string | Main heading text | 'Welcome to My App' |
subtext | string | Subtitle text | 'Sign in to continue' |
ctaText | string | Button text | 'Continue' |
backgroundColor | string | Background color | '#ffffff' |
buttonColor | string | Button background color | '#007bff' |
buttonTextColor | string | Button text color | '#ffffff' |
titleColor | string | Title text color | '#333333' |
subtextColor | string | Subtitle text color | '#666666' |
radius | number | Border radius in pixels | 12 |
fontFamily | string | Font family | 'sans-serif' |
fontSize | string | Font size | '14px' |
Error Codes And Recovery
| Error Message | What it Means | Recommended Action |
|---|---|---|
"No matching passkey found" | User doesn’t exist in Circuit | User typo or registration |
"Username is already taken" | Registration failed - username exists | Ask user for different username |
"Authentication verification failed" | Passkey verification failed | Ask user to try again |