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.
- Transport-agnostic — authentication schemes receive a plain
AuthenticationContext(headers + cookies + items), not a raw HTTP request. Principal<T>— immutable, typed value object exposing.hasRole(),.hasClaim(),.isAuthenticated, and.claims.- JWT scheme —
jwtScheme()supports HS256/HS384/HS512 and RS256/RS384/RS512; configurable issuer, audience, clock tolerance, and role claim. - Cookie scheme —
cookieScheme()with a user-supplied asyncvalidate()function (session lookup, signature check, etc.). - Authorization —
PolicyBuilderwith.requireRole()/.require(predicate)fluent API;AuthorizationService.authorize()accepts requirements or a named policy. requireRole()helper — convenience factory for single-call role requirements.- Cookie utilities —
parseCookies()andserializeCookie()for low-level cookie handling. - Zero runtime dependencies — uses only Node.js built-in
crypto.
npm install @cleverbrush/authimport { 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.