;
}
```
| 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 (
);
}
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 (
);
}
if (step.type === 'enter_totp') {
return (
);
}
if (step.type === 'setup_totp') {
return (
Set up authenticator
Or enter manually: {step.secret}
);
}
if (step.type === 'setup_sms') {
const [phone, setPhone] = useState('');
return (
);
}
if (step.type === 'await_approval') {
return (
{step.qrData &&
}`})
}
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')
```
---
## Java Backend SDK (Complete API)
### HawcxOauthClient
```java
import com.hawcx.oauth.HawcxOauthClient;
import com.hawcx.oauth.HawcxOauthConfig;
import com.hawcx.oauth.JwtVerifier;
import com.hawcx.oauth.TokenRequest;
HawcxOauthConfig config = new HawcxOauthConfig(System.getenv("HAWCX_BASE_URL"));
HawcxOauthClient client = new HawcxOauthClient(config);
```
### `exchangeCodeForClaims(TokenRequest, VerifyOptions)`
```java
TokenRequest req = new TokenRequest(configId, authCode, codeVerifier);
JwtVerifier.VerifyOptions opts = JwtVerifier.VerifyOptions.builder()
.jwksUrl(config.keysEndpoint())
.audience(System.getenv("HAWCX_CONFIG_ID"))
.issuer(System.getenv("HAWCX_BASE_URL"))
.leewaySeconds(10)
.build();
JsonObject claims = client.exchangeCodeForClaims(req, opts);
String userId = claims.get("sub").getAsString();
```
### `verifyJwt(token, opts)`
```java
JsonObject claims = client.verifyJwt(idToken, opts);
```
### Spring Boot Integration
```java
@RestController
public class AuthController {
private final HawcxOauthClient client;
private final JwtVerifier.VerifyOptions verifyOpts;
private final String configId;
public AuthController(@Value("${hawcx.base-url}") String baseUrl,
@Value("${hawcx.config-id}") String configId) {
this.configId = configId;
HawcxOauthConfig config = new HawcxOauthConfig(baseUrl);
this.client = new HawcxOauthClient(config);
this.verifyOpts = JwtVerifier.VerifyOptions.builder()
.jwksUrl(config.keysEndpoint())
.audience(configId)
.issuer(baseUrl)
.leewaySeconds(10)
.build();
}
public record ExchangeRequest(String authCode, String codeVerifier) {}
@PostMapping("/exchange")
public ResponseEntity> exchange(@RequestBody ExchangeRequest body) {
try {
TokenRequest req = new TokenRequest(configId, body.authCode(), body.codeVerifier());
JsonObject claims = client.exchangeCodeForClaims(req, verifyOpts);
return ResponseEntity.ok(Map.of(
"success", true,
"userId", claims.get("sub").getAsString()
));
} catch (Exception e) {
return ResponseEntity.status(401).body(Map.of("error", "Authentication failed"));
}
}
}
```
### Java Delegation Client (MFA Management)
```java
import com.hawcx.delegation.DelegationClient;
import com.hawcx.delegation.MfaMethod;
DelegationClient client = DelegationClient.fromSecretKey(
System.getenv("HAWCX_BASE_URL"),
System.getenv("HAWCX_SECRET_KEY"), // hwx_sk_v1_...
System.getenv("HAWCX_CONFIG_ID")
);
// Initiate MFA
JsonObject result = client.mfa.initiate(
"user@example.com",
MfaMethod.SMS,
"+15551234567",
null
);
// Verify
client.mfa.verify("user@example.com",
result.get("session_id").getAsString(),
"123456");
// User credentials and suggested-MFA preference
JsonObject creds = client.users.getCredentials("user@example.com");
client.users.setSuggestedMfa("user@example.com", "always");
// Device management
JsonObject devices = client.devices.list("user@example.com");
client.devices.revoke("user@example.com", "h2index");
```
---
## 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"
)
}
}
```
### Hawcx Authentication
Hawcx 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 authentication
- `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")
```
### Hawcx 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.1.5
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',
});
```
### Authentication (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' ? (
<>
);
}
async function exchangeCode(userId, payload) {
const response = await fetch('https://your-backend/api/hawcx/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: payload.code, email: userId, expires_in: payload.expiresIn }),
});
const { access_token, refresh_token } = await response.json();
await storeBackendOAuthTokens(userId, access_token, refresh_token);
}
```
### React Native SDK Methods
- `initialize(config)` — configure native instance
- `authenticate(userId)` — begin authentication
- `submitOtp(code)` — forward OTP
- `addAuthListener(listener)` — receive events
- `useHawcxAuth()` — React hook (state, authenticate, submitOtp, reset)
- `storeBackendOAuthTokens(userId, accessToken, refreshToken?)` — persist backend tokens
- `hawcxClient.authenticate(userId, options)` — promise-based helper
### React Native Auth Events
- `otp_required` — show OTP UI
- `authorization_code` — send code to backend
- `additional_verification_required` — step-up needed
- `auth_error` — error occurred
---
## Flutter SDK
### Installation
```yaml
# pubspec.yaml
dependencies:
hawcx_flutter_sdk: ^1.0.2
```
```bash
flutter pub get
cd ios && pod install # iOS
# Android: add Hawcx Maven repo to settings.gradle (same as React Native)
```
Requirements: Flutter 3.19+, iOS 17+, Android 8+ (minSdk 26).
### Initialize
```dart
import 'package:hawcx_flutter_sdk/hawcx_flutter_sdk.dart';
final client = HawcxClient();
await client.initialize(HawcxConfig(
projectApiKey: 'YOUR_CONFIG_ID',
baseUrl: 'https://your-tenant.hawcx-api.hawcx.com',
));
```
### Authentication (Flutter)
```dart
Future startSmartConnect(String email) async {
final auth = client.authenticate(
userId: email.trim(),
onOtpRequired: () {
// Show OTP UI
},
onAuthorizationCode: (payload) async {
final response = await backendExchangeCode(
email: email.trim(),
code: payload.code,
expiresIn: payload.expiresIn,
);
await client.storeBackendOAuthTokens(
userId: email.trim(),
accessToken: response.accessToken,
refreshToken: response.refreshToken,
);
},
onAdditionalVerificationRequired: (payload) {
// Handle step-up
},
);
try {
final result = await auth.future;
// result.isLoginFlow, result.accessToken, result.refreshToken
} catch (error) {
// Handle HawcxAuthException
}
}
// Submit OTP when required:
await client.submitOtp(otpCode.trim());
```
### Flutter SDK Methods
- `client.initialize(config)` — configure native instance
- `client.authenticate(userId, ...)` — begin authentication, returns `HawcxAuthHandle`
- `client.submitOtp(otp)` — forward OTP
- `client.storeBackendOAuthTokens(userId, accessToken, refreshToken?)` — persist tokens
---
## Admin Console Configuration
### Projects
- Create projects from the dashboard (Admin role required)
- Each project has its own Config IDs, flow config, and analytics
- Environments: Development (free) and Production (contact sales)
### Config IDs
- Public identifiers that connect your app to Hawcx
- Scoped per project and environment (Development / Production)
- Generate from Admin Console > Config IDs page
- Select platform: Web, iOS, or Android
### Flow Configuration
Each auth flow has 3 phases:
1. **Primary Verification** — how users prove identity (required)
- Email OTP, SMS OTP, Magic Link, QR Code, Push
2. **MFA** — optional second factor
- Policy: Off / Optional / Required
- Methods: Email OTP, SMS OTP, TOTP, QR Code, Push
3. **Device Trust** — remember trusted devices
- Skip MFA on trusted devices (on by default)
Configure signup and signin flows independently.
### Team & RBAC
- **Admin**: full access (create projects, manage team, revoke Config IDs)
- **Developer**: limited access (manage own Config IDs, view projects)
---
## Troubleshooting
### Frontend: State stuck on "loading"
The SDK handles `setup_device` and `device_challenge` steps internally. If loading persists >15s: check network, verify apiBase URL, check console.
### Frontend: Unknown step type
Always include a default case in your step handler.
### Backend: "Authentication failed" on exchange
- Codes expire in 60 seconds — exchange immediately
- Codes are single-use — don't retry with same code
- Send BOTH `authCode` AND `codeVerifier` (PKCE required)
### React: `useAuthState()` returns undefined
Component must be wrapped in `HawcxProvider`.
### React: Side effects in render
Use `useEffect` for the completed state, not inline fetch calls.
### CORS errors
Register your local origin in the Hawcx dashboard, or use a dev proxy.
---
## Security Best Practices
1. **Always verify on backend** — never trust frontend state for authorization
2. **Don't use idToken as access token** — create your own session token from claims
3. **Use httpOnly cookies** for session storage:
```typescript
res.cookie('session', token, { httpOnly: true, secure: true, sameSite: 'strict' });
```
4. **HTTPS required** — the SDK will fail on HTTP in production
5. **Exchange codes immediately** — they expire in 60 seconds
6. **Backend secrets stay on backend** — never expose `HAWCX_SECRET_KEY` to frontend/mobile
---
## Support
- Slack: https://join.slack.com/t/hawcx-dev/shared_invite/zt-2o1no0mer-9LX7rnzBkE3MAtLoi63gRw
- Email: support@hawcx.com
- Docs: https://docs.hawcx.com