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-clientStep 2: Set Up Environment Variables
Create a .env file or configure your environment with:
# OAuth Configuration (required)
HAWCX_API_KEY=your_api_key
OAUTH_TOKEN_ENDPOINT=https://example.hawcx.com/oauth2/token
OAUTH_CLIENT_ID=your_client_id
JWKS_URL="https://example.hawcx.com/.well-known/jwks.json"
# Optional but recommended
OAUTH_ISSUER=https://example.hawcx.com
OAUTH_AUDIENCE=your_client_id
# For MFA features (if using delegation client)
HAWCX_BASE_URL=https://example.hawcx.com
SP_ED25519_PRIVATE_KEY_PEM=-----BEGIN PRIVATE KEY-----\n...
SP_X25519_PRIVATE_KEY_PEM=-----BEGIN PRIVATE KEY-----\n...
IDP_ED25519_PUBLIC_KEY_PEM=-----BEGIN PUBLIC KEY-----\n...
IDP_X25519_PUBLIC_KEY_PEM=-----BEGIN PUBLIC KEY-----\n...Load environment variables in your application:
import dotenv from 'dotenv';
dotenv.config();Step 3: Create an Authentication Endpoint
Create a new route to handle Hawcx OAuth callbacks:
import express from 'express';
import { exchangeCodeForClaims } from '@hawcx/oauth-client';
const router = express.Router();
router.post('/auth/hawcx/callback', async (req, res) => {
try {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: 'Missing authorization code' });
}
// Exchange the authorization code for verified claims
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!, // Dynamic key fetching (recommended)
audience: process.env.OAUTH_AUDIENCE || process.env.OAUTH_CLIENT_ID,
issuer: process.env.OAUTH_ISSUER,
leeway: 10
});
// Here: Find or create user in your database
const user = await findOrCreateUser({
id: claims.sub,
email: claims.email,
emailVerified: claims.email_verified
});
// 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 callback error:', error);
res.status(401).json({ error: 'Authentication failed' });
}
});
export default router;Step 4: 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;
emailVerified: boolean;
}
export async function findOrCreateUser(hawcxUser: HawcxUser) {
// Check if user exists
let user = await db.users.findOne({
hawcx_id: hawcxUser.id
});
if (user) {
// Update email_verified if needed
if (hawcxUser.emailVerified && !user.email_verified) {
await db.users.update(
{ id: user.id },
{ email_verified: true }
);
}
return user;
}
// Create new user
const newUser = {
hawcx_id: hawcxUser.id,
email: hawcxUser.email,
email_verified: hawcxUser.emailVerified,
created_at: new Date(),
last_login: new Date()
};
const { id } = await db.users.insert(newUser);
return { id, ...newUser };
}
export async function generateSessionToken(user: any) {
// Use JWT or your preferred session method
return jwt.sign(
{
userId: user.id,
email: user.email,
hawcxId: user.hawcx_id
},
process.env.SESSION_SECRET!,
{ expiresIn: '7d' }
);
}Step 5: Add Middleware for Protected Routes
Create middleware to verify session tokens:
// middleware/authMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export interface AuthRequest extends Request {
user?: {
userId: string;
email: string;
hawcxId: string;
};
}
export function requireAuth(req: AuthRequest, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Missing authorization token' });
}
try {
const payload = jwt.verify(token, process.env.SESSION_SECRET!);
req.user = payload as AuthRequest['user'];
next();
} catch (error) {
res.status(401).json({ error: 'Invalid or expired token' });
}
}
export function optionalAuth(req: AuthRequest, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (token) {
try {
const payload = jwt.verify(token, process.env.SESSION_SECRET!);
req.user = payload as AuthRequest['user'];
} catch (error) {
// Token is invalid, but that's okay for optional auth
}
}
next();
}Step 6: Use Protected Routes
import { requireAuth, optionalAuth, AuthRequest } from '../middleware/authMiddleware';
// Protected route - requires authentication
router.get('/me', requireAuth, (req: AuthRequest, res) => {
res.json({
userId: req.user?.userId,
email: req.user?.email
});
});
// Optional authentication - works with or without auth
router.get('/articles', optionalAuth, (req: AuthRequest, res) => {
const userId = req.user?.userId;
// Return different content based on whether user is authenticated
res.json({
articles: getArticles(userId)
});
});
// Logout endpoint
router.post('/logout', requireAuth, (req: AuthRequest, res) => {
// In a session-based approach, you might track invalidated tokens
// In a stateless JWT approach, just delete client-side
res.json({ success: true, message: 'Logged out successfully' });
});Step 7: Set Up MFA (Optional)
If you want to manage user MFA settings programmatically:
// routes/mfaRoutes.ts
import express from 'express';
import { HawcxDelegationClient, MfaMethod } from '@hawcx/oauth-client';
import { requireAuth, AuthRequest } from '../middleware/authMiddleware';
const router = express.Router();
const delegationClient = 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: process.env.HAWCX_BASE_URL!,
spId: process.env.OAUTH_CLIENT_ID!,
apiKey: process.env.HAWCX_API_KEY
});
// Initiate MFA setup for the authenticated user
router.post('/mfa/initiate', requireAuth, async (req: AuthRequest, res) => {
try {
const { mfaMethod, phoneNumber } = req.body;
const userEmail = req.user?.email;
const result = await delegationClient.initiateMfaChange({
userid: userEmail!,
mfaMethod,
phoneNumber
});
// Store session ID temporarily (e.g., in cache with TTL)
await cache.set(`mfa_session:${userEmail}`, result.session_id, 600); // 10 min TTL
res.json({
success: true,
message: 'MFA setup initiated. Check your email/SMS for verification code.'
});
} catch (error) {
console.error('MFA initiation failed:', error);
res.status(400).json({ error: 'Failed to initiate MFA setup' });
}
});
// Verify MFA code
router.post('/mfa/verify', requireAuth, async (req: AuthRequest, res) => {
try {
const { otp } = req.body;
const userEmail = req.user?.email;
// Retrieve stored session ID
const sessionId = await cache.get(`mfa_session:${userEmail}`);
if (!sessionId) {
return res.status(400).json({ error: 'MFA session expired. Please try again.' });
}
const result = await delegationClient.verifyMfaChange({
userid: userEmail!,
sessionId,
otp
});
if (result.status !== 'success') {
return res.status(400).json({ error: 'Invalid verification code' });
}
// Clean up session ID
await cache.delete(`mfa_session:${userEmail}`);
res.json({
success: true,
message: 'MFA setup completed successfully'
});
} catch (error) {
console.error('MFA verification failed:', error);
res.status(400).json({ error: 'MFA verification failed' });
}
});
// Get user's current MFA settings
router.get('/mfa/status', requireAuth, async (req: AuthRequest, res) => {
try {
const userEmail = req.user?.email;
const credentials = await delegationClient.getUserCredentials(userEmail!);
res.json({
mfaEnabled: credentials.mfa_method !== 'none',
mfaMethod: credentials.mfa_method
});
} catch (error) {
console.error('Failed to fetch MFA status:', error);
res.status(500).json({ error: 'Failed to fetch MFA status' });
}
});
export default router;Step 8: Register Routes in Main App
// app.ts
import express from 'express';
import authRoutes from './routes/authRoutes';
import mfaRoutes from './routes/mfaRoutes';
const app = express();
app.use(express.json());
// Authentication routes
app.use('/api/auth', authRoutes);
// MFA routes (protected)
app.use('/api/user', mfaRoutes);
app.listen(3000, () => {
console.log('Server listening on port 3000');
});Step 9: Error Handling
Add proper error handling for Hawcx-specific errors:
import {
OAuthExchangeError,
JWTVerificationError,
InvalidPublicKeyError
} from '@hawcx/oauth-client';
export function handleHawcxError(error: unknown, res: express.Response) {
if (error instanceof OAuthExchangeError) {
return res.status(401).json({
error: 'OAuth exchange failed',
message: 'The authorization code is invalid or expired'
});
}
if (error instanceof JWTVerificationError) {
return res.status(401).json({
error: 'JWT verification failed',
message: 'The authentication token could not be verified'
});
}
if (error instanceof InvalidPublicKeyError) {
console.error('Invalid public key configuration:', error);
return res.status(500).json({
error: 'Server configuration error'
});
}
throw error;
}Complete Example
Here's a minimal complete example:
import express, { Express } from 'express';
import { exchangeCodeForClaims, OAuthExchangeError } from '@hawcx/oauth-client';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
dotenv.config();
const app: Express = express();
app.use(express.json());
// Callback endpoint
app.post('/api/auth/callback', async (req, res) => {
try {
const { code } = req.body;
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!
});
const sessionToken = jwt.sign(
{ userId: claims.sub, email: claims.email },
process.env.SESSION_SECRET!,
{ expiresIn: '7d' }
);
res.json({ sessionToken });
} catch (error) {
console.error('Auth error:', error);
res.status(401).json({ error: 'Authentication failed' });
}
});
// Protected endpoint
app.get('/api/me', (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Unauthorized' });
try {
const payload = jwt.verify(token, process.env.SESSION_SECRET!);
res.json(payload);
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));Next Steps
- Review the complete API reference for all available methods
- Explore MFA features for advanced user management
- Check out the demo app for a full working example