# Hawcx Authentication SDK — Complete Reference for LLMs > This file contains everything an LLM needs to help a developer integrate Hawcx passwordless authentication. It is designed to be consumed by AI coding assistants (Claude Code, Cursor, GitHub Copilot, etc.) so they can generate working integration code without visiting the docs website. --- ## What is Hawcx? Hawcx is a passwordless authentication platform. It replaces passwords and passkeys with device-bound, zero-knowledge authentication. Key properties: - **No passwords, no passkeys** — authentication uses short-lived, per-session key material - **Device-bound** — credentials never leave the device, never sync to cloud - **Zero-knowledge proofs** — the server receives only a proof, never the secret itself - **Quantum-resilient** — uses ephemeral symmetric keys, not long-lived public keys - **Server-driven flow** — the backend tells the frontend what step to render next (state machine) The standard integration has two pieces: 1. **Frontend SDK** — collects user email, renders auth steps, returns `authCode` + `codeVerifier` on completion 2. **Backend SDK** — exchanges `authCode` + `codeVerifier` for verified user claims (`sub`, `email`), then you create your own session --- ## Quick Reference: Packages & Install Commands | Platform | Package | Install | |----------|---------|---------| | Web (React) | `@hawcx/react` | `npm install @hawcx/react` | | Web (Vanilla JS) | `@hawcx/core` | `npm install @hawcx/core` | | Node.js Backend | `@hawcx/oauth-client` | `npm install @hawcx/oauth-client` | | Python Backend | `hawcx-oauth-client` | `pip install hawcx-oauth-client` | | Android (Native) | `com.hawcx:hawcx-android-sdk` | Gradle: `implementation 'com.hawcx:hawcx-android-sdk:4.0.0'` | | iOS (Native) | `HawcxFramework` | SPM: `https://github.com/hawcx/hawcx_ios_sdk` (from 4.0.0) | | React Native | `@hawcx/react-native-sdk` | `npm install @hawcx/react-native-sdk@1.0.7` | | Flutter | `hawcx_flutter_sdk` | pubspec.yaml: `hawcx_flutter_sdk: ^1.0.2` | --- ## Environment Variables ### Frontend ```bash VITE_HAWCX_CONFIG_ID=your_config_id # For Vite-based apps # or pass configId directly in code ``` ### Backend (OAuth Code Exchange) ```bash HAWCX_CONFIG_ID=your_config_id # Public identifier from dashboard HAWCX_BASE_URL=https://api.hawcx.com # Optional, has default ``` ### Backend (Delegation / MFA Management) — Optional ```bash HAWCX_SECRET_KEY=hwx_sk_v1_... # Credential blob from dashboard ``` ### Mobile Backend Verification (React Native / Flutter) ```bash HAWCX_OAUTH_CLIENT_ID=-demo.hawcx.com HAWCX_OAUTH_TOKEN_ENDPOINT=https://-api.hawcx.com/oauth2/token HAWCX_OAUTH_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----" HAWCX_OAUTH_ISSUER=https://-api.hawcx.com ``` --- ## Auth Flow Overview (Sequence) ``` Browser/App → Hawcx: start(email) Hawcx → Browser/App: steps (select_method, enter_code, etc.) Browser/App → Hawcx: submit user input Hawcx → Browser/App: completed(authCode, codeVerifier) Browser/App → Your Backend: POST /exchange { authCode, codeVerifier } Your Backend → Hawcx: exchangeCode(authCode, codeVerifier) Hawcx → Your Backend: { claims: { sub, email } } Your Backend → Browser/App: session token / cookie ``` --- ## Architecture — How It Works ### System Layers Hawcx splits responsibility across four layers: | Layer | What lives here | What it never sees | |-------|----------------|-------------------| | **Device** | Private keys, biometric binding | — | | **SDK (Browser/App)** | State machine, UI steps, PKCE challenge | Private keys (handled by device APIs) | | **Your Backend** | OAuth client, session logic | Private keys, raw proofs | | **Hawcx Cloud** | Proof verification, challenge issuance | Private keys, session tokens | Key principle: **Secrets never leave the device. Your backend never sees them either.** ### Authentication Sequence (Detailed) ``` 1. User enters email → SDK calls start(email) 2. SDK generates PKCE pair (codeVerifier + codeChallenge) 3. SDK → Hawcx Cloud: initiate auth (email, codeChallenge) 4. Hawcx Cloud → SDK: challenge + step instructions 5. [ON DEVICE] SDK requests device-bound key from secure storage 6. [ON DEVICE] Device returns ephemeral key pair 7. [ON DEVICE] SDK generates zero-knowledge proof using challenge + key 8. SDK → Hawcx Cloud: submit proof 9. Hawcx Cloud verifies proof (learns nothing about the secret) 10. Hawcx Cloud → SDK: auth code (status: completed) 11. SDK → Your Backend: authCode + codeVerifier 12. Your Backend → Hawcx Cloud: exchange code (OAuth token endpoint) 13. Hawcx Cloud → Your Backend: verified claims (sub, email) 14. Your Backend creates session (cookie, JWT, etc.) ``` Steps 5–7 happen entirely on-device. The proof is zero-knowledge — Hawcx Cloud learns that the user controls the device, nothing more. PKCE ensures only the originating client can exchange the auth code. ### Device-Bound Keys vs Passkeys | Property | Passkeys | Hawcx | |----------|----------|-------| | Key storage | Cloud-synced across devices | Device-only, hardware-bound | | Transferability | Can be shared, exported | Non-exportable by design | | Key lifetime | Long-lived public key on server | Ephemeral per-session keys | | Quantum exposure | Harvest-now-decrypt-later risk | No long-lived keys to harvest | ### Security Properties | Attack | How Hawcx prevents it | Where | |--------|----------------------|-------| | Phishing | Credentials are device-bound — nothing to type or steal | Device | | Credential stuffing | No passwords or shared secrets exist | Device | | Server breach | Server stores only public verification data, never secrets | Cloud | | Man-in-the-middle | Zero-knowledge proof is bound to the session challenge | SDK ↔ Cloud | | Token interception | PKCE ensures only originating client exchanges the auth code | SDK ↔ Backend | | Replay attacks | Ephemeral keys and nonces make each proof single-use | Device + Cloud | | Harvest-now-decrypt-later | No long-lived public keys — ephemeral keys have no future value | Device | | Session hijacking | Your backend controls session policy (cookies, TTL, rotation) | Your Backend | ### Device-Bound Authentication Credentials are physically inseparable from the device. Each device generates cryptographic material inside secure hardware (Secure Enclave, TEE, WebCrypto) that is: - **Non-exportable** — the OS physically prevents extraction - **Non-syncable** — never uploaded to iCloud, Google, or any cloud service - **Non-transferable** — cannot be copied to another device Multi-device support: users register each device independently. Losing one device doesn't affect others. Revoking one doesn't require re-enrollment elsewhere. This is fundamentally different from passkey sync where all devices share key material through a cloud account. ### Zero-Knowledge Proofs The server receives a **proof** — mathematical evidence the user has the secret — without the secret itself ever being transmitted or stored. Your backend never handles, stores, or transmits user secrets. You receive verified claims (user ID, email) — that's it. A complete database dump of your system reveals zero usable credentials. No password hashing needed, no salt management, no credential table to encrypt. ### Session Security & Replay Prevention Every authentication proof is single-use and unreplayable: 1. Hawcx issues a unique challenge per authentication attempt 2. Device generates a proof bound to that challenge, device identity, and timestamp 3. Challenge is consumed on verification — can never be reused 4. Replayed proofs are rejected (challenge consumed, timestamp expired, device mismatch) Hawcx handles authentication proofs. You own session management (cookies, refresh tokens, revocation). ### Cryptographic Agility Algorithm upgrades happen at the protocol level, not the data level. No user re-enrollment, no migration scripts, no downtime. When NIST deprecates an algorithm or PQC standards are finalized, Hawcx adopts them transparently. This is possible because proofs are ephemeral — no long-lived credentials are tied to a specific algorithm. ### Quantum Resilience "Harvest now, decrypt later" attacks target long-lived asymmetric keys (RSA, ECDSA used in passkeys). Hawcx uses ephemeral per-session key material — created at authentication, used once, discarded immediately. There is no persistent key to harvest. Quantum computers can't break what doesn't exist. | Cryptographic method | Quantum impact | Used in | |---|---|---| | RSA-2048 | Broken by Shor's algorithm | TLS, passkey signatures | | ECDSA P-256 | Broken by Shor's algorithm | Passkeys (WebAuthn) | | Long-lived private keys | Harvestable now, crackable later | Passkeys, PKI | | Ephemeral per-session keys | Not harvestable — nothing persists | Hawcx proofs | ### Passwords vs Passkeys vs Hawcx | | Passwords | Passkeys | Hawcx | |---|---|---|---| | What's stored on server | Password hash | Public key | Nothing | | Cloud sync | Via password managers | Via iCloud/Google | None | | Phishing resistance | None | Resistant (origin-bound) | Impossible (nothing to phish) | | Server breach impact | Hashes exposed | Public keys exposed | Nothing to expose | | Cloud account compromise | Password manager breach | All passkeys accessible | Not applicable | | Quantum threat | Hashes crackable faster | Long-lived keys harvestable | Ephemeral — nothing to harvest | | Developer effort | High (reset flows, hashing, MFA) | Medium-High (WebAuthn) | Low (SDK handles everything) | --- ## Web SDK — Complete API ### Factory: `createHawcxAuth(config)` ```typescript import { createHawcxAuth } from '@hawcx/core'; const client = createHawcxAuth({ configId: 'your_config_id' }); ``` | Property | Type | Required | Description | |----------|------|----------|-------------| | `configId` | `string` | Yes | Your Hawcx Config ID | | `apiBase` | `string` | No | Your tenant API URL | ### State Machine The client maintains an `AuthState` that drives your UI: ```typescript client.onStateChange((state) => { // Render UI based on state.status }); const state = client.getState(); ``` #### Flow States | Status | Shape | Description | |--------|-------|-------------| | `idle` | `{ status: 'idle' }` | Ready to start | | `loading` | `{ status: 'loading' }` | Processing request | | `step` | `{ status: 'step', step: AuthStep }` | Server needs user input | | `completed` | `{ status: 'completed', authCode, codeVerifier, expiresAt }` | Auth successful | | `error` | `{ status: 'error', error: AuthError }` | Something failed | #### Completed State ```typescript interface AuthStateCompleted { status: 'completed'; authCode: string; // Send to backend codeVerifier: string; // Send to backend (PKCE) expiresAt: string; // ISO timestamp — exchange within 60 seconds } ``` #### AuthError ```typescript interface AuthError { code: string; message: string; category: 'retryable' | 'user_action' | 'fatal'; details?: Record; } ``` | Category | Meaning | Recovery | |----------|---------|----------| | `retryable` | Transient error (network, rate limit) | Retry the same action | | `user_action` | User error (wrong code, invalid input) | Show error, let user retry | | `fatal` | Unrecoverable (session expired) | Call `reset()` and start over | ### Step Types When `state.status === 'step'`, render UI based on `state.step.type`: #### `select_method` User chooses an authentication method. ```typescript interface SelectMethodPrompt { type: 'select_method'; methods: Array<{ name: string; label: string; }>; } ``` **Action:** Call `selectMethod(method.name)` #### `enter_code` User enters an OTP code sent via email or SMS. ```typescript interface EnterCodePrompt { type: 'enter_code'; destination: string; // Masked email/phone codeLength: number; // Expected code length } ``` **Action:** Call `submitCode(code)` #### `enter_totp` User enters a code from their authenticator app. ```typescript interface EnterTotpPrompt { type: 'enter_totp'; } ``` **Action:** Call `submitTotp(code)` #### `setup_totp` User sets up TOTP for the first time. ```typescript interface SetupTotpPrompt { type: 'setup_totp'; secret: string; // Manual entry key otpauthUrl: string; // QR code URL } ``` **UI:** Show QR code from `otpauthUrl`, display `secret` for manual entry. **Action:** Call `submitTotp(code)` after user enters code from authenticator app. #### `setup_sms` User enrolls their phone number. ```typescript interface SetupSmsPrompt { type: 'setup_sms'; } ``` **Action:** Call `submitPhone(phoneNumber)` (E.164 format, e.g. `+15551234567`) #### `await_approval` Waiting for external approval (QR scan, push notification). ```typescript interface AwaitApprovalPrompt { type: 'await_approval'; qrData?: string; } ``` **UI:** Show QR code if `qrData` provided, otherwise show "Waiting for approval..." **Action:** None — SDK polls automatically and transitions when approved. #### `redirect` Redirect to external OAuth provider. ```typescript interface RedirectPrompt { type: 'redirect'; url: string; } ``` **Action:** `window.location.href = step.url` ### SDK Methods | Method | When to Call | |--------|--------------| | `start(email, flowType?)` | Begin authentication. `flowType` optional: `'signup'` or `'account_manage'` | | `selectMethod(methodId)` | After `select_method` step | | `submitCode(code)` | After `enter_code` step | | `submitTotp(code)` | After `enter_totp` or `setup_totp` step | | `submitPhone(phone)` | After `setup_sms` step | | `resend()` | Resend OTP code (valid after `enter_code` step) | | `reset()` | Clear state, return to `idle` | | `cancel()` | Cancel the current flow | | `getState()` | Get current state | | `onStateChange(callback)` | Subscribe to state changes. Returns unsubscribe function | If `flowType` is omitted from `start()`, the SDK auto-detects new vs returning users. ### React Hooks ```tsx import { HawcxProvider, useAuthState, useAuthActions, useAuthClient } from '@hawcx/react'; // Wrap your app // In components: const state = useAuthState(); const { start, selectMethod, submitCode, submitTotp, submitPhone, resend, reset, cancel } = useAuthActions(); const client = useAuthClient(); // underlying client instance ``` ### MFA & Step-Up Use `start(email, 'account_manage')` when a signed-in user performs a sensitive action (changing email, exporting data, billing). The flow is identical — same steps, same backend exchange. --- ## Web SDK — Complete React Example ```tsx // src/App.tsx import { HawcxProvider } from '@hawcx/react'; import { LoginFlow } from './LoginFlow'; export function App() { return ( ); } ``` ```tsx // src/LoginFlow.tsx import { useState, useEffect } from 'react'; import { useAuthState, useAuthActions } from '@hawcx/react'; export function LoginFlow() { const state = useAuthState(); const { start, selectMethod, submitCode, submitTotp, submitPhone, reset } = useAuthActions(); const [email, setEmail] = useState(''); const [code, setCode] = useState(''); // Handle completed state with useEffect (not in render) useEffect(() => { 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 = '/'); } }, [state.status]); if (state.status === 'idle') { return (
{ e.preventDefault(); start(email); }}> setEmail(e.target.value)} placeholder="Email" />
); } if (state.status === 'loading') return
Loading...
; if (state.status === 'error') { return (

Error: {state.error.message}

); } if (state.status === 'completed') return
Success! Redirecting...
; if (state.status === 'step') { const { step } = state; if (step.type === 'select_method') { return (

Choose method

{step.methods.map((m) => ( ))}
); } if (step.type === 'enter_code') { return (
{ e.preventDefault(); submitCode(code); }}>

Code sent to {step.destination}

setCode(e.target.value)} maxLength={step.codeLength} />
); } if (step.type === 'enter_totp') { return (
{ e.preventDefault(); submitTotp(code); }}> setCode(e.target.value)} placeholder="Authenticator code" />
); } if (step.type === 'setup_totp') { return (

Set up authenticator

QR Code

Or enter manually: {step.secret}

{ e.preventDefault(); submitTotp(code); }}> setCode(e.target.value)} placeholder="Enter code from app" />
); } if (step.type === 'setup_sms') { const [phone, setPhone] = useState(''); return (
{ e.preventDefault(); submitPhone(phone); }}> setPhone(e.target.value)} placeholder="+1234567890" />
); } if (step.type === 'await_approval') { return (
{step.qrData && QR Code}

Waiting for approval...

); } if (step.type === 'redirect') { window.location.href = step.url; return
Redirecting...
; } return
Step: {step.type}
; } return null; } ``` --- ## Web SDK — Vanilla JS Example ```typescript import { createHawcxAuth } from '@hawcx/core'; const client = createHawcxAuth({ configId: 'your_config_id' }); client.onStateChange(renderUI); function login(email: string) { client.start(email); } function renderUI(state) { const app = document.getElementById('app')!; if (state.status === 'idle') { app.innerHTML = ` `; } else if (state.status === 'loading') { app.innerHTML = '
Loading...
'; } else if (state.status === 'error') { app.innerHTML = `

Error: ${state.error.message}

`; } else 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 = '
Success! Redirecting...
'; } else if (state.status === 'step') { const { step } = state; if (step.type === 'select_method') { app.innerHTML = `

Choose method

${step.methods.map(m => ``).join('')}`; } else if (step.type === 'enter_code') { app.innerHTML = `

Code sent to ${step.destination}

`; } else { app.innerHTML = `
Step: ${step.type}
`; } } } (window as any).login = login; (window as any).client = client; ``` --- ## Node.js Backend SDK — Complete API ### HawcxOAuth ```typescript import { HawcxOAuth } from '@hawcx/oauth-client'; const oauth = new HawcxOAuth({ configId: process.env.HAWCX_CONFIG_ID!, baseUrl: process.env.HAWCX_BASE_URL, // optional, default: https://api.hawcx.com jwksCacheTtl: 60 * 60 * 1000, // optional, default: 1 hour timeout: 10_000 // optional, default: 10000ms }); ``` ### `exchangeCode(authCode, codeVerifier)` ```typescript const { idToken, claims } = await oauth.exchangeCode(authCode, codeVerifier); // claims.sub = user ID // claims.email = verified email ``` Returns: `{ idToken: string, claims: { sub: string, email: string, ... } }` ### `verifyToken(token)` — verify a JWT and return claims ```typescript const claims = await oauth.verifyToken(idToken); ``` ### `clearCache()` — clear JWKS cache ```typescript oauth.clearCache(); ``` ### Error Types ```typescript import { TokenExchangeError, TokenVerificationError } from '@hawcx/oauth-client'; try { const { claims } = await oauth.exchangeCode(authCode, codeVerifier); } catch (error) { if (error instanceof TokenExchangeError) { // Code invalid, expired, or already used. Ask user to re-authenticate. console.error(error.message, error.statusCode); } if (error instanceof TokenVerificationError) { // JWT signature/claims failed. Log incident, re-authenticate. console.error(error.message); } } ``` ### Express.js Integration ```typescript import express from 'express'; import { HawcxOAuth } from '@hawcx/oauth-client'; const app = express(); app.use(express.json()); const oauth = new HawcxOAuth({ configId: process.env.HAWCX_CONFIG_ID! }); app.post('/exchange', async (req, res) => { const { authCode, codeVerifier } = req.body; if (!authCode || !codeVerifier) { return res.status(400).json({ error: 'Missing authCode or codeVerifier' }); } try { const { claims } = await oauth.exchangeCode(authCode, codeVerifier); // Create your own session — DO NOT use idToken as your access token req.session.userId = claims.sub; req.session.email = claims.email; // Or use httpOnly cookies (recommended): // const sessionToken = jwt.sign({ sub: claims.sub, email: claims.email }, SECRET, { expiresIn: '1h' }); // res.cookie('session', sessionToken, { httpOnly: true, secure: true, sameSite: 'strict' }); res.json({ success: true, userId: claims.sub }); } catch (error) { console.error('Authentication failed:', error); res.status(401).json({ error: 'Authentication failed' }); } }); ``` ### Fastify Integration ```typescript import Fastify from 'fastify'; import { HawcxOAuth } from '@hawcx/oauth-client'; const app = Fastify(); const oauth = new HawcxOAuth({ configId: process.env.HAWCX_CONFIG_ID! }); app.post<{ Body: { authCode: string; codeVerifier: string } }>('/exchange', async (request, reply) => { const { authCode, codeVerifier } = request.body; try { const { claims } = await oauth.exchangeCode(authCode, codeVerifier); reply.send({ success: true, userId: claims.sub }); } catch (error) { reply.status(401).send({ error: 'Authentication failed' }); } }); ``` ### Delegation Client (MFA Management) ```typescript import { DelegationClient, MfaMethod } from '@hawcx/oauth-client'; // Option 1: From secret key blob const client = DelegationClient.fromSecretKey({ secretKey: process.env.HAWCX_SECRET_KEY!, baseUrl: 'https://api.hawcx.com', apiKey: process.env.HAWCX_CONFIG_ID }); // Option 2: From individual keys const client = DelegationClient.fromKeys({ spSigningKey: process.env.SP_ED25519_PRIVATE_KEY_PEM!, spEncryptionKey: process.env.SP_X25519_PRIVATE_KEY_PEM!, idpVerifyKey: process.env.IDP_ED25519_PUBLIC_KEY_PEM!, idpEncryptionKey: process.env.IDP_X25519_PUBLIC_KEY_PEM!, spKid: process.env.SP_KEY_ID!, idpKid: process.env.IDP_KEY_ID!, baseUrl: 'https://api.hawcx.com', apiKey: process.env.HAWCX_CONFIG_ID }); // Initiate MFA setup const result = await client.mfa.initiate({ userId: 'user@example.com', mfaMethod: MfaMethod.SMS, // or MfaMethod.EMAIL, MfaMethod.TOTP phoneNumber: '+15551234567' }); // Verify OTP await client.mfa.verify({ userId: 'user@example.com', sessionId: result.session_id, otp: '123456' }); // User management const creds = await client.users.getCredentials('user@example.com'); // Device management const devices = await client.devices.list('user@example.com'); await client.devices.revoke({ userId: 'user@example.com', deviceId: 'h2index' }); ``` --- ## Python Backend SDK — Complete API ### HawcxOAuth ```python from hawcx_oauth_client import HawcxOAuth import os oauth = HawcxOAuth( config_id=os.getenv('HAWCX_CONFIG_ID'), base_url=os.getenv('HAWCX_BASE_URL') # optional ) ``` ### `exchange_code(auth_code, code_verifier)` ```python result = oauth.exchange_code(auth_code, code_verifier) claims = result.claims # claims['sub'] = user ID # claims.get('email') = verified email # result.id_token = raw JWT (do not use as access token) ``` ### `verify_token(token)` ```python claims = oauth.verify_token(id_token) ``` ### Flask Integration ```python from flask import Flask, request, jsonify, session from hawcx_oauth_client import HawcxOAuth import os app = Flask(__name__) app.secret_key = os.getenv('FLASK_SECRET_KEY') oauth = HawcxOAuth(config_id=os.getenv('HAWCX_CONFIG_ID')) @app.route('/exchange', methods=['POST']) def exchange(): data = request.json auth_code = data.get('authCode') code_verifier = data.get('codeVerifier') if not auth_code or not code_verifier: return jsonify({'error': 'Missing authCode or codeVerifier'}), 400 try: result = oauth.exchange_code(auth_code, code_verifier) claims = result.claims session['user_id'] = claims['sub'] session['email'] = claims.get('email') return jsonify({'success': True, 'userId': claims['sub']}) except Exception as error: app.logger.error(f'Authentication failed: {error}') return jsonify({'error': 'Authentication failed'}), 401 ``` ### Django Integration ```python from django.http import JsonResponse from django.views.decorators.http import require_http_methods from hawcx_oauth_client import HawcxOAuth import os oauth = HawcxOAuth(config_id=os.getenv('HAWCX_CONFIG_ID')) @require_http_methods(["POST"]) def exchange(request): auth_code = request.POST.get('authCode') code_verifier = request.POST.get('codeVerifier') if not auth_code or not code_verifier: return JsonResponse({'error': 'Missing authCode or codeVerifier'}, status=400) try: result = oauth.exchange_code(auth_code, code_verifier) claims = result.claims request.session['user_id'] = claims['sub'] return JsonResponse({'success': True, 'userId': claims['sub']}) except Exception: return JsonResponse({'error': 'Authentication failed'}, status=401) ``` ### FastAPI Integration ```python from fastapi import FastAPI from fastapi.responses import JSONResponse from hawcx_oauth_client import HawcxOAuth import os app = FastAPI() oauth = HawcxOAuth(config_id=os.getenv('HAWCX_CONFIG_ID')) @app.post('/exchange') async def exchange(payload: dict): auth_code = payload.get('authCode') code_verifier = payload.get('codeVerifier') if not auth_code or not code_verifier: return JSONResponse({'error': 'Missing authCode or codeVerifier'}, status_code=400) try: result = oauth.exchange_code(auth_code, code_verifier) claims = result.claims return JSONResponse({'success': True, 'userId': claims['sub'], 'email': claims.get('email')}) except Exception: return JSONResponse({'error': 'Authentication failed'}, status_code=401) ``` ### Python Delegation Client (MFA Management) ```python from hawcx_oauth_client.delegation import HawcxDelegationClient, MfaMethod import os client = HawcxDelegationClient.from_keys( sp_signing_key=os.getenv('SP_ED25519_PRIVATE_KEY_PEM'), sp_encryption_key=os.getenv('SP_X25519_PRIVATE_KEY_PEM'), idp_verify_key=os.getenv('IDP_ED25519_PUBLIC_KEY_PEM'), idp_encryption_key=os.getenv('IDP_X25519_PUBLIC_KEY_PEM'), base_url=os.getenv('HAWCX_BASE_URL') or 'https://api.hawcx.com', sp_id=os.getenv('OAUTH_CLIENT_ID') ) # Initiate MFA result = client.initiate_mfa_change( userid='user@example.com', mfa_method=MfaMethod.SMS, phone_number='+15551234567' ) # Verify client.verify_mfa_change( userid='user@example.com', session_id=result['session_id'], otp='123456' ) # Get user credentials creds = client.get_user_credentials('user@example.com') ``` --- ## Android Native SDK (Kotlin) ### Installation ```groovy // build.gradle dependencies { implementation 'com.hawcx:hawcx-android-sdk:4.0.0' } ``` ### Initialize ```kotlin import com.hawcx.internal.HawcxSDK class MainApplication : Application() { companion object { lateinit var hawcxSdkInstance: HawcxSDK private set } override fun onCreate() { super.onCreate() hawcxSdkInstance = HawcxSDK( context = this.applicationContext, projectApiKey = "YOUR_CONFIG_ID" ) } } ``` ### Smart Connect Authentication Smart Connect auto-detects new vs returning users. Single entry point — no separate signup/signin. ```kotlin class AuthManager : AuthV4Callback { private val sdk = MainApplication.hawcxSdkInstance fun smartConnect(email: String) { sdk.authenticateV4(userid = email, callback = this) } fun submitOTP(otp: String) { sdk.submitOtpV4(otp = otp) } override fun onOtpRequired() { // Show OTP input UI } override fun onAuthSuccess(accessToken: String?, refreshToken: String?, isLoginFlow: Boolean) { if (isLoginFlow) { // Fully authenticated — navigate to home } else { // Device registration phase — SDK continues automatically } } override fun onError(errorCode: AuthV4ErrorCode, errorMessage: String) { // Handle error } } ``` ### SDK Methods (Android) - `authenticateV4(userid: String, callback: AuthV4Callback)` — initiate Smart Connect - `submitOtpV4(otp: String)` — submit OTP when required - `cancelV4Auth()` — cancel ongoing authentication ### SDK Callbacks (AuthV4Callback) - `onOtpRequired()` — new user/device needs verification - `onAuthSuccess(accessToken, refreshToken, isLoginFlow)` — authentication succeeded - `onError(errorCode, errorMessage)` — error occurred ### Device Session Management (Android) - `clearSessionTokens(userId)` — standard logout, keeps device trusted (no OTP on next login) - `clearUserData(userId)` — full device removal, user must re-verify with OTP - `getLastLoggedInUser()` — returns last authenticated email (for pre-fill) - `clearLastLoggedInUser()` — clear pre-fill record ### Error Codes (Android AuthV4ErrorCode) | Code | Description | |------|-------------| | `networkError` | Network connectivity issue | | `invalidInput` | Invalid email format | | `keychainSaveFailed` | Failed to save to Keystore | | `clientCryptoError` | Cryptographic operation failed | | `fingerprintError` | Device fingerprint failed | | `authInitFailed` | Auth initialization failed | | `otpVerificationFailed` | Invalid or expired OTP | | `deviceVerificationFailed` | Device registration verification failed | | `missingCryptoForLogin` | Device keys not found | --- ## iOS Native SDK (Swift) ### Installation (SPM) ```swift dependencies: [ .package(url: "https://github.com/hawcx/hawcx_ios_sdk", .upToNextMajor(from: "4.0.0")) ] ``` ### Initialize ```swift import HawcxFramework let hawcxSDK = HawcxSDK(projectApiKey: "YOUR_CONFIG_ID") ``` ### Smart Connect Authentication ```swift class AuthManager: NSObject, AuthV4Callback { private let sdk = HawcxSDK(projectApiKey: "YOUR_CONFIG_ID") func smartConnect(email: String) { sdk.authenticateV4(userid: email, callback: self) } func submitOTP(otp: String) { sdk.submitOtpV4(otp: otp) } func onOtpRequired() { // Show OTP input UI } func onAuthSuccess(accessToken: String?, refreshToken: String?, isLoginFlow: Bool) { if isLoginFlow { // Fully authenticated } else { // Device registration phase } } func onError(errorCode: AuthV4ErrorCode, errorMessage: String) { // Handle error } } ``` ### Web Login Approval (iOS) ```swift // Step 1: Validate PIN from QR code sdk.webLogin(pin: pin, callback: self) // WebLoginCallback // Step 2: Approve login let webToken = UserDefaults.standard.string(forKey: "web_token")! sdk.webApprove(token: webToken, callback: self) // Callbacks: onSuccess(), showError(webLoginErrorCode:errorMessage:) ``` ### Device Session Management (iOS) - `clearSessionTokens(forUser: userId)` — logout, keep device trusted - `clearUserKeychainData(forUser: userId)` — full device removal (destructive) - `getLastLoggedInUser() -> String` — last authenticated email - `clearLastLoggedInUser()` — clear pre-fill ### Error Codes (iOS AuthV4ErrorCode) | Code | Description | |------|-------------| | `networkError` | Network issue or timeout | | `invalidInput` | Invalid email format | | `keychainSaveFailed` | Failed to save to Keychain | | `clientCryptoError` | Cryptographic operation failed | | `fingerprintError` | Device fingerprint failed | | `otpVerificationFailed` | Invalid or expired OTP | | `missingCryptoForLogin` | Device keys not found | | `internalStateError` | SDK state corruption | ### iOS WebLoginErrorCode | Code | Description | |------|-------------| | `invalidPin` | 7-digit PIN invalid or expired | | `failedApprove` | Approval failed | | `networkError` | Network issue | --- ## React Native SDK ### Installation ```bash npm install @hawcx/react-native-sdk@1.0.7 cd ios && pod install # iOS # Android: add Hawcx Maven repo to settings.gradle (see below) ``` Android Maven setup: ```kts // android/settings.gradle(.kts) dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://raw.githubusercontent.com/hawcx/hawcx_android_sdk/main/maven") metadataSources { mavenPom(); artifact() } } } } ``` ### Initialize ```tsx import { initialize, addAuthListener } from '@hawcx/react-native-sdk'; await initialize({ projectApiKey: 'YOUR_CONFIG_ID', baseUrl: 'https://your-tenant.hawcx-api.hawcx.com', }); ``` ### Smart Connect (React Native) ```tsx import { useHawcxAuth, storeBackendOAuthTokens } from '@hawcx/react-native-sdk'; function SmartConnectScreen() { const { state, authenticate, submitOtp, reset } = useHawcxAuth(); const [email, setEmail] = useState(''); const [otp, setOtp] = useState(''); useEffect(() => { if (state.status === 'authorization_code' && email.trim()) { exchangeCode(email.trim(), state.payload) .catch(console.error) .finally(() => reset()); } }, [state, email, reset]); return ( {state.status === 'otp' ? ( <>