Transport-agnostic authentication and authorization for TypeScript. Ships with JWT and cookie schemes, a fluent policy builder, and a typed Principal value object. Designed for @cleverbrush/server but usable in any Node.js context.
AuthenticationContext (headers + cookies + items), not a raw HTTP request.Principal<T> — immutable, typed value object exposing .hasRole(), .hasClaim(), .isAuthenticated, and .claims.jwtScheme() supports HS256/HS384/HS512 and RS256/RS384/RS512; configurable issuer, audience, clock tolerance, and role claim.cookieScheme() with a user-supplied async validate() function (session lookup, signature check, etc.).PolicyBuilder with .requireRole() / .require(predicate) fluent API; AuthorizationService.authorize() accepts requirements or a named policy.requireRole() helper — convenience factory for single-call role requirements.parseCookies() and serializeCookie() for low-level cookie handling.crypto.npm install @cleverbrush/auth
import { jwtScheme, Principal } from '@cleverbrush/auth';
type UserClaims = { sub: string; role: string };
const jwt = jwtScheme<UserClaims>({
secret: process.env.JWT_SECRET!,
mapClaims: claims => ({
sub: claims.sub as string,
role: claims.role as string
})
});
// Authenticate a request context (built from HTTP headers)
const result = await jwt.authenticate({
headers: { authorization: 'Bearer <token>' },
cookies: {},
items: new Map()
});
if (result.succeeded) {
const principal: Principal<UserClaims> = result.principal;
principal.isAuthenticated; // true
principal.hasRole('admin'); // boolean
principal.value.sub; // string
}
import { jwtScheme, signJwt } from '@cleverbrush/auth';
// Sign a token (for testing or token issuance)
const token = signJwt({
sub: 'user-1', role: 'admin',
iss: 'https://my-app.com', aud: 'my-api',
exp: Math.floor(Date.now() / 1000) + 3600
}, 'my-secret');
// Verify and authenticate
const scheme = jwtScheme({
secret: 'my-secret',
algorithms: ['HS256'], // default
issuer: 'https://my-app.com', // optional iss check
audience: 'my-api', // optional aud check
clockTolerance: 5, // seconds
roleClaim: 'role', // default
mapClaims: c => ({ sub: c.sub as string, role: c.role as string })
});
Supported algorithms: HS256, HS384, HS512, RS256, RS384, RS512.
Configure a single key family per scheme: HMAC secrets and RSA keys must never share an algorithm allowlist. RSA requires an RSA PEM key; HMAC rejects empty secrets and PEM key material. Use a cryptographically random secret from secure configuration, not the illustrative value above. Unsupported critical JOSE extensions, malformed claim types and tokens at their expiration instant are rejected. Clock tolerance must be finite and non-negative.
See security guidance and the v4.x-to-v5 migration guide.
import { cookieScheme } from '@cleverbrush/auth';
const cookie = cookieScheme<{ userId: string; role: string }>({
cookieName: 'session',
validate: async (cookieValue) => {
// Look up session from DB, verify signature, etc.
const session = await sessionStore.get(cookieValue);
if (!session) return null;
return { userId: session.userId, role: session.role };
}
});
import { PolicyBuilder, requireRole } from '@cleverbrush/auth';
const adminPolicy = new PolicyBuilder()
.requireRole('admin')
.require(principal => principal.hasClaim('verified', 'true'))
.build('admin-only');
import { AuthorizationService } from '@cleverbrush/auth';
const policies = new Map([['admin-only', adminPolicy]]);
const authz = new AuthorizationService(policies);
// Check by requirements array
const result = await authz.authorize(principal, [requireRole('admin')]);
// Check by named policy
const result2 = await authz.authorize(principal, 'admin-only');
if (!result.allowed) {
console.log(result.reason); // 'Not authenticated' | 'Forbidden'
}
import { parseCookies, serializeCookie } from '@cleverbrush/auth';
// Parse Cookie header
const cookies = parseCookies('session=abc123; theme=dark');
// { session: 'abc123', theme: 'dark' }
// Build Set-Cookie header value
const header = serializeCookie('session', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'Lax',
maxAge: 3600
});
import { ServerBuilder } from '@cleverbrush/server';
import { jwtScheme } from '@cleverbrush/auth';
const server = new ServerBuilder();
server
.useAuthentication({
defaultScheme: 'jwt',
schemes: [jwtScheme({ secret: process.env.JWT_SECRET!, mapClaims: c => c })]
})
.useAuthorization();
await server.listen(3000);
BSD-3-Clause — see LICENSE.