@re-auth/http-adapters
Version:
HTTP protocol adapters for ReAuth - framework-agnostic integration with Express, Fastify, and Hono
747 lines (594 loc) âĸ 18.4 kB
Markdown
V2 HTTP protocol adapters for ReAuth - framework-agnostic integration with Express, Fastify, and Hono.
- đ **Framework Agnostic**: Support for Express, Fastify, and Hono
- đ **Security First**: Built-in CORS, rate limiting, and security headers
- đĄī¸ **Type Safe**: Full TypeScript integration with V2 engine types
- đ **Plugin Aware**: Automatically discovers and exposes V2 plugin endpoints
- đ¤ **User Access**: Easy current user retrieval with middleware and utilities
- đ **Introspection**: Built-in API documentation and health checks
- ⥠**Performance**: Optimized for high-throughput scenarios
```bash
npm install @re-auth/http-adapters-v2
pnpm add @re-auth/http-adapters-v2
yarn add @re-auth/http-adapters-v2
```
```typescript
import express from 'express';
import { ReAuthEngineV2 } from '@re-auth/reauth';
import { createExpressAdapter } from '@re-auth/http-adapters-v2';
import { createDefaultLogger } from '@re-auth/logger';
const app = express();
// Create logger instance
const logger = createDefaultLogger({
prefix: 'MyApp',
enabledTags: ['auth', 'session', 'http']
});
const engine = new ReAuthEngineV2({
/* config */,
logger: logger // Required logger instance
});
// Create adapter with device info extraction
const adapter = createExpressAdapter({
engine,
basePath: '/api/auth',
cors: {
origin: ['https://app.example.com'],
credentials: true
},
rateLimit: {
windowMs: 15 * 60 * 1000,
max: 100
}
}, false, async (req) => {
// Extract device information for enhanced security
return {
ip: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
fingerprint: req.get('X-Request-ID'),
geoLocation: req.get('CF-IPCountry'),
deviceType: req.get('CF-Device-Type'),
sessionId: req.session?.id,
};
});
app.use('/api/auth', adapter.createRouter());
// Enable user population on all requests (optional)
app.use(adapter.createUserMiddleware());
app.listen(3000);
```
```typescript
import Fastify from 'fastify';
import { ReAuthEngineV2 } from '@re-auth/reauth';
import { createFastifyAdapter } from '@re-auth/http-adapters-v2';
const fastify = Fastify();
const engine = new ReAuthEngineV2({
/* config */
});
// Create adapter with device info extraction
const adapter = createFastifyAdapter(
{
engine,
basePath: '/api/auth',
},
async (request) => {
// Extract device information for enhanced security
return {
ip: request.ip,
userAgent: request.headers['user-agent'],
fingerprint: request.headers['x-request-id'],
geoLocation: request.headers['cf-ipcountry'],
deviceType: request.headers['cf-device-type'],
};
},
);
fastify.register(adapter.createPlugin());
// Enable user population on all requests (optional)
fastify.register(adapter.createUserPlugin());
fastify.listen({ port: 3000 });
```
```typescript
import { Hono } from 'hono';
import { ReAuthEngineV2 } from '@re-auth/reauth';
import { createHonoAdapter } from '@re-auth/http-adapters-v2';
import { createDefaultLogger } from '@re-auth/logger';
const app = new Hono();
// Create logger instance
const logger = createDefaultLogger({
prefix: 'MyApp',
enabledTags: ['auth', 'session', 'http']
});
const engine = new ReAuthEngineV2({
/* config */,
logger: logger // Required logger instance
});
// Create adapter with device info extraction
const adapter = createHonoAdapter({
engine,
basePath: '/api/auth'
}, async (c) => {
// Extract device information for enhanced security
return {
ip: c.env?.CF_CONNECTING_IP || c.req.header('x-forwarded-for'),
userAgent: c.req.header('user-agent'),
fingerprint: c.req.header('cf-ray'),
country: c.req.header('cf-ipcountry'),
deviceType: c.req.header('cf-device-type'),
timezone: c.req.header('cf-timezone'),
};
});
adapter.registerRoutes(app, '/api/auth');
// Enable user population on all requests (optional)
app.use('*', adapter.createUserMiddleware());
// or use a standalone app
// const authApp = adapter.createApp();
// app.route('/api/auth', authApp);
```
Device information extraction is a core security feature that allows you to capture and analyze request metadata for enhanced authentication, fraud prevention, and analytics. All adapters support customizable device info extraction through their constructor parameters.
Device information enables:
- **Geographic Analysis**: Track user locations and detect suspicious access patterns
- **Device Fingerprinting**: Identify and validate trusted devices
- **Security Monitoring**: Detect unusual access patterns or potential breaches
- **Analytics**: Gather insights about user behavior and platform usage
- **Compliance**: Meet regulatory requirements for access logging
```typescript
const adapter = createExpressAdapter(config, false, async (req) => {
return {
ip: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
fingerprint: req.get('X-Request-ID'),
geoLocation: req.get('CF-IPCountry'),
deviceType: req.get('CF-Device-Type'),
sessionId: req.session?.id,
browserFingerprint: req.get('X-Browser-Fingerprint'),
};
});
```
```typescript
const adapter = createFastifyAdapter(config, async (request) => {
return {
ip: request.ip,
userAgent: request.headers['user-agent'],
fingerprint: request.headers['x-request-id'],
geoLocation: request.headers['cf-ipcountry'],
deviceType: request.headers['cf-device-type'],
timezone: request.headers['cf-timezone'],
};
});
```
```typescript
const adapter = createHonoAdapter(config, async (c) => {
return {
ip: c.env?.CF_CONNECTING_IP || c.req.header('x-forwarded-for'),
userAgent: c.req.header('user-agent'),
fingerprint: c.req.header('cf-ray'),
country: c.req.header('cf-ipcountry'),
deviceType: c.req.header('cf-device-type'),
timezone: c.req.header('cf-timezone'),
datacenter: c.req.header('cf-ray')?.split('-')[0],
};
});
```
For comprehensive device analysis, you can extract extensive metadata:
```typescript
const deviceInfoExtractor = async (request) => {
// Framework-specific extraction logic
const basicInfo = extractBasicInfo(request);
// Additional processing
const enhancedInfo = await enrichDeviceInfo(basicInfo);
// Database lookups, external API calls, etc.
const riskScore = await calculateRiskScore(enhancedInfo);
return {
...enhancedInfo,
riskScore,
trustLevel: riskScore < 0.3 ? 'high' : riskScore < 0.7 ? 'medium' : 'low',
lastSeen: new Date().toISOString(),
};
};
```
1. **Privacy Compliance**: Ensure device info collection complies with privacy regulations (GDPR, CCPA, etc.)
2. **Minimal Collection**: Only collect what's necessary for your security and analytics needs
3. **Secure Storage**: Handle sensitive device data appropriately
4. **Performance**: Keep device info extraction fast to avoid impacting response times
5. **Error Handling**: Gracefully handle extraction failures
## Full Examples
Complete examples with detailed configurations are available in the `examples/` directory:
- **[Express Example](./examples/express-example.ts)** - Full Express.js integration with middleware, security, and error handling
- **[Fastify Example](./examples/fastify-example.ts)** - High-performance Fastify setup with schema validation
- **[Hono Example](./examples/hono-example.ts)** - Edge-optimized Hono deployment for serverless environments
## Getting Current User
The V2 HTTP adapters provide multiple ways to access the current authenticated user in your route handlers.
### Method 1: Using User Middleware (Recommended)
Automatically populate user information on all requests:
#### Express.js
```typescript
// Add user middleware globally
app.use(adapter.createUserMiddleware());
// Access user in any route handler
app.get('/api/profile', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
res.json({
message: 'Profile data',
user: req.user.subject,
sessionValid: req.user.valid,
});
});
```
```typescript
// Register user plugin globally
fastify.register(adapter.createUserPlugin());
// Access user in any route handler
fastify.get('/api/profile', async (request, reply) => {
const user = (request as any).user;
if (!user) {
return reply.status(401).send({ error: 'Authentication required' });
}
return {
message: 'Profile data',
user: user.subject,
sessionValid: user.valid,
};
});
```
```typescript
// Add user middleware globally
app.use('*', adapter.createUserMiddleware());
// Access user in any route handler
app.get('/api/profile', (c) => {
const user = c.get('user');
if (!user) {
return c.json({ error: 'Authentication required' }, 401);
}
return c.json({
message: 'Profile data',
user: user.subject,
sessionValid: user.valid,
});
});
```
Check for current user when needed:
```typescript
// Express
app.get('/api/dashboard', async (req, res) => {
const user = await adapter.getCurrentUser(req);
if (!user) {
return res.status(401).json({ error: 'Authentication required' });
}
res.json({ user: user.subject });
});
// Fastify
fastify.get('/api/dashboard', async (request, reply) => {
const user = await adapter.getCurrentUser(request);
if (!user) {
return reply.status(401).send({ error: 'Authentication required' });
}
return { user: user.subject };
});
// Hono
app.get('/api/dashboard', async (c) => {
const user = await adapter.getCurrentUser(c);
if (!user) {
return c.json({ error: 'Authentication required' }, 401);
}
return c.json({ user: user.subject });
});
```
Handle routes that work with or without authentication:
```typescript
app.get('/api/content', async (req, res) => {
const user = await adapter.getCurrentUser(req);
res.json({
message: 'Content data',
isAuthenticated: !!user,
user: user?.subject || null,
content: user ? 'Premium content' : 'Public content',
});
});
```
The user object contains:
```typescript
interface AuthenticatedUser {
subject: any; // The authenticated user data from the session
token: string; // The session token
valid: boolean; // Whether the session is valid
metadata?: {
// Optional session metadata
expiresAt?: string;
createdAt?: string;
lastAccessed?: string;
[]: any;
};
}
```
The adapters automatically check for session tokens in:
1. **Authorization header**: `Bearer <token>`
2. **Cookies**: `reauth-session=<token>`
3. **Request body**: `{ "token": "<token>" }`
HTTP adapters provide comprehensive logging for all HTTP operations, authentication flows, and middleware execution. All adapters require a logger instance to be passed to the ReAuth engine.
```typescript
import { createDefaultLogger } from '@re-auth/logger';
// Create logger with HTTP-specific tags
const logger = createDefaultLogger({
prefix: 'MyApp',
prefixEnv: 'REAUTH_',
enabledTags: ['auth', 'session', 'http'],
timestampFormat: 'human',
emojis: true
});
// Pass to ReAuth engine
const engine = new ReAuthEngine({
/* config */,
logger: logger // Required
});
```
- **`http`** - HTTP adapter operations (requests, responses, middleware)
- **`request`** - Incoming HTTP requests
- **`response`** - HTTP responses and status codes
- **`middleware`** - Middleware execution and authentication checks
- **`error`** - HTTP errors and exception handling
### Environment Variable Control
```bash
# Enable HTTP and authentication logging
REAUTH_DEBUG=http,auth,session
REAUTH_DEBUG=*
NODE_ENV=production
```
```typescript
// Development output
[] [MyApp] [http] âšī¸ POST /api/auth/login
[] [MyApp] [auth] â
User authentication successful
[] [MyApp] [http] â
200 OK response
// Production output (JSON)
{"level":"info","tags":["http"],"message":"POST /api/auth/login","timestamp":"2024-01-15T10:30:00.000Z","prefix":"MyApp"}
{"level":"info","tags":["auth"],"message":"User authentication successful","timestamp":"2024-01-15T10:30:01.000Z","prefix":"MyApp"}
```
```typescript
import { createHttpAdapterV2 } from '@re-auth/http-adapters-v2';
const adapter = createHttpAdapterV2({
engine: reAuthEngine,
framework: 'express', // 'express' | 'fastify' | 'hono'
basePath: '/api/v2',
cors: {
origin: ['https://app.example.com'],
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
},
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // requests per window
message: 'Too many requests, please try again later.',
},
security: {
helmet: true, // Enable security headers
csrf: false, // Disable CSRF (for API-only usage)
sanitizeInput: true,
sanitizeOutput: false,
},
validation: {
validateInput: true,
maxPayloadSize: 1024 * 1024, // 1MB
allowedFields: [], // Empty = allow all
sanitizeFields: ['email', 'username', 'name'],
},
});
```
```typescript
const adapter = createExpressAdapter({
engine: reAuthEngine,
basePath: '/auth',
cors: {
origin: (origin, callback) => {
// Custom origin validation
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
},
rateLimit: {
windowMs: 15 * 60 * 1000,
max: (req) => (req.user?.isPremium ? 1000 : 100), // Dynamic limits
keyGenerator: (req) => `${req.ip}:${req.headers['user-agent']}`,
skipSuccessfulRequests: true,
},
security: {
helmet: {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
},
},
},
});
```
Execute plugin-specific authentication steps:
```
POST /auth/:plugin/:step
GET /auth/:plugin/:step
PUT /auth/:plugin/:step
PATCH /auth/:plugin/:step
DELETE /auth/:plugin/:step
```
Example:
```bash
curl -X POST http://localhost:3000/api/auth/email-password/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "secret"}'
```
```
GET /session
POST /session
DELETE /session
```
```
GET /plugins
GET /plugins/:plugin
```
```
GET /introspection
GET /health
```
All endpoints return a consistent response format:
```typescript
{
success: boolean;
data?: any;
error?: {
code: string;
message: string;
details?: any;
};
meta: {
timestamp: string;
requestId?: string;
};
}
```
```json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"subject": {
"id": "user123",
"email": "user@example.com"
}
},
"meta": {
"timestamp": "2024-01-01T00:00:00.000Z"
}
}
```
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": {
"field": "email",
"value": "invalid-email"
}
},
"meta": {
"timestamp": "2024-01-01T00:00:00.000Z"
}
}
```
```typescript
cors: {
origin: ['https://app.example.com'],
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
methods: ['GET', 'POST', 'PUT', 'DELETE']
}
```
```typescript
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // requests per window
keyGenerator: (req) => req.ip,
skipSuccessfulRequests: false
}
```
```typescript
security: {
helmet: true, // Enables:
// - X-Content-Type-Options: nosniff
// - X-Frame-Options: DENY
// - X-XSS-Protection: 1; mode=block
// - Referrer-Policy: strict-origin-when-cross-origin
// - Strict-Transport-Security (HTTPS only)
}
```
```typescript
validation: {
validateInput: true,
maxPayloadSize: 1024 * 1024, // 1MB
sanitizeFields: ['email', 'username', 'name'],
allowedFields: [] // Empty = allow all
}
```
The adapter provides comprehensive error handling with specific error types:
```typescript
import { HttpAdapterError, ValidationError, AuthenticationError, AuthorizationError, NotFoundError, RateLimitError } from '@re-auth/http-adapters-v2';
// Custom error handling
app.use((err, req, res, next) => {
if (err instanceof ValidationError) {
res.status(400).json({
success: false,
error: {
code: err.code,
message: err.message,
details: err.details,
},
});
}
// ... handle other error types
});
```
The V2 adapter is designed to work alongside V1 adapters. Key differences:
1. **Engine Integration**: Uses `ReAuthEngineV2` instead of `ReAuthEngine`
2. **Plugin Discovery**: Automatically discovers V2 plugins and their steps
3. **Type Safety**: Enhanced TypeScript integration
4. **Response Format**: Consistent JSON response format
5. **Security**: Built-in security features
1. Install the V2 adapter package
2. Update your engine to `ReAuthEngineV2`
3. Replace V1 adapter imports with V2 equivalents
4. Update configuration format
5. Test endpoints and response formats
MIT