API Documentation
/
Frontend
/
Web
/
Web SDK Quickstart

Web SDK Quickstart

Get started with Hawcx passwordless authentication for web

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:

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' });
  }
});
from hawcx_oauth_client import exchange_code_for_claims
import os, jwt
from datetime import datetime, timedelta

@app.route('/api/login', methods=['POST'])
def login():
    try:
        data = request.get_json()
        code = data.get('code')
        email = data.get('email')

        # Validate authorization code
        claims = exchange_code_for_claims(
            code=code,
            oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
            client_id=os.getenv('OAUTH_CLIENT_ID'),
            public_key=os.getenv('OAUTH_PUBLIC_KEY'),
            api_key=os.getenv('HAWCX_API_KEY'),
            code_verifier=data.get('code_verifier')  # PKCE if used
        )

        # Create session for user
        session_token = jwt.encode(
            {
                'userId': claims['sub'],
                'email': claims['email'],
                'exp': datetime.utcnow() + timedelta(days=7)
            },
            os.getenv('SESSION_SECRET'),
            algorithm='HS256'
        )

        return jsonify({'success': True, 'sessionToken': session_token})
    except Exception as error:
        return jsonify({'error': 'Authentication failed'}), 401

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