Web SDK Quickstart
Get started with Hawcx passwordless authentication for web
Add passwordless authentication to your web app. The SDK handles device registration, login, and MFA - then sends the authorization code to your backend for validation.
Installation
Add the Hawcx SDK script to your HTML page:
<script src="https://drprpwzor5vmy.cloudfront.net/v1.1.58/hawcx-auth.window.min.js" defer></script>Then in your JavaScript, initialize it:
const hawcx = await window.hawcx.init('YOUR_API_KEY', "your-base-url");
if (hawcx.status !== 'INITIALIZED') {
console.error('Failed to initialize');
}Setup
Step 1: Initialize
See Installation section above. The SDK will be available as window.hawcx after initialization.
Step 2: Start Authentication
async function handleLogin(email) {
const response = await hawcx.authenticate(email);
if (response.status === 'SUCCESS') {
// Trusted device - send code to backend
await sendToBackend(email, response.code);
} else if (response.status === 'OTP_NEEDED') {
// New device - show registration flow
showRegistrationFlow(email);
} else if (response.status === 'MFA_REQUIRED') {
// Existing device with MFA - show MFA input
showMfaScreen(email, response.sessionId);
}
}Step 3: Handle Registration (New Device)
async function showRegistrationFlow(email) {
// Step 1: Verify email OTP
const emailOtp = prompt('Enter OTP from email:');
await hawcx.verifyEmailOtp({
userid: email,
otp: emailOtp
});
// Step 2: Complete registration with MFA
const result = await hawcx.verifyDevice({
userid: email,
mfaMethod: 'sms' // 'email', 'sms', or 'totp'
});
if (result.status === 'SUCCESS' || result.status === 'DEVICE_REGISTERED' || result.code) {
// Send authorization code to backend
await sendToBackend(email, result.code);
}
}Step 4: Handle MFA
async function showMfaScreen(email, sessionId) {
// Start MFA challenge
await hawcx.initiateMfa({
userid: email,
sessionId
});
// Get OTP from user
const otp = prompt('Enter verification code:');
// Verify MFA
const result = await hawcx.verifyMfa({
userid: email,
otp: otp,
remember_me: true // Optional: skip MFA on this device next time
});
if (result.status === 'SUCCESS') {
// Send authorization code to backend
await sendToBackend(email, result.code);
}
}Step 5: Backend Validation
Your backend receives the authorization code and validates it:
import { exchangeCodeForClaims } from '@hawcx/oauth-client';
app.post('/api/login', async (req, res) => {
try {
const { code, email } = req.body;
// Validate authorization code
const claims = await exchangeCodeForClaims({
code,
oauthTokenUrl: process.env.OAUTH_TOKEN_ENDPOINT,
clientId: process.env.OAUTH_CLIENT_ID,
publicKey: process.env.OAUTH_PUBLIC_KEY,
apiKey: process.env.HAWCX_API_KEY,
code_verifier: req.body.code_verifier // PKCE if used
});
// Create session for user
const sessionToken = jwt.sign(
{ userId: claims.sub, email: claims.email },
process.env.SESSION_SECRET,
{ expiresIn: '7d' }
);
res.json({ success: true, sessionToken });
} catch (error) {
res.status(401).json({ error: 'Authentication failed' });
}
});from hawcx_oauth_client import exchange_code_for_claims
import os, jwt
from datetime import datetime, timedelta
@app.route('/api/login', methods=['POST'])
def login():
try:
data = request.get_json()
code = data.get('code')
email = data.get('email')
# Validate authorization code
claims = exchange_code_for_claims(
code=code,
oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
client_id=os.getenv('OAUTH_CLIENT_ID'),
public_key=os.getenv('OAUTH_PUBLIC_KEY'),
api_key=os.getenv('HAWCX_API_KEY'),
code_verifier=data.get('code_verifier') # PKCE if used
)
# Create session for user
session_token = jwt.encode(
{
'userId': claims['sub'],
'email': claims['email'],
'exp': datetime.utcnow() + timedelta(days=7)
},
os.getenv('SESSION_SECRET'),
algorithm='HS256'
)
return jsonify({'success': True, 'sessionToken': session_token})
except Exception as error:
return jsonify({'error': 'Authentication failed'}), 401Step 6: Frontend Handles Response
async function sendToBackend(email, authCode) {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code: authCode,
email: email,
code_verifier: retrievePkceVerifier(email) // Optional: PKCE
})
});
if (response.ok) {
const { sessionToken } = await response.json();
localStorage.setItem('sessionToken', sessionToken);
// Redirect to app or update UI
window.location.href = '/app';
} else {
alert('Authentication failed');
}
}Authentication Flows
New Device (First Time)
authenticate() → OTP_NEEDED → verifyEmailOtp() → verifyDevice() →
SUCCESS → Send code to backend → Logged in ✓Trusted Device (No MFA)
authenticate() → SUCCESS → Send code to backend → Logged in ✓Device with MFA Enabled
authenticate() → MFA_REQUIRED → initiateMfa() → verifyMfa() →
SUCCESS → Send code to backend → Logged in ✓Full Example
<!DOCTYPE html>
<html>
<head>
<title>Hawcx Login</title>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 50px auto; }
input { width: 100%; padding: 8px; margin: 10px 0; box-sizing: border-box; }
button { width: 100%; padding: 10px; background: #0076e0; color: white; border: none; cursor: pointer; }
</style>
</head>
<body>
<h1>Login</h1>
<input type="email" id="email" placeholder="Email">
<button onclick="handleLogin()">Sign In</button>
<div id="status"></div>
<script type="module">
import { HawcxInitializer } from 'https://drprpwzor5vmy.cloudfront.net/v1.1.55/hawcx-auth.esm.min.js';
let hawcx;
let currentEmail;
// Initialize
async function init() {
hawcx = await HawcxInitializer.init('YOUR_API_KEY', "your-base-url");
document.getElementById('status').textContent = 'Ready';
}
// Handle login
window.handleLogin = async function() {
currentEmail = document.getElementById('email').value;
if (!currentEmail) return alert('Enter email');
try {
const response = await hawcx.authenticate(currentEmail);
if (response.status === 'SUCCESS') {
// Trusted device
await exchangeCode(currentEmail, response.code);
} else if (response.status === 'OTP_NEEDED') {
// New device - get email OTP
document.getElementById('status').textContent = 'Check email for OTP';
const emailOtp = prompt('Email OTP:');
await hawcx.verifyEmailOtp({ userid: currentEmail, otp: emailOtp });
// Complete registration
const result = await hawcx.verifyDevice({
userid: currentEmail,
mfaMethod: 'sms' // 'email', 'sms', or 'totp'
});
if (result.code || result.status === 'SUCCESS' || result.status === 'DEVICE_REGISTERED') {
await exchangeCode(currentEmail, result.code);
}
} else if (response.status === 'MFA_REQUIRED') {
// MFA required
await hawcx.initiateMfa({ userid: currentEmail, sessionId: response.sessionId });
const mfaOtp = prompt('MFA OTP:');
const mfaResult = await hawcx.verifyMfa({
userid: currentEmail,
otp: mfaOtp,
remember_me: true
});
if (mfaResult.code) {
await exchangeCode(currentEmail, mfaResult.code);
}
}
} catch (error) {
document.getElementById('status').textContent = 'Error: ' + error.message;
}
};
// Exchange code with backend
async function exchangeCode(email, code) {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, email })
});
if (res.ok) {
const { sessionToken } = await res.json();
localStorage.setItem('sessionToken', sessionToken);
window.location.href = '/app';
} else {
document.getElementById('status').textContent = 'Backend auth failed';
}
}
init();
</script>
</body>
</html>Environment Setup
Your backend needs these from the Hawcx dashboard:
OAUTH_TOKEN_ENDPOINT=https://example.hawcx.com/token
OAUTH_CLIENT_ID=your_client_id
OAUTH_PUBLIC_KEY=-----BEGIN PUBLIC KEY-----\n...
# Your app secret
SESSION_SECRET=your_secret_keyKey Points
- Always validate on backend - The authorization code must be exchanged by your backend
- Handle all response statuses - Different statuses require different UI flows
- Store tokens securely - Use httpOnly cookies or secure storage
- Use HTTPS - Never send authentication over HTTP
- PKCE optional - For extra security, use PKCE for code verification
Next Steps
- Full API Reference - All methods and status codes
- Backend SDKs - Validate codes in your backend
- Community Support - Get help on Slack