API Documentation
/
Backend
/
Node.js
/
Node.js Backend SDK Quickstart

Node.js Backend SDK Quickstart

Integrate Hawcx OAuth authentication in your Node.js backend

Hawcx OAuth Client SDK for Node.js

Add passwordless authentication to your Node.js backend. Exchange OAuth codes for verified user claims and manage MFA.

Installation

npm install @hawcx/oauth-client

Quick Start

OAuth Code Exchange

The most common flow: exchange a Hawcx authorization code for verified user claims.

import { exchangeCodeForClaims } from '@hawcx/oauth-client';

app.post('/api/auth/callback', async (req, res) => {
  try {
    // Exchange the authorization code for verified claims
    const claims = await exchangeCodeForClaims({
      code: req.body.code,
      oauthTokenUrl: process.env.OAUTH_TOKEN_ENDPOINT!,
      clientId: process.env.OAUTH_CLIENT_ID!,
      apiKey: process.env.HAWCX_API_KEY!,
      jwksUrl: process.env.JWKS_URL!,
      // Optional (recommended for production):
      codeVerifier: req.session?.pkceVerifier,  // PKCE support
      audience: 'my-app',  // Validate 'aud' claim
      issuer: 'https://oauth.example.com',  // Validate 'iss' claim
      leeway: 10  // Clock skew tolerance
    });

    // Use the verified claims
    const userId = claims.sub;
    const email = claims.email;

    // Mint your own access token or session
    const sessionToken = jwt.sign(
      { sub: claims.sub, email: claims.email },
      process.env.APP_SESSION_SECRET!,
      { expiresIn: '1h' }
    );

    res.json({ sessionToken, userId });
  } catch (error) {
    console.error('OAuth exchange failed:', error);
    res.status(401).json({ error: 'Authentication failed' });
  }
});

Hawcx Delegation Client (MFA Setup)

For advanced use cases like setting up MFA for users programmatically:

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

// Initialize the delegation client with your signing/encryption keys
const client = HawcxDelegationClient.fromKeys({
  spSigningKey: process.env.SP_ED25519_PRIVATE_KEY_PEM!,
  spEncryptionKey: process.env.SP_X25519_PRIVATE_KEY_PEM!,
  idpVerifyKey: process.env.IDP_ED25519_PUBLIC_KEY_PEM!,
  idpEncryptionKey: process.env.IDP_X25519_PUBLIC_KEY_PEM!,
  baseUrl: 'https://example.hawcx.com',
  spId: process.env.OAUTH_CLIENT_ID!,
  apiKey: process.env.HAWCX_API_KEY
})

// Initiate MFA setup (Email, SMS, or TOTP)
const result = await client.initiateMfaChange({
  userid: "[email protected]",
  mfaMethod: MfaMethod.SMS, 
  phoneNumber: "+15551234567"
});

// Verify OTP and complete MFA setup
await client.verifyMfaChange({
  userid: "[email protected]",
  sessionId: result.session_id,
  otp: "123456"
});

// Get user credentials
const creds = await client.getUserCredentials("[email protected]");
console.log(`MFA method: ${creds.mfa_method}`);

Configuration

Environment Variables

For OAuth Code Exchange:

HAWCX_API_KEY="your-api-key"
OAUTH_TOKEN_ENDPOINT="https://example.hawcx.com/oauth2/token"
JWKS_URL="https://example.hawcx.com/.well-known/jwks.json"
OAUTH_CLIENT_ID="your-client-id"
OAUTH_ISSUER="https://example.hawcx.com"  # Optional but recommended
OAUTH_AUDIENCE="your-client-id"  # Optional but recommended

For Hawcx Delegation:

SP_ED25519_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..."
SP_X25519_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..."
IDP_ED25519_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----..."
IDP_X25519_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----..."
OAUTH_CLIENT_ID="your-client-id"

Public Key Formats

The SDK accepts public keys in multiple formats or via JWKS endpoint (recommended):

Using JWKS endpoint (recommended for production):

const claims = await exchangeCodeForClaims({
  code: authCode,
  oauthTokenUrl: process.env.OAUTH_TOKEN_ENDPOINT!,
  clientId: process.env.OAUTH_CLIENT_ID!,
  apiKey: process.env.HAWCX_API_KEY!,
  jwksUrl: 'https://example.hawcx.com/.well-known/jwks.json'  // Dynamic key fetching
});

From environment variable (static key):

const claims = await exchangeCodeForClaims({
  // ...
  apiKey: process.env.HAWCX_API_KEY!,
  publicKey: process.env.OAUTH_PUBLIC_KEY!  // PEM string
});

From file path:

const claims = await exchangeCodeForClaims({
  // ...
  apiKey: process.env.HAWCX_API_KEY!,
  publicKey: '/path/to/public.pem'  // Absolute path
});

Expected PEM format:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----

Error Handling

The SDK provides specific exceptions for different failure scenarios:

import {
  exchangeCodeForClaims,
  OAuthExchangeError,
  JWTVerificationError,
  InvalidPublicKeyError
} from '@hawcx/oauth-client';

try {
  const claims = await exchangeCodeForClaims({
    code: authCode,
    oauthTokenUrl: tokenEndpoint,
    clientId: clientId,
    apiKey: process.env.HAWCX_API_KEY!,
    publicKey: publicKey
  });
} catch (error) {
  if (error instanceof OAuthExchangeError) {
    console.error('OAuth exchange failed:', error.message);
    // The authorization code may be invalid or expired
  } else if (error instanceof JWTVerificationError) {
    console.error('JWT verification failed:', error.message);
    // The JWT signature or claims validation failed
  } else if (error instanceof InvalidPublicKeyError) {
    console.error('Invalid public key:', error.message);
    // The provided public key is malformed
  } else {
    throw error;
  }
}

Integration Examples

Express.js

import express, { Request, Response } from 'express';
import { exchangeCodeForClaims } from '@hawcx/oauth-client';

const app = express();
app.use(express.json());

// Callback endpoint after user completes Hawcx authentication
app.post('/auth/callback', async (req: Request, res: Response) => {
  const { code } = req.body;

  if (!code) {
    return res.status(400).json({ error: 'Missing authorization code' });
  }

  try {
    const claims = await exchangeCodeForClaims({
      code,
      oauthTokenUrl: process.env.OAUTH_TOKEN_ENDPOINT!,
      clientId: process.env.OAUTH_CLIENT_ID!,
      apiKey: process.env.HAWCX_API_KEY!,
      jwksUrl: process.env.JWKS_URL!,
      audience: process.env.OAUTH_CLIENT_ID
    });

    // Create user session
    req.session.userId = claims.sub;
    req.session.email = claims.email;

    res.json({ success: true, userId: claims.sub });
  } catch (error) {
    console.error('Authentication failed:', error);
    res.status(401).json({ error: 'Authentication failed' });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

Fastify

import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
import { exchangeCodeForClaims } from '@hawcx/oauth-client';

const app = Fastify();

app.post<{ Body: { code: string } }>('/auth/callback', async (request: FastifyRequest, reply: FastifyReply) => {
  const { code } = request.body;

  try {
    const claims = await exchangeCodeForClaims({
      code,
      oauthTokenUrl: process.env.OAUTH_TOKEN_ENDPOINT!,
      clientId: process.env.OAUTH_CLIENT_ID!,
      apiKey: process.env.HAWCX_API_KEY!,
      jwksUrl: process.env.JWKS_URL!
    });

    reply.send({ success: true, userId: claims.sub });
  } catch (error) {
    reply.status(401).send({ error: 'Authentication failed' });
  }
});

app.listen({ port: 3000 }, (err) => {
  if (err) throw err;
  console.log('Server running on port 3000');
});

PKCE Support

For enhanced security, especially in native or SPA applications, use PKCE:

// On client: Generate code verifier
const codeVerifier = generateRandomString(128);
const codeChallenge = base64url(sha256(codeVerifier));

// On backend: Exchange with verifier
const claims = await exchangeCodeForClaims({
  code: authCode,
  codeVerifier,  // Include the verifier from client session
  oauthTokenUrl: process.env.OAUTH_TOKEN_ENDPOINT!,
  clientId: process.env.OAUTH_CLIENT_ID!,
  apiKey: process.env.HAWCX_API_KEY!,
  jwksUrl: process.env.JWKS_URL!
});

Next Steps