@nathapp/nestjs-auth
v4.1.0
Published
nestjs-auth
Readme
@nathapp/nestjs-auth
JWT authentication + CASL authorization library for NestJS.
Installation
npm install @nathapp/nestjs-auth @nestjs/passportNote for Fastify users: There is slightly different request/response handling for Fastify. Interceptors and middleware check
req.rawfor Fastify vs Express compatibility.
JWKS (Remote Identity Provider)
To verify tokens issued by an external IdP (Auth0, Cognito, Okta, Keycloak), install the optional jwks-rsa peer dependency:
npm install jwks-rsaThen configure your module:
import { Module } from '@nestjs/common';
import { AuthenticationModule, createJwksStrategyProvider } from '@nathapp/nestjs-auth';
import { MyAuthProvider } from './my-auth-provider';
@Module({
imports: [
AuthenticationModule.forRoot({
jwtOptions: {
signOption: { algorithm: 'RS256' },
issuer: 'https://my-tenant.auth0.com/',
audience: 'https://api.example.com',
jwks: {
jwksUri: 'https://my-tenant.auth0.com/.well-known/jwks.json',
// optional tuning:
// cacheMaxAge: 600000,
// jwksRequestsPerMinute: 10,
// algorithms: ['RS256'],
},
},
authProvider: MyAuthProvider,
}),
],
// Override the default JwtStrategyProvider with the JWKS factory.
// Must be registered AFTER AuthenticationModule so DI resolves this provider.
providers: [createJwksStrategyProvider()],
})
export class AppModule {}Note: Don't pass
strategyProvider: JwksJwtStrategyProviderdirectly — this monorepo usesuseFactoryregistration (see CLAUDE.md). ThecreateJwksStrategyProvider()helper wiresJWT_OPTIONS+AUTH_PROVIDERinjection for you.
JWKS Security Defaults
issueris required when using JWKS (prevents accepting tokens from any key the URI serves).algorithmsdefaults to['RS256']. HMAC algorithms (HS*) andnoneare rejected.jwksUrimust use HTTPS (setallowInsecure: trueonly for local dev against a mock server).sign()throws in JWKS mode — the strategy is verify-only.
validate<T> is an unchecked cast
DefaultJwtStrategyProvider.validate<T>() returns the principal produced by your
AuthProvider via an unchecked cast (principal as any as T). The library cannot verify
that your provider actually returns the declared type: if your AuthProvider is
typed to return IPrincipal but returns a differently shaped object, the mismatch
surfaces at runtime, not compile time. Consumers must ensure their
AuthProvider.getPrincipal() returns data compatible with the T declared at the
call site (defaults to IPrincipal).
Token purpose (typ) and refresh configuration
Access and refresh tokens carry a purpose payload claim typ: 'access' | 'refresh'
(distinct from the JWT JOSE header's typ). The built-in access provider signs object
payloads with typ: 'access'; the refresh provider always signs with typ: 'refresh'.
Verification enforces the matching purpose, so an access token can no longer be replayed
at a refresh endpoint (or vice versa) even when both use the same signing key.
Migration policy
- Existing unmarked refresh tokens stop working. A refresh token without
typ: 'refresh'is rejected byJwtRefreshStrategyand by the built-in refresh provider. There is no opt-out to accept unmarked refresh tokens. - Mint refresh tokens through the refresh strategy provider (e.g.
JwtRefreshStrategyProvider.sign({ sub })) or, for external issuers, include thetyp: 'refresh'payload claim in the token you issue. - Distinct access/refresh secrets alone are not a substitute for marking newly issued refresh tokens under this policy. Purpose is enforced independently of the key.
- Because previously issued refresh tokens cannot be re-marked, a planned rollout may require reauthentication of existing sessions before the new library is deployed.
- Refresh signing requires an object payload so the claim can be attached. Previously
accepted
string/Bufferpayloads must be converted to objects (e.g.provider.sign(String(payload))→provider.sign({ value: payload })). Access signing still accepts opaquestring/Bufferpayloads, which are treated as legacy unmarked access tokens and remain valid.
Refresh options are independent of access options
Refresh options reuse the access secrets/keys only when the refresh provides no key
material of its own, and the refresh TTL defaults to 7h regardless of the access TTL
(the access algorithm is inherited only when keys are shared and the refresh algorithm is
absent). Access TTL, issuer/audience sign claims, and unrelated validation flags are not
inherited. Configure refresh issuer/audience and their matching verification claims
explicitly when the refresh tokens need issuer/audience scoping:
AuthenticationModule.forRoot({
jwtOptions: {
secret: 'access-secret',
signOption: { expiresIn: '15m', issuer: 'my-issuer' },
issuer: 'my-issuer',
},
refreshJwtOptions: {
secret: 'refresh-secret', // or omit to share the access key
signOption: { expiresIn: '7h', issuer: 'my-issuer' },
issuer: 'my-issuer', // verification must match the signed claim
},
});