Integrate in Existing Node.js Project

Add Hawcx authentication to an existing Node.js application

Integrating Hawcx Backend SDK into Existing Node.js Projects

This guide walks through adding Hawcx OAuth authentication to an existing Node.js backend.

Step 1: Install the SDK

npm install @hawcx/oauth-client

Step 2: Set Up Environment Variables

Create a .env file or configure your environment with:

# Base URL — from Admin Console → Project Settings (environment-specific)
HAWCX_BASE_URL="<Base URL from Admin Console>"
# Config ID — from Admin Console → Project Settings (tenant routing key)
HAWCX_CONFIG_ID="<Config ID from Admin Console>"
# Client ID — from Admin Console → Project Settings (expected `aud` on id_tokens)
HAWCX_CLIENT_ID="<Client ID from Admin Console>"

# Optional (for delegation / MFA management)
HAWCX_SECRET_KEY=hwx_sk_v1_...

Where to find these values

Open the Hawcx Admin Console, go to Project Settings, and copy the Base URL, Config ID, and Client ID. All three are environment-specific.

configId vs clientId

These look similar but are distinct values. configId is the tenant routing key sent as the X-Config-Id header on token exchange. clientId is the value the tenant has configured as the aud claim on issued id_tokens. The SDK passes each to the right place; you just need both set correctly. See the Quickstart for details.

Load environment variables in your application:

import dotenv from 'dotenv';
dotenv.config();

Step 3: Construct the SDK Once at Startup

Use the discovery-mode factory so verification enforces iss / aud / exp / nbf (not just signature). The discovery fetch happens once at boot — don't run it per request.

// src/hawcx.ts
import { HawcxOAuth } from '@hawcx/oauth-client';

export const oauth = await HawcxOAuth.fromIssuer({
  issuer: process.env.HAWCX_BASE_URL!,
  configId: process.env.HAWCX_CONFIG_ID!,
  clientId: process.env.HAWCX_CLIENT_ID!,
});

The await rejects with DiscoveryError if the issuer is unreachable or the discovery document is malformed — your bootstrap fails fast rather than starting with broken verification.

Step 4: Create an Exchange Endpoint

import express from 'express';
import { oauth } from './hawcx';   // singleton from Step 3

const router = express.Router();

router.post('/exchange', async (req, res) => {
  try {
    const { authCode, codeVerifier } = req.body;

    if (!authCode || !codeVerifier) {
      return res.status(400).json({ error: 'Missing authCode or codeVerifier' });
    }

    // Verification enforces signature + iss + aud + exp + nbf
    const { claims } = await oauth.exchangeCode(authCode, codeVerifier);

    // Find or create user in your database
    const user = await findOrCreateUser({
      id: claims.sub,
      email: claims.email,
    });

    // Create your application's session/JWT
    const sessionToken = generateSessionToken(user);

    res.json({
      success: true,
      sessionToken,
      user: { id: user.id, email: user.email },
    });
  } catch (error) {
    console.error('Hawcx exchange error:', error);
    res.status(401).json({ error: 'Authentication failed' });
  }
});

export default router;

Step 5: Integrate with Your User Management

Update your user service to handle Hawcx identities:

// services/userService.ts
import db from '../db';

interface HawcxUser {
  id: string;  // Hawcx user ID (sub claim)
  email?: string;
}

export async function findOrCreateUser(hawcxUser: HawcxUser) {
  let user = await db.users.findOne({ hawcx_id: hawcxUser.id });

  if (!user) {
    user = await db.users.create({
      hawcx_id: hawcxUser.id,
      email: hawcxUser.email
    });
  }

  return user;
}

Optional: Backend-Driven MFA Management

If you need to manage MFA from your backend, use the delegation client with your secret key blob:

import { DelegationClient, MfaMethod } from '@hawcx/oauth-client';

const client = DelegationClient.fromSecretKey({
  secretKey: process.env.HAWCX_SECRET_KEY!,
  baseUrl: process.env.HAWCX_BASE_URL!,
  apiKey: process.env.HAWCX_CONFIG_ID
});

const result = await client.mfa.initiate({
  userId: '[email protected]',
  mfaMethod: MfaMethod.SMS,
  phoneNumber: '+15551234567'
});

await client.mfa.verify({
  userId: '[email protected]',
  sessionId: result.session_id,
  otp: '123456'
});