@hawcx/oauth-client
v0.2.0
Published
Lightweight OAuth client for exchanging authorization codes and verifying JWT claims with Hawcx delegation support
Downloads
215
Readme
Hawcx OAuth Client SDK (Node.js)
A lightweight, production-ready Node.js/TypeScript library for exchanging OAuth authorization codes and verifying JWT claims. Built with security, reliability, and ease of use in mind.
Features
- 🔐 Secure JWT Verification: RS256/ES256/EdDSA signature verification with configurable validation
- 🆕 PKCE Support: Native PKCE (RFC 7636) support for enhanced OAuth security
- 🚀 Simple API: One function call to go from code to verified claims
- 🎯 High-Level Hawcx Client: One-line MFA setup with automatic encryption/signing
- 🛡️ Comprehensive Error Handling: Clear, actionable error messages for all failure scenarios
- 📝 TypeScript First: Full type definitions for better IDE support and type checking
- 🧪 Production Ready: Extensive error handling and security best practices
- 🔧 Express/Fastify Compatible: Works with any Node.js framework
- 🔍 Detailed Logging: Structured logging for debugging (no sensitive data logged)
Installation
npm install @hawcx/oauth-client
# or with yarn
yarn add @hawcx/oauth-client
# or with pnpm
pnpm add @hawcx/oauth-clientQuick Start
OAuth Code Exchange
import { exchangeCodeForClaims } from '@hawcx/oauth-client';
// Exchange 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!,
publicKey: process.env.OAUTH_PUBLIC_KEY!,
// Optional (recommended for production):
codeVerifier: 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;
// Mint your own access token (SDK only verifies, doesn't mint)Hawcx Delegation (MFA Setup)
For Hawcx MFA setup and user management, use the delegation client:
import { HawcxDelegationClient, MfaMethod } from '@hawcx/oauth-client';
// 🎉 One-line initialization from explicit 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!
});
// 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}`);What it does automatically: ECIES encryption, Ed25519 signatures, request/response crypto, Hawcx payload formatting, type-safe MfaMethod enum
Demo App
A simple demo application is included to show the OAuth and delegation flows in action:
cd demo
cp env.template .env
# Edit .env with your credentials
npm install
npm run dev
# Visit http://localhost:3000See demo/README.md for details.
Configuration
Environment Variables
For OAuth Code Exchange:
OAUTH_TOKEN_ENDPOINT="https://oauth.example.com/token"
OAUTH_CLIENT_ID="your-client-id"
OAUTH_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..."
OAUTH_ISSUER="https://oauth.example.com" # Optional but recommended
OAUTH_AUDIENCE="your-client-id" # Optional but recommendedFor 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-----..."Public Key Formats
The SDK accepts public keys in multiple formats:
From file:
const claims = await exchangeCodeForClaims({
// ...
apiKey: process.env.HAWCX_API_KEY!,
publicKey: '/path/to/public.pem' // Absolute path
});From environment variable:
const claims = await exchangeCodeForClaims({
// ...
apiKey: process.env.HAWCX_API_KEY!,
publicKey: process.env.OAUTH_PUBLIC_KEY! // PEM string
});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,
oauthTokenUrl,
clientId,
apiKey,
publicKey
});
} catch (error) {
if (error instanceof OAuthExchangeError) {
// Code exchange failed (invalid code, network error, etc.)
console.error(`Exchange failed: ${error.message}`);
console.error(`HTTP Status: ${error.statusCode}`);
console.error(`Response: ${error.responseBody}`);
} else if (error instanceof JWTVerificationError) {
// JWT verification failed (invalid signature, expired, etc.)
console.error(`Verification failed: ${error.message}`);
console.error(`Original error: ${error.originalError}`);
} else if (error instanceof InvalidPublicKeyError) {
// Public key is invalid or unreadable
console.error(`Key error: ${error.message}`);
}
}Common Error Scenarios
| Exception | Common Causes | Recommended Action |
|-----------|--------------|-------------------|
| OAuthExchangeError | Invalid/expired code, network issues | Ask user to re-authenticate |
| JWTVerificationError | Token tampering, expired token | Log incident, ask user to re-authenticate |
| InvalidPublicKeyError | Wrong key, file not found | Check configuration, verify key format |
Security Best Practices
✅ DO
- Always validate audience and issuer in production environments
- Use HTTPS for all OAuth endpoints
- Store keys securely (environment variables, secrets manager)
- Set appropriate leeway for clock skew (5-10 seconds typical)
- Log authentication failures for security monitoring
- Rotate keys regularly following your security policy
❌ DON'T
- Never log JWT tokens or claims containing sensitive data
- Don't disable signature verification in production
- Don't use the id_token as your application's access token (mint your own)
- Don't commit keys to version control
- Don't ignore verification errors or catch them silently
API Reference
exchangeCodeForClaims()
async function exchangeCodeForClaims(
options: ExchangeCodeOptions
): Promise<Record<string, any>>Parameters:
code(string): Authorization code from OAuth flowoauthTokenUrl(string): Token endpoint URLclientId(string): OAuth client identifierapiKey(string): API key for Hawcx OAuthpublicKey(string, optional): RS256 public key (PEM string or file path) - optional if jwksUrl providedjwksUrl(string, optional): JWKS URL for dynamic key fetching (alternative to publicKey)codeVerifier(string, optional): PKCE code verifier (RFC 7636)redirectUri(string, optional): OAuth redirect URItimeout(number, optional): Request timeout in seconds (default: 20)audience(string, optional): Expected 'aud' claim (recommended)issuer(string, optional): Expected 'iss' claim (recommended)verifyExp(boolean, optional): Verify token expiration (default: true)leeway(number, optional): Clock skew tolerance in seconds (default: 0)algorithms(string[], optional): Allowed signing algorithms (default: RS256, ES256/384/512, EdDSA)
Returns: Promise<Record<string, any>> - Verified JWT claims
Throws:
OAuthExchangeError: Code exchange failedJWTVerificationError: JWT verification failedInvalidPublicKeyError: Public key invalid
verifyJwt()
function verifyJwt(
options: VerifyJwtOptions
): Record<string, any>Verify a JWT and return its claims. See full documentation in API.md.
HawcxDelegationClient
See DELEGATION.md for full Hawcx delegation client documentation.
TypeScript Support
This package is written in TypeScript and includes full type definitions:
import type {
ExchangeCodeOptions,
VerifyJwtOptions
} from '@hawcx/oauth-client';
// All options are fully typed
const options: ExchangeCodeOptions = {
code: 'abc123',
oauthTokenUrl: 'https://oauth.example.com/token',
clientId: 'my-app',
publicKey: process.env.OAUTH_PUBLIC_KEY!,
audience: 'my-app', // TypeScript will catch typos!
};Development
Building
npm install
npm run buildTesting
npm test
npm run test:coverageLinting
npm run lint
npm run format