Middleware¶
Pre-built Express and Passport.js integration.
Express JWT Middleware¶
Create a ready-to-use Express middleware that validates JWTs and attaches the payload to req.user.
import { createJwtMiddleware } from '@authvital/sdk/server';
const requireAuth = createJwtMiddleware({
authVitalHost: process.env.AV_HOST!,
audience: 'my-client-id', // Optional but recommended
});
Basic Usage¶
// Protect single route
app.get('/api/protected', requireAuth, (req, res) => {
// req.user contains the full JWT payload
console.log('User ID:', req.user.sub);
console.log('Tenant:', req.user.tenant_id);
console.log('Roles:', req.user.app_roles);
res.json({ message: `Hello, ${req.user.email}!` });
});
// Protect entire router
const apiRouter = express.Router();
apiRouter.use(requireAuth);
apiRouter.get('/me', (req, res) => res.json(req.user));
apiRouter.get('/settings', (req, res) => { /* ... */ });
app.use('/api', apiRouter);
Configuration Options¶
interface JwtMiddlewareOptions {
authVitalHost: string; // Required: AuthVital server URL
audience?: string; // Optional: Expected JWT audience (client ID)
issuer?: string; // Optional: Override expected issuer
algorithms?: string[]; // Optional: Allowed algorithms (default: ['RS256'])
}
Error Responses¶
The middleware returns appropriate HTTP errors:
| Status | Error | Cause |
|---|---|---|
| 401 | No token provided | Missing Authorization header/cookie |
| 401 | Token expired | JWT exp claim in the past |
| 401 | Invalid token | Bad signature, malformed JWT |
| 401 | Invalid audience | Token audience doesn't match |
Passport.js Integration¶
For apps using Passport.js, get pre-configured JWT strategy options.
import passport from 'passport';
import { Strategy as JwtStrategy } from 'passport-jwt';
import { createPassportJwtOptions } from '@authvital/sdk/server';
// Get options (fetches JWKS automatically)
const jwtOptions = await createPassportJwtOptions({
authVitalHost: process.env.AV_HOST!,
audience: 'my-client-id',
});
// Configure Passport
passport.use(new JwtStrategy(jwtOptions, (payload, done) => {
// payload contains the full JWT claims
// Optionally enrich with database lookup
// User.findById(payload.sub).then(user => done(null, user || payload));
done(null, payload);
}));
// Use in routes
app.get('/api/protected',
passport.authenticate('jwt', { session: false }),
(req, res) => {
res.json({ user: req.user });
}
);
What createPassportJwtOptions() Returns¶
interface PassportJwtOptions {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken();
secretOrKeyProvider: (req, token, done) => Promise<void>;
audience?: string;
issuer: string;
algorithms: ['RS256'];
}
JWKS Caching
The secretOrKeyProvider automatically fetches and caches the JWKS from AuthVital for signature verification.
Permission Middleware Factory¶
Combine with the SDK for permission-based middleware:
import { createAuthVital, createJwtMiddleware } from '@authvital/sdk/server';
const authvital = createAuthVital({ /* config */ });
const requireAuth = createJwtMiddleware({
authVitalHost: process.env.AV_HOST!,
});
// Create permission-checking middleware
const requireAppPermission = (permission: string) => {
return async (req, res, next) => {
if (!await authvital.hasAppPermission(req, permission)) {
return res.status(403).json({
error: 'Forbidden',
required: permission,
});
}
next();
};
};
// Create feature-gating middleware
const requireFeature = (feature: string) => {
return async (req, res, next) => {
if (!await authvital.hasFeatureFromJwt(req, feature)) {
return res.status(402).json({
error: 'Feature requires upgrade',
feature,
});
}
next();
};
};
// Stack middlewares
app.delete('/api/projects/:id',
requireAuth,
requireAppPermission('projects:delete'),
async (req, res) => {
await deleteProject(req.params.id);
res.json({ deleted: true });
}
);
app.get('/api/advanced-analytics',
requireAuth,
requireFeature('advanced-analytics'),
async (req, res) => {
res.json({ analytics: '...' });
}
);
Tenant Role Middleware¶
const requireTenantRole = (...roles: string[]) => {
return async (req, res, next) => {
const tenantRoles = await authvital.getTenantRoles(req);
const hasRole = roles.some(r => tenantRoles.includes(r));
if (!hasRole) {
return res.status(403).json({
error: 'Forbidden',
required: roles,
current: tenantRoles,
});
}
next();
};
};
// Only owners and admins
app.delete('/api/team/:id',
requireAuth,
requireTenantRole('owner', 'admin'),
async (req, res) => {
// Remove team member
}
);
Complete Example: Protected API¶
import express from 'express';
import { createAuthVital, createJwtMiddleware } from '@authvital/sdk/server';
const authvital = createAuthVital({
authVitalHost: process.env.AV_HOST!,
clientId: process.env.AV_CLIENT_ID!,
clientSecret: process.env.AV_CLIENT_SECRET!,
});
const requireAuth = createJwtMiddleware({
authVitalHost: process.env.AV_HOST!,
audience: process.env.AV_CLIENT_ID,
});
const app = express();
// Public routes
app.get('/api/health', (req, res) => res.json({ ok: true }));
// Protected routes
const protectedRouter = express.Router();
protectedRouter.use(requireAuth);
protectedRouter.get('/me', async (req, res) => {
res.json({ user: req.user });
});
protectedRouter.get('/team', async (req, res) => {
const { memberships } = await authvital.memberships.listForTenant(req);
res.json(memberships);
});
protectedRouter.post('/invite', async (req, res) => {
// Check permission first
if (!await authvital.hasTenantPermission(req, 'members:invite')) {
return res.status(403).json({ error: 'Cannot invite members' });
}
const result = await authvital.invitations.send(req, {
email: req.body.email,
});
res.status(201).json(result);
});
app.use('/api', protectedRouter);
app.listen(3000);