@sftech/nestjs-auth-backend
v1.6.0
Published
A standalone, reusable NestJS library for OAuth 2.0 / OIDC authentication with Authorization Code Flow.
Readme
@sftech/nestjs-auth-backend
A standalone, reusable NestJS library for OAuth 2.0 / OIDC authentication with Authorization Code Flow.
This library provides a complete authentication microservice that can be used as a central auth service for your microservices architecture.
Features
- OAuth 2.0 / OIDC Authorization Code Flow with PKCE support
- Database-backed session management using TypeORM
- Session-based authentication with HTTP-only cookies
- Token management (Access Token, Refresh Token, ID Token)
- Single Sign-On (SSO) with provider logout support
- Dynamic post-login redirect URL with OWASP open-redirect protection (v1.4.0+)
- Clean Architecture (domain, application, infrastructure, presentation)
- REST API endpoints for authentication flows
Installation
npm install @sftech/nestjs-auth-backendPeer Dependencies
npm install @sftech/nestjs-core @sftech/nestjs-dbUsage
1. Configure Module
import { Module } from '@nestjs/common';
import { NestjsAuthBackendModule } from '@sftech/nestjs-auth-backend';
@Module({
imports: [
NestjsAuthBackendModule.register({
// Module configuration options
}),
],
})
export class AppModule {}2. Configuration
The module uses configuration values from @sftech/nestjs-core configuration system. Add the following to your app.config.json:
{
"OAUTH_PROVIDER_URL": "https://your-oauth-provider.com/realms/your-realm",
"OAUTH_AUTHORIZATION_ENDPOINT": "https://your-oauth-provider.com/realms/your-realm/protocol/openid-connect/auth",
"OAUTH_TOKEN_ENDPOINT": "https://your-oauth-provider.com/realms/your-realm/protocol/openid-connect/token",
"OAUTH_USERINFO_ENDPOINT": "https://your-oauth-provider.com/realms/your-realm/protocol/openid-connect/userinfo",
"OAUTH_END_SESSION_ENDPOINT": "https://your-oauth-provider.com/realms/your-realm/protocol/openid-connect/logout",
"OAUTH_CLIENT_ID": "your-client-id",
"OAUTH_CLIENT_SECRET": "your-client-secret",
"OAUTH_REDIRECT_URL": "http://localhost:3000/api/auth/callback",
"OAUTH_FRONTEND_CALLBACK_URL": "http://localhost:4200/auth/callback",
"OAUTH_SCOPE": "openid profile email roles",
"OAUTH_SESSION_MODE": "single",
"OAUTH_SESSION_MAX_AGE": "3600000",
"OAUTH_ALLOWED_REDIRECT_ORIGINS": "https://localhost,http://localhost:4200,https://myapp.example.com"
}Configuration Parameters
| Parameter | Required | Description | Example |
|-----------|----------|-------------|---------|
| OAUTH_PROVIDER_URL | Yes | Base URL of OAuth provider | https://keycloak.example.com/realms/myrealm |
| OAUTH_AUTHORIZATION_ENDPOINT | Yes | OAuth authorization endpoint | https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth |
| OAUTH_TOKEN_ENDPOINT | Yes | OAuth token endpoint | https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token |
| OAUTH_USERINFO_ENDPOINT | Yes | OAuth userinfo endpoint | https://keycloak.example.com/realms/myrealm/protocol/openid-connect/userinfo |
| OAUTH_END_SESSION_ENDPOINT | No | OAuth logout endpoint (for SSO logout) | https://keycloak.example.com/realms/myrealm/protocol/openid-connect/logout |
| OAUTH_CLIENT_ID | Yes | OAuth client ID | my-app |
| OAUTH_CLIENT_SECRET | Yes | OAuth client secret | your-secret-here |
| OAUTH_REDIRECT_URL | Yes | OAuth callback URL (this service's own endpoint) | http://localhost:3000/api/auth/callback |
| OAUTH_FRONTEND_CALLBACK_URL | No | Default frontend URL to redirect to after login | http://localhost:4200/auth/callback |
| OAUTH_SCOPE | Yes | OAuth scopes to request | openid profile email roles |
| OAUTH_SESSION_MODE | No | Session mode: single or multi | single (default) |
| OAUTH_SESSION_MAX_AGE | No | Session max age in milliseconds | 3600000 (1 hour, default) |
| OAUTH_ALLOWED_REDIRECT_ORIGINS | No | Comma-separated list of origins allowed as post-login redirect targets (v1.4.0+) | https://localhost,http://localhost:4200 |
| OAUTH_ROLE_CLAIM_PATH | Required for /api-keys | Dot-separated path into the OAuth userinfo response or its additionalClaims where the caller's role list lives (v1.6.0+) | realm_access.roles (Keycloak) or roles |
| OAUTH_ADMIN_ROLES | Required for /api-keys | Comma-separated list of role identifiers that grant administrative access to API key management endpoints (v1.6.0+) | admin,api-key-manager |
Notes:
- Session cookie name is fixed to
oauth_session(not configurable). - When
OAUTH_ALLOWED_REDIRECT_ORIGINSis not set, the library derives a single allowed origin fromOAUTH_FRONTEND_CALLBACK_URLautomatically. - When
OAUTH_ROLE_CLAIM_PATHandOAUTH_ADMIN_ROLESare unset, all/api-keysmanagement endpoints return HTTP 503 — fail-closed (v1.6.0+).
3. Keycloak Example
For Keycloak, the endpoints follow this pattern:
{
"OAUTH_PROVIDER_URL": "https://your-keycloak.com/realms/your-realm",
"OAUTH_AUTHORIZATION_ENDPOINT": "https://your-keycloak.com/realms/your-realm/protocol/openid-connect/auth",
"OAUTH_TOKEN_ENDPOINT": "https://your-keycloak.com/realms/your-realm/protocol/openid-connect/token",
"OAUTH_USERINFO_ENDPOINT": "https://your-keycloak.com/realms/your-realm/protocol/openid-connect/userinfo",
"OAUTH_END_SESSION_ENDPOINT": "https://your-keycloak.com/realms/your-realm/protocol/openid-connect/logout"
}Authentication and Authorization Model (v1.6.0+)
Security advisory —
@sftech/[email protected]shipped theApiKeyControllerwithout any authentication guard. Any unauthenticated caller could enumerate, create, rotate, or revoke API keys. All<= 1.5.0versions are vulnerable; upgrade to1.6.0. If you cannot upgrade immediately, block/api-keysat the reverse-proxy edge (see "Interim mitigation" below).
Per-route policy
| Route | Authentication | Authorization | Rate limit |
|---|---|---|---|
| GET /auth/login | public | — | — |
| GET /auth/callback | public | — | — |
| GET /auth/status | public | — | — |
| GET /auth/user | session | — | — |
| POST /auth/refresh | session | — | — |
| POST /auth/logout | session | — | — |
| POST /auth/anonymous | public | — | per-IP (10 req / 60 s) |
| POST /auth/anonymous/recover | public | — | per-IP (10 req / 60 s) |
| GET /api-keys | session | admin role required | — |
| GET /api-keys/:id | session | admin role required | — |
| POST /api-keys | session | admin role required | — |
| POST /api-keys/:id/rotate | session | admin role required | — |
| DELETE /api-keys/:id | session | admin role required | — |
| POST /api-keys/validate | public (@Public()) | — | per-IP (60 req / 60 s) |
Configuring admin role enforcement
API key management endpoints require BOTH a valid session AND that the caller holds one of the configured admin roles. Without configuration, those endpoints fail closed with HTTP 503.
{
"OAUTH_ROLE_CLAIM_PATH": "realm_access.roles",
"OAUTH_ADMIN_ROLES": "admin,api-key-manager"
}OAUTH_ROLE_CLAIM_PATHis a dot-separated path resolved against the OAuthuserinforesponse. The library reads from the standard fields and fromadditionalClaims. Userealm_access.rolesfor Keycloak;rolesfor providers that emit a top-level array;https://example.com/roles(with the dots quoted in your config layer if necessary) for custom namespaced claims.OAUTH_ADMIN_ROLESis a comma-separated list. A caller passes the admin check if any of their resolved roles appears in this list.- The library tolerates two claim shapes: a string array (
["admin", "user"]) or a comma-separated string ("admin,user"). Any other shape (object, number, missing) is treated as "no roles" — never as "any role". - If the consuming service starts without these keys configured, the startup log prints a
WARNline; calls to management endpoints respond with HTTP 503 and a clear error message.
The validate endpoint
POST /api-keys/validate is the only /api-keys route that does not require a session. It is intended for backend-to-backend traffic that authenticates inbound requests using an API key. To prevent brute-force enumeration of the key space, the library applies a per-source-IP rate limiter (default 60 requests / 60 seconds, in-memory fixed window) before any database lookup. When the budget is exceeded, the response is HTTP 429 with a retryAfter value.
Upgrading from 1.5.0
- Upgrade the package:
pnpm up @sftech/[email protected]. - Add the two new configuration keys (
OAUTH_ROLE_CLAIM_PATH,OAUTH_ADMIN_ROLES) to your config layer. - Verify that your OAuth provider includes the role claim in
userinfo. For Keycloak, therealm_access.rolesclaim is included by default for confidential clients with theview-rolesscope. - Sanity-check after deploy:
curl -i https://your-host/api/api-keysshould return HTTP 401 (no session).curl -i --cookie "oauth_session=<valid>" https://your-host/api/api-keysreturns HTTP 403 if the session user has no admin role, HTTP 200 if they do.- If you forgot to set the env keys, you will see HTTP 503 with the message "Admin role enforcement is not configured".
Interim mitigation (if you cannot upgrade immediately from 1.5.0)
location /api/api-keys {
return 404;
}Reload nginx. This is no-functional-loss as long as the consuming service does not call /api-keys from external traffic. Remove the block once you have upgraded to 1.6.0 and configured the admin role keys.
REST API Endpoints
The module provides the following REST endpoints under /api/auth:
GET /auth/login
Initiates OAuth login flow by redirecting to the OAuth provider.
Query Parameters:
| Parameter | Required | Description |
|-----------|----------|-------------|
| redirect_url | No | Post-login redirect URL (see validation rules below) |
Successful response: 302 Redirect to OAuth provider authorization endpoint.
Error response: 400 Bad Request when redirect_url is provided but fails validation.
redirect_url Validation Rules (v1.4.0+)
When redirect_url is supplied the library enforces the following rules in order. Any rule failure returns HTTP 400 with a distinct error message — the request is never silently redirected to a fallback.
| Rule | Accepted | Rejected |
|------|----------|---------|
| Empty / whitespace-only | Treated as absent; falls back to OAUTH_FRONTEND_CALLBACK_URL | — |
| Relative path starting with / (not //) | redirect_url=/auth/callback | redirect_url=//evil.com/auth/callback |
| Absolute URL — must be parseable | https://localhost/auth/callback | not-a-url |
| Absolute URL — origin must be in the allowlist | Origin is in OAUTH_ALLOWED_REDIRECT_ORIGINS | https://evil.com/auth/callback |
| Absolute URL — path must be exactly /auth/callback | https://localhost/auth/callback | https://localhost/dashboard |
| Absolute URL — must contain no query parameters | https://localhost/auth/callback | https://localhost/auth/callback?foo=bar |
400 error messages:
| Condition | Message |
|-----------|---------|
| Not a valid URL format | Invalid redirect URL: not a valid URL format |
| Origin not in allowlist | Invalid redirect URL: origin not in allowlist |
| Path is not /auth/callback | Invalid redirect URL: path must be /auth/callback |
| Query parameters present | Invalid redirect URL: must not contain query parameters |
Examples:
# Valid — accepted, user is redirected here after login
GET /auth/login?redirect_url=https%3A%2F%2Flocalhost%2Fauth%2Fcallback
# Valid — Capacitor mobile (https://localhost)
GET /auth/login?redirect_url=https%3A%2F%2Flocalhost%2Fauth%2Fcallback
# Valid — Angular dev server
GET /auth/login?redirect_url=http%3A%2F%2Flocalhost%3A4200%2Fauth%2Fcallback
# Valid — production SPA
GET /auth/login?redirect_url=https%3A%2F%2Fmyapp.example.com%2Fauth%2Fcallback
# Invalid — origin not in allowlist → 400
GET /auth/login?redirect_url=https%3A%2F%2Fevil.com%2Fauth%2Fcallback
# Invalid — wrong path → 400
GET /auth/login?redirect_url=https%3A%2F%2Flocalhost%2Fdashboard
# No redirect_url — falls back to OAUTH_FRONTEND_CALLBACK_URL (backward compatible)
GET /auth/loginGET /auth/callback
Handles OAuth callback after user authentication. Exchanges authorization code for tokens and creates a session.
Query Parameters:
code(required): Authorization code from OAuth providerstate(required): State parameter for CSRF validation
Response: 302 Redirect
- On success: redirects to the
redirect_urlthat was stored in the PKCE challenge (orOAUTH_FRONTEND_CALLBACK_URLif none was provided during login). - On error: redirects to the same target URL with
?error=<code>appended (see Dynamic Error Redirect below).
GET /auth/status
Returns current authentication status. Does not require authentication.
Response:
{
"authenticated": true,
"userId": "user-id",
"email": "[email protected]",
"name": "John Doe",
"roles": ["admin", "user"],
"expiresAt": "2024-12-31T23:59:59.000Z",
"timeRemaining": 3600000
}GET /auth/user
Returns current user information from session. Requires authentication.
Response:
{
"sub": "user-id",
"email": "[email protected]",
"name": "John Doe",
"roles": ["admin", "user"],
"expiresAt": "2024-12-31T23:59:59.000Z",
"timeRemaining": 3600000
}POST /auth/refresh
Refreshes access token using refresh token. Extends session lifetime. Requires authentication.
Response:
{
"message": "Tokens refreshed successfully",
"expiresAt": "2024-12-31T23:59:59.000Z",
"timeRemaining": 3600000
}POST /auth/logout
Ends user session by deleting from database and clearing cookie. If OAUTH_END_SESSION_ENDPOINT is configured, redirects to OAuth provider logout for SSO logout.
Response (without provider logout):
{
"message": "Logout successful"
}Response (with provider logout):
302 Redirect to OAuth provider logout endpoint
GET /auth/logout/callback
Handles OAuth provider logout callback. After the OAuth provider completes logout, they redirect back to this endpoint, which then redirects to the frontend.
Response: 302 Redirect to OAUTH_FRONTEND_CALLBACK_URL
Authentication Flow
Login Flow
- Frontend calls
GET /auth/login(optionally withredirect_urlquery parameter) - Backend validates
redirect_urlif provided (returns 400 on failure — does NOT fall back silently) - Backend generates PKCE challenge and stores the validated
redirect_urlalongside it - Backend redirects to OAuth provider
- User authenticates at OAuth provider
- OAuth provider redirects back to
GET /auth/callback?code=xxx&state=xxx - Backend exchanges code for tokens (with PKCE verifier)
- Backend retrieves the
redirect_urlstored in the PKCE challenge - Backend creates session in database
- Backend sets
oauth_sessioncookie (HTTP-only, SameSite=Lax) - Backend redirects browser to the stored
redirect_url(orOAUTH_FRONTEND_CALLBACK_URLif none was stored) - Frontend stores cookie and is now authenticated
Logout Flow
- Frontend calls
POST /auth/logout - Backend deletes session from database
- Backend clears
oauth_sessioncookie - If
OAUTH_END_SESSION_ENDPOINTconfigured:- Backend redirects to OAuth provider logout with
id_token_hint - OAuth provider clears SSO session
- OAuth provider redirects back to
GET /auth/logout/callback(backend) - Backend redirects to
OAUTH_FRONTEND_CALLBACK_URL(frontend)
- Backend redirects to OAuth provider logout with
- Otherwise: Returns JSON success response
Session Validation
Other microservices can validate sessions by calling GET /auth/status with the session cookie.
See @sftech/nestjs-auth for client library to validate sessions from microservices.
Dynamic Error Redirect (v1.4.0+)
When OAuth callback processing fails (e.g. invalid authorization code, expired state, provider error), the backend must redirect the browser to an error page rather than returning a JSON error body because the response is a browser redirect flow.
Behavior:
- If the login was initiated with a
redirect_url, that URL is stored in the PKCE challenge alongside the code verifier. - On callback error, the stored
redirect_urlis retrieved from the thrown exception and used as the redirect target. - The error code is appended as a
?error=<code>query parameter. - If no
redirect_urlwas stored (login was initiated without one), the fallback isOAUTH_FRONTEND_CALLBACK_URL.
Error codes:
| Code | Condition |
|------|-----------|
| auth_failed | Invalid credentials, expired session, unauthorized |
| invalid_request | Missing required parameters (code, state) |
| auth_error | Unexpected internal error |
Example redirect on error:
# Login initiated with redirect_url=https://localhost/auth/callback
# Callback fails with invalid code
→ 302 https://localhost/auth/callback?error=auth_failed
# Login initiated without redirect_url
# Callback fails
→ 302 {OAUTH_FRONTEND_CALLBACK_URL}?error=auth_failedFrontend handling example:
// Angular component at /auth/callback
import { ActivatedRoute } from '@angular/router';
export class AuthCallbackComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
const error = this.route.snapshot.queryParamMap.get('error');
if (error) {
// Handle error — e.g. show message or redirect to login page
console.error('Authentication failed:', error);
}
// Otherwise: authenticated, proceed to application
}
}Open-Redirect Protection (v1.4.0+)
The library enforces OWASP CWE-601 open-redirect protection on all redirect_url values before they are stored. Validation happens at the presentation layer in AuthController.validateRedirectUrl().
Allowlist configuration:
OAUTH_ALLOWED_REDIRECT_ORIGINS accepts a comma-separated list of origins. Each entry can be either a bare origin (https://example.com) or a full URL (the origin is extracted automatically).
{
"OAUTH_ALLOWED_REDIRECT_ORIGINS": "https://localhost,http://localhost:4200,https://myapp.example.com"
}Fallback when OAUTH_ALLOWED_REDIRECT_ORIGINS is not set:
The library automatically derives one allowed origin from OAUTH_FRONTEND_CALLBACK_URL. This preserves backward compatibility for deployments with a single frontend origin.
{
"OAUTH_FRONTEND_CALLBACK_URL": "http://localhost:4200/auth/callback"
// Derived allowed origin: http://localhost:4200
}Multi-origin deployment example (Capacitor + dev + prod):
{
"OAUTH_FRONTEND_CALLBACK_URL": "https://myapp.example.com/auth/callback",
"OAUTH_ALLOWED_REDIRECT_ORIGINS": "https://localhost,http://localhost:4200,https://myapp.example.com"
}This configuration permits all three frontends to pass a redirect_url:
https://localhost/auth/callback— Capacitor mobile (Ionic/Capacitor useshttps://localhost)http://localhost:4200/auth/callback— Angular development serverhttps://myapp.example.com/auth/callback— production SPA
Attack surface covered:
| Attack | Protection |
|--------|-----------|
| Redirect to unrelated domain (https://evil.com) | Origin must be in allowlist |
| Subdomain hijack (https://localhost.evil.com) | Exact origin match, not substring |
| Path traversal (https://localhost/admin) | Path must be exactly /auth/callback |
| Parameter injection (https://localhost/auth/callback?token=x) | No query parameters allowed |
| Protocol-relative redirect (//evil.com/auth/callback) | Must not start with // |
Session Management
Sessions are stored in the database using TypeORM. The session entity includes:
sessionId: Unique session identifier (stored in cookie)userId: User ID from OAuth provideraccessToken: OAuth access tokenrefreshToken: OAuth refresh token (optional)idToken: OIDC ID token (used for provider logout)expiresAt: Session expiration timestampuserInfo: Cached user information (email, name, roles)
Session Modes
- Single mode (
OAUTH_SESSION_MODE=single): Only one active session per user. New login invalidates previous session. - Multi mode (
OAUTH_SESSION_MODE=multi): Multiple concurrent sessions allowed per user.
Architecture
This library follows Clean Architecture principles:
lib/
├── domain/ # Core business models and interfaces
│ ├── models/ # Session, UserInfo, PKCEChallenge domain models
│ ├── repositories/ # Repository interfaces (ports)
│ └── oauth-options.interface.ts
├── application/ # Use cases and business logic
│ └── usecases/ # Login, Callback, Logout, Refresh, etc.
├── infrastructure/ # External concerns
│ ├── entities/ # TypeORM database entities
│ ├── repositories/ # Repository implementations
│ └── mappers/ # Domain ↔ Entity mapping
└── presentation/ # REST API layer
├── controllers/ # AuthController
├── dtos/ # Request/Response DTOs
└── guards/ # SessionGuardBuilding
nx build nestjs-auth-backendPublishing
# Build first
nx build nestjs-auth-backend
# Navigate to dist folder
cd dist/libs/nestjs-auth-backend
# Publish to npm
npm publish --access publicBase Classes & Interfaces
IAuthBundle
Interface for the main authentication bundle containing all auth use cases.
Provides:
initiateLogin- Start OAuth login flow (accepts optionalredirectUrl)handleCallback- Process OAuth callback (returns optionalredirectUrlin output)getAuthStatus- Get current auth statusgetUserInfo- Get current user inforefreshToken- Refresh tokenslogout- End sessioncreateAnonymousSession- Create anonymous sessionrecoverAnonymousSession- Recover anonymous session by recovery code
Usage: This is typically used internally by the module. Access it if you need custom auth flows.
IApiKeyBundle
Interface for API key management bundle.
Provides:
createApiKeyUsecase- Create new API keyvalidateApiKeyUsecase- Validate an API keyrevokeApiKeyUsecase- Revoke an API keylistApiKeysUsecase- List all API keys for a user
Usage example:
import { IApiKeyBundle } from '@sftech/nestjs-auth-backend';
@Controller('api-keys')
@UseGuards(JwtAuthGuard)
export class ApiKeyController {
constructor(
@Inject('IApiKeyBundle')
private readonly apiKeyBundle: IApiKeyBundle,
) {}
@Post()
async create(@Body() dto: CreateApiKeyDto, @CurrentUser() user: UserInfo) {
return this.apiKeyBundle.createApiKeyUsecase.execute({
userId: user.sub,
name: dto.name,
scopes: dto.scopes,
});
}
@Get()
async list(@CurrentUser() user: UserInfo) {
return this.apiKeyBundle.listApiKeysUsecase.execute({ userId: user.sub });
}
@Delete(':id')
async revoke(@Param('id') id: string) {
return this.apiKeyBundle.revokeApiKeyUsecase.execute({ keyId: id });
}
}ISessionRepository
Repository interface for session management.
Methods:
create(session: Session): Promise<Session>- Create new sessionfindById(sessionId: string): Promise<Session | null>- Find session by IDfindByUserId(userId: string): Promise<Session[]>- Find all sessions for userupdate(session: Session): Promise<Session>- Update sessiondelete(sessionId: string): Promise<void>- Delete sessiondeleteAllForUser(userId: string): Promise<void>- Delete all sessions for user
Usage: Implement this interface if you need custom session storage (e.g., Redis).
IUserRepository
Repository interface for user management.
Extends: IBaseDbRepository<User>
Additional methods:
findByEmail(email: string): Promise<User | null>findByOAuthSub(oauthSub: string): Promise<User | null>
IOAuthOptions
Configuration interface for OAuth settings. Populated automatically from app.config.json by OAuthOptionsMapper.
Properties:
interface IOAuthOptions {
providerUrl: string;
authorizationEndpoint: string;
tokenEndpoint: string;
userinfoEndpoint: string;
endSessionEndpoint?: string;
clientId: string;
clientSecret: string;
redirectUri: string; // OAuth callback URL (this service's own endpoint)
frontendCallbackUrl?: string; // Default post-login redirect target
scope: string;
sessionMode: 'single' | 'multi';
sessionMaxAge: number;
anonymousSessionMaxAge?: number;
allowedRedirectOrigins?: string[]; // Parsed from OAUTH_ALLOWED_REDIRECT_ORIGINS (v1.4.0+)
}allowedRedirectOrigins notes:
- Populated by splitting
OAUTH_ALLOWED_REDIRECT_ORIGINSon commas and trimming whitespace. - Each entry is normalized to a bare origin at validation time (e.g.
https://example.com/some/pathbecomeshttps://example.com). - When
undefinedor empty, the origin offrontendCallbackUrlis used as the single allowed origin.
IApiKeyOptions
Configuration interface for API key settings.
Properties:
interface IApiKeyOptions {
hashAlgorithm: string; // e.g., 'sha256'
keyPrefix: string; // e.g., 'sk_'
keyLength: number; // e.g., 32
}PKCEChallenge
Domain model for PKCE (Proof Key for Code Exchange, RFC 7636) challenges.
Constructor parameters:
codeVerifier: string- Random string, 43–128 characterscodeChallenge: string- SHA-256 of codeVerifier, base64url-encodedstate: string- CSRF protection token, minimum 32 characterscreatedAt: Date- Creation timestamp (used for 5-minute TTL)redirectUrl?: string- Optional post-login redirect URL stored alongside the challenge (v1.4.0+)
Methods:
isValid(): boolean- Returnstrueif the challenge is less than 5 minutes oldisExpired(): boolean- Returnstrueif the challenge is 5 minutes old or older
Note: PKCE challenges are stored in an in-memory cache (InitiateLoginUseCase.pkceCache) with a 5-minute TTL. When the challenge expires, any stored redirectUrl is also lost; the error redirect falls back to frontendCallbackUrl in that case.
Extending the Module
Custom Session Storage
To use Redis or another storage backend:
import { ISessionRepository } from '@sftech/nestjs-auth-backend';
import { Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';
@Injectable()
export class RedisSessionRepository implements ISessionRepository {
constructor(private readonly redis: Redis) {}
async create(session: Session): Promise<Session> {
await this.redis.setex(
`session:${session.sessionId}`,
session.maxAge / 1000,
JSON.stringify(session),
);
return session;
}
async findById(sessionId: string): Promise<Session | null> {
const data = await this.redis.get(`session:${sessionId}`);
return data ? JSON.parse(data) : null;
}
// ... implement other methods
}
// Register in module
@Module({
providers: [
{
provide: 'ISessionRepository',
useClass: RedisSessionRepository,
},
],
})
export class CustomAuthModule {}Changelog
See CHANGELOG.md
License
MIT
