Troubleshooting

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 apiBase URL 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 completed state
  • Codes are single-use - don't retry with the same code
  • Ensure you're sending both authCode AND codeVerifier (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/v1

Backend:

HAWCX_API_KEY=your_config_id
HAWCX_BASE_URL=https://your-tenant.hawcx.com  # optional, has default

Wrong 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.com

CORS 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?