Skip to main content

Hawcx Web SDK Quickstart

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:
  • Node.js
  • Python
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' });
  }
});

Step 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_key

Key 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