Troubleshooting
Common issues and how to fix them
Frontend SDK Issues
Client not initializing
Cause: Invalid config or network issue.
const client = createHawcxClient({
configId: 'YOUR_CONFIG_ID',
apiBase: 'https://your-tenant.hawcx.com/v1'
});
// Check if initial state is idle
const state = client.getState();
if (state.status !== 'idle') {
console.error('Unexpected initial state:', state);
}State stuck on "loading"
Cause: Network timeout or the SDK is handling device signatures internally.
Note: The SDK automatically handles setup_device and device_challenge prompts. During this time, state will be loading. This is normal - the SDK is performing cryptographic operations.
Fix: If loading persists beyond 10-15 seconds:
- Check network connectivity
- Verify
apiBaseURL is correct - Check browser console for errors
Unknown prompt type
Cause: Not handling all prompt types in your UI.
Fix: Always include a default case:
switch (prompt.type) {
case 'select_method':
// ...
case 'enter_code':
// ...
// ... other prompts
default:
console.warn('Unhandled prompt:', prompt.type);
// Show generic "please wait" or contact support
}Handling errors by category
Note: FlowError has code, message, and category fields. Use category to determine how to recover:
import { FlowErrorCategory } from '@hawcx/core';
if (state.status === 'error') {
switch (state.error.category) {
case FlowErrorCategory.RETRYABLE:
// Transient error - can retry the same action
// e.g., network timeout, rate limit (after waiting)
break;
case FlowErrorCategory.USER_ACTION:
// User needs to do something - wrong code, invalid input
// Show the error message, let them try again
break;
case FlowErrorCategory.FATAL:
// Unrecoverable - session invalid, restart flow
client.reset();
break;
}
}Backend Token Exchange Errors
"Authentication failed" on code exchange
Cause: Authorization code is invalid, expired, or already used.
Fix:
- Codes expire in 60 seconds - exchange immediately after
completedstate - Codes are single-use - don't retry with the same code
- Ensure you're sending both
authCodeANDcodeVerifier(PKCE)
// Frontend - send BOTH values
if (state.status === 'completed') {
await fetch('/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
authCode: state.authCode,
codeVerifier: state.codeVerifier // Don't forget this!
})
});
}TokenExchangeError
Cause: Code exchange failed at the OAuth server.
import { HawcxOAuth, TokenExchangeError } from '@hawcx/oauth-client';
try {
const { claims } = await oauth.exchangeCode(authCode, codeVerifier);
} catch (error) {
if (error instanceof TokenExchangeError) {
console.error(`Exchange failed: ${error.message}`);
console.error(`HTTP Status: ${error.statusCode}`);
// Ask user to re-authenticate
}
}TokenVerificationError
Cause: JWT signature or claims validation failed.
import { TokenVerificationError } from '@hawcx/oauth-client';
try {
const { claims } = await oauth.exchangeCode(authCode, codeVerifier);
} catch (error) {
if (error instanceof TokenVerificationError) {
console.error(`Verification failed: ${error.message}`);
// Log incident, ask user to re-authenticate
}
}Missing codeVerifier
Cause: Frontend didn't send the PKCE verifier.
The SDK automatically generates a codeVerifier during start() and exposes it in the completed state. You must send it to your backend:
// Frontend
if (state.status === 'completed') {
console.log('authCode:', state.authCode);
console.log('codeVerifier:', state.codeVerifier); // This must exist!
}
// Backend
const { claims } = await oauth.exchangeCode(authCode, codeVerifier);React Hook Issues
useFlowState() returns undefined
Cause: Component not wrapped in HawcxProvider.
Fix:
// Must wrap your app
function App() {
return (
<HawcxProvider config={{ configId: '...', apiBase: '...' }}>
<LoginFlow /> {/* Hooks work here */}
</HawcxProvider>
);
}State not updating after actions
Cause: Using stale state reference.
Fix: The useFlowState() hook automatically subscribes to changes. Make sure you're using the hook value, not a cached copy:
function LoginFlow() {
const state = useFlowState(); // Always use this directly
// Wrong - stale closure
useEffect(() => {
const cachedState = state;
setTimeout(() => {
console.log(cachedState); // Stale!
}, 1000);
}, []);
// Right - use state directly in render
return <div>{state.status}</div>;
}Side effects in render
Cause: Calling async functions or redirects directly in render.
Fix: Use useEffect for side effects:
// Wrong - side effect in render
case 'completed':
sendToBackend(state.authCode); // Called on every re-render!
return <div>Success!</div>;
// Right - side effect in useEffect
useEffect(() => {
if (state.status === 'completed') {
sendToBackend(state.authCode, state.codeVerifier);
}
}, [state.status]);Environment & Configuration
Required environment variables
Frontend:
HAWCX_CONFIG_ID=your_config_id
HAWCX_API_BASE=https://your-tenant.hawcx.com/v1Backend:
HAWCX_API_KEY=your_config_id
HAWCX_BASE_URL=https://your-tenant.hawcx.com # optional, has defaultWrong tenant URL
Symptoms: 404 errors, connection refused
Fix: Use your specific tenant URL:
# Correct
https://your-company.hawcx.com
# Wrong
https://api.hawcx.com
https://hawcx.comCORS errors
Cause: Frontend and API on different origins.
Fix: The Hawcx API includes CORS headers for your registered domains. If you're testing locally:
- Ensure your local origin is registered in the Hawcx dashboard
- Or use a proxy in development
Security Best Practices
Always verify on backend
Never trust frontend state for authorization:
// Wrong - trusting frontend
if (state.status === 'completed') {
localStorage.setItem('userId', 'some-id'); // No!
}
// Right - verify on backend
if (state.status === 'completed') {
const res = await fetch('/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ authCode: state.authCode, codeVerifier: state.codeVerifier })
});
const { userId } = await res.json(); // Backend verified
}Don't use idToken as your access token
The idToken from Hawcx proves user identity. Create your own session/access token:
const { idToken, claims } = await oauth.exchangeCode(authCode, codeVerifier);
// Don't do this:
res.json({ token: idToken }); // Wrong!
// Do this:
const sessionToken = createYourOwnSession(claims.sub, claims.email);
res.json({ token: sessionToken });Use httpOnly cookies for sessions
// Backend
res.cookie('session', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});HTTPS required
Auth must happen over HTTPS. The SDK will fail on HTTP in production.
Still Stuck?
- Check the API Reference for detailed types
- Join our Slack for live support