5-Minute Integration
Minimal end-to-end passwordless login
What you'll build
- A browser login flow driven by Hawcx steps
- A backend exchange endpoint using the Hawcx backend SDK
- An authenticated session in your app
Flow overview
Prerequisites
From your Hawcx Dashboard, grab:
- Config ID — your client identifier
You’ll set up a small backend exchange endpoint in Backend — Exchange Code.
Step 1: Install
Frontend SDK
Choose React or Vanilla JS:
npm install @hawcx/reactnpm install @hawcx/coreBackend SDK
Choose your backend language:
npm install @hawcx/oauth-clientpip install hawcx-oauth-clientStep 2: Configure Environment
Set these before writing any code. You need both — frontend talks to Hawcx, backend verifies the result.
Frontend — create .env in your app root:
VITE_HAWCX_CONFIG_ID=your_config_idStep 3: Frontend — Minimal Login
By default, start(email) auto-detects whether the user is new or returning.
src/App.tsx
import { HawcxProvider } from '@hawcx/react';
import { LoginFlow } from './LoginFlow';
export function App() {
return (
<HawcxProvider config={{
configId: import.meta.env.VITE_HAWCX_CONFIG_ID
}}>
<LoginFlow />
</HawcxProvider>
);
}src/LoginFlow.tsx
import { useState } from 'react';
import { useFlowState, useFlowActions } from '@hawcx/react';
export function LoginFlow() {
const state = useFlowState();
const { start, selectMethod, submitCode, reset } = useFlowActions();
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
if (state.status === 'idle') {
return (
<form onSubmit={(e) => { e.preventDefault(); start(email); }}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Continue</button>
</form>
);
}
if (state.status === 'loading') return <div>Loading...</div>;
if (state.status === 'error') {
return (
<div>
<p>Error: {state.error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
);
}
if (state.status === 'completed') {
fetch('/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
authCode: state.authCode,
codeVerifier: state.codeVerifier
})
}).then(() => window.location.href = '/');
return <div>Success! Redirecting...</div>;
}
if (state.status === 'prompt') {
const { prompt } = state;
if (prompt.type === 'select_method') {
return (
<div>
<h3>Choose method</h3>
{prompt.methods.map((m) => (
<button key={m.name} onClick={() => selectMethod(m.name)}>
{m.label}
</button>
))}
</div>
);
}
if (prompt.type === 'enter_code') {
return (
<form onSubmit={(e) => { e.preventDefault(); submitCode(code); }}>
<p>Code sent to {prompt.destination}</p>
<input
value={code}
onChange={(e) => setCode(e.target.value)}
maxLength={prompt.codeLength}
/>
<button type="submit">Verify</button>
</form>
);
}
return <div>Step: {prompt.type}</div>;
}
return null;
}src/main.ts (replace placeholders with your env values)
import { createHawcxClient } from '@hawcx/core';
// Replace with your actual values from Step 2
const client = createHawcxClient({
configId: 'your_config_id'
});
// Subscribe to state changes
client.onStateChange(renderUI);
// Start login
function login(email: string) {
client.start(email);
}
function renderUI(state) {
const app = document.getElementById('app')!;
if (state.status === 'idle') {
app.innerHTML = `
<input type="email" id="email" placeholder="Enter your email">
<button onclick="login(document.getElementById('email').value)">Continue</button>
`;
return;
}
if (state.status === 'loading') {
app.innerHTML = '<div>Loading...</div>';
return;
}
if (state.status === 'error') {
app.innerHTML = `
<p>Error: ${state.error.message}</p>
<button onclick="client.reset()">Try Again</button>
`;
return;
}
if (state.status === 'completed') {
fetch('/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
authCode: state.authCode,
codeVerifier: state.codeVerifier
})
}).then(() => window.location.href = '/');
app.innerHTML = '<div>Success! Redirecting...</div>';
return;
}
if (state.status === 'prompt') {
const { prompt } = state;
if (prompt.type === 'select_method') {
app.innerHTML = `
<h3>Choose verification method</h3>
${prompt.methods.map(m =>
`<button onclick="client.selectMethod('${m.name}')">${m.label}</button>`
).join('')}
`;
return;
}
if (prompt.type === 'enter_code') {
app.innerHTML = `
<p>Code sent to ${prompt.destination}</p>
<input id="code" maxlength="${prompt.codeLength}" placeholder="Enter code">
<button onclick="client.submitCode(document.getElementById('code').value)">Verify</button>
`;
return;
}
app.innerHTML = `<div>Step: ${prompt.type}</div>`;
}
}
// Make login available globally for onclick
(window as any).login = login;
(window as any).client = client;Step 4: Backend — Exchange Code
When the frontend completes, it sends authCode + codeVerifier. Exchange them for verified user claims in your backend using the Hawcx backend SDK. The route itself is yours to implement.
Backend — create .env in your server root:
# Config ID (public identifier)
HAWCX_API_KEY=your_config_id
# Optional (backend-only): credential blob for MFA / step-up management
# HAWCX_SECRET_KEY=hwx_sk_v1_...Only the backend ever sees secrets. HAWCX_SECRET_KEY is used for delegation/step-up calls and is not required for the code exchange in this quickstart.
src/routes/auth.ts
import express from 'express';
import { HawcxOAuth } from '@hawcx/oauth-client';
const router = express.Router();
const oauth = new HawcxOAuth({
configId: process.env.HAWCX_API_KEY!
});
// Your backend route (Hawcx does not provide this endpoint)
router.post('/exchange', async (req, res) => {
try {
const { authCode, codeVerifier } = req.body;
const { claims } = await oauth.exchangeCode(authCode, codeVerifier);
// claims.sub = user ID, claims.email = verified email
// Create your own session here
res.json({ success: true, userId: claims.sub, email: claims.email });
} catch (error) {
console.error('Auth failed:', error);
res.status(401).json({ error: 'Authentication failed' });
}
});
export default router;routes/auth.py
from flask import Blueprint, request, jsonify
from hawcx_oauth_client import HawcxOAuth
import os
auth_bp = Blueprint('auth', __name__)
oauth = HawcxOAuth(
config_id=os.getenv('HAWCX_API_KEY')
)
@auth_bp.route('/exchange', methods=['POST'])
def auth():
try:
data = request.json
result = oauth.exchange_code(data['authCode'], data['codeVerifier'])
# result.claims['sub'] = user ID, result.claims['email'] = verified email
# Create your own session here
return jsonify({'success': True, 'userId': result.claims['sub']})
except Exception as e:
print(f'Auth failed: {e}')
return jsonify({'error': 'Authentication failed'}), 401Optional (advanced): If you need backend-driven MFA setup or step-up verification, initialize the delegation client with HAWCX_SECRET_KEY (a credential blob from the dashboard). This is separate from the exchange above.
Step 5: Run & Verify
- Start your backend server.
- Start your frontend app.
- Enter email → select method → enter code.
- Confirm the backend receives
authCode+codeVerifierand returns a success response. - You should land on your homepage.
Next Steps
- Web SDK Quickstart — Full step handling
- API Reference — All methods, types, and step types
- Troubleshooting — Common issues