@autolabz/service-auth-fastify
v1.0.1
Published
Fastify authentication middleware for AutoLab services
Readme
@autolabz/service-auth-fastify
Fastify authentication middleware for AutoLab services: centralized authentication with fallback to OAuth userinfo.
Version 1.0.0 - This is a major version upgrade from
@autolabz/service-auth-middlewarev0.2.x with breaking changes. See Migration from v0.2.x below.
Features
- Unified authentication chain: Prioritize SIMPLE JWT verification, fallback to OAuth userinfo
- OAuth validation:
- X-Client-Id matches azp (or userinfo.client_id)
- Support for required scopes subset validation
- Flexible: iss/aud validation only when JWT claims exist; skip during userinfo fallback
- Framework-agnostic core: Built on
@autolabz/service-auth-corefor maximum reusability
Installation
npm install @autolabz/service-auth-fastifyQuick Start
Use the all-in-one plugin for the simplest integration:
import Fastify from 'fastify';
import { authPlugin } from '@autolabz/service-auth-fastify';
const app = Fastify({ logger: true });
const authCfg = {
jwtAlg: 'HS256',
jwtAccessSecret: process.env.JWT_ACCESS_SECRET,
authBaseUrl: process.env.AUTH_BASE_URL!,
oauthUserinfoPath: '/oauth/userinfo',
oauthUserinfoTimeoutMs: 2000,
};
app.register(authPlugin, {
authConfig: authCfg,
clientId: {},
enforce: { requiredScopes: ['data'] }, // Optional: require 'data' scope
});
app.get('/api/hello', async (req, reply) => {
const userId = req.auth?.userId;
const clientId = req.clientId;
return { message: `Hello, user ${userId} from client ${clientId}` };
});
app.listen({ port: 3000, host: '0.0.0.0' });Usage with Downstream Services
Use makeAuthBridgeFromRequest to create an AuthBridge that transparently forwards authentication to downstream services:
import { makeAuthBridgeFromRequest } from '@autolabz/service-auth-fastify';
import { createPointsClient } from '@autolabz/points-sdk';
import { createDataClient } from '@autolabz/data-sdk';
import { createLLMClient } from '@autolabz/llmapi-sdk';
app.get('/api/user/balance', async (req, reply) => {
// Create AuthBridge from incoming request
const auth = makeAuthBridgeFromRequest(req, {
onUnauthorized: () => {
req.log.warn('Downstream service returned 401');
},
});
// Pass to SDK
const pointsClient = createPointsClient({
baseURL: process.env.POINTS_BASE_URL!,
auth,
});
const balance = await pointsClient.getMyBalance();
return reply.send(balance);
});Configuration Reference
AuthConfig
| Key | Description | Required | Default |
| --- | --- | --- | --- |
| jwtAlg | SIMPLE mode algorithm: 'HS256' or 'RS256' | Yes | - |
| jwtAccessSecret | HS256 local verification secret | Yes (when jwtAlg=HS256) | - |
| jwksUrl | RS256 JWK Set URL | Yes (when jwtAlg=RS256) | - |
| authIssuer | Expected issuer (validates only when JWT claim exists) | No | - |
| authBaseUrl | OAuth base URL | Yes | - |
| oauthUserinfoPath | Userinfo endpoint path | No | 'oauth/userinfo' |
| oauthUserinfoTimeoutMs | Userinfo request timeout (ms) | No | 2000 |
| oauthExpectedAudience | Expected audience value | No | - |
EnforceOptions
| Key | Description | Default |
| --- | --- | --- |
| requiredScopes | Array of required scopes (subset check) | [] (no check) |
| enforceForSimple | Enforce scope check for SIMPLE mode | false |
Environment Variables
Typical environment variable setup:
# SIMPLE mode (local JWT verification)
JWT_ALG=HS256
JWT_ACCESS_SECRET=your-secret
# OAuth userinfo fallback
AUTH_BASE_URL=http://auth-service:4001/api
OAUTH_USERINFO_PATH=/oauth/userinfo
OAUTH_USERINFO_TIMEOUT_MS=2000
# Optional
AUTH_ISSUER=https://auth.example.com
OAUTH_EXPECTED_AUDIENCE=autolab-apiAdvanced Usage
Custom Scope Requirements per Route
import { oauthEnforceClientScope } from '@autolabz/service-auth-fastify';
app.get('/api/admin/users', {
preHandler: oauthEnforceClientScope(authCfg, { requiredScopes: ['admin'] }),
}, async (req, reply) => {
// This route requires 'admin' scope
return { users: [] };
});Manual Middleware Chain
If you prefer manual control over the middleware chain:
import {
oauthOrSimpleAuth,
clientIdMiddleware,
oauthEnforceClientScope
} from '@autolabz/service-auth-fastify';
app.addHook('onRequest', oauthOrSimpleAuth(authCfg));
app.addHook('onRequest', clientIdMiddleware({}));
app.addHook('onRequest', oauthEnforceClientScope(authCfg, { requiredScopes: ['data'] }));Request Properties
After authentication, the following properties are added to the request:
req.auth:AuthPayloadobjectuserId: User ID (UUID)sub?: Subjectemail?: User emailiss?: Issueraud?: Audienceazp?: Authorized party (client ID)scope?: Token scopes (space-separated)tokenType?: Token type
req.clientId: Client ID fromX-Client-Idheader orclient_idquery parameter
Migration from v0.2.x
Breaking Changes
- Package name changed:
@autolabz/service-auth-middleware→@autolabz/service-auth-fastify - Core logic extracted: JWT/userinfo logic moved to
@autolabz/service-auth-core - Plugin name updated: Internal plugin name changed from
@autolabz/service-auth-middleware:authPluginto@autolabz/service-auth-fastify:authPlugin
Migration Steps
- Update
package.json:
{
"dependencies": {
- "@autolabz/service-auth-middleware": "^0.2.2"
+ "@autolabz/service-auth-fastify": "^1.0.0"
}
}- Update imports:
- import { authPlugin } from '@autolabz/service-auth-middleware';
+ import { authPlugin } from '@autolabz/service-auth-fastify';- No code changes required - the API remains the same!
Troubleshooting
401 Unauthorized
- Check
AUTH_BASE_URLandOAUTH_USERINFO_PATHare correct - Verify
Authorizationheader usesBearer <token>format - Confirm token has required scopes
X-Client-Id Mismatch
- Ensure gateway doesn't forward external
X-Client-Idheaders - Let backend set this header consistently
Request Timeout
- Increase
OAUTH_USERINFO_TIMEOUT_MS - Check auth service performance and network connectivity
JWT Verification Failed
- HS256: Verify
JWT_ACCESS_SECRETmatches auth service - RS256: Ensure
JWKS_URLis accessible andkidmatches
License
MIT
