@comradeweb/nestjs-authentik
v0.3.1
Published
NestJS module for OIDC authentication (Keycloak, Authentik, etc.)
Downloads
380
Readme
@comradeweb/nestjs-authentik
NestJS module for OIDC authentication. Works with Keycloak, Authentik, and any standard OIDC provider.
- Guard — protect endpoints via JWT validation or token introspection
- M2M Client — get service-to-service tokens via
client_credentialsgrant - OIDC Discovery — auto-discovers endpoints, JWKS, and issuer from
.well-known/openid-configuration - Audience validation — scope-based access control between services
- Zero-config — reads
OIDC_*env vars automatically
Installation
npm install @comradeweb/nestjs-authentikPeer dependencies: @nestjs/common and @nestjs/core ^11.0.0.
Quick Start
Zero-config (reads env vars)
import { AuthentikModule } from '@comradeweb/nestjs-authentik';
@Module({
imports: [AuthentikModule.forRoot()],
})
export class AppModule {}Set env vars:
OIDC_ISSUER_URL=https://keycloak.example.com/realms/my-realm
OIDC_CLIENT_ID=my-service
OIDC_CLIENT_SECRET=my-secret
OIDC_AUDIENCE=my-service
OIDC_USE_INTROSPECTION=trueWith overrides
AuthentikModule.forRoot({
publicMetadataKey: 'isPublicKey',
useIntrospection: true,
})Async config (for custom injection)
AuthentikModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
issuerUrl: config.get('OIDC_ISSUER_URL')!,
clientId: config.get('OIDC_CLIENT_ID'),
clientSecret: config.get('OIDC_CLIENT_SECRET'),
useIntrospection: true,
}),
})Protecting Endpoints
The module exports AuthentikGuard. Register it globally or per-controller:
// Global guard
@Module({
providers: [{ provide: APP_GUARD, useClass: AuthentikGuard }],
})
export class AuthModule {}Mark public routes with @Public():
import { Public } from '@comradeweb/nestjs-authentik';
@Public()
@Get('health')
health() {
return { status: 'ok' };
}Access user payload via request.user:
@Get('me')
me(@Req() req) {
return req.user; // AuthentikUser with sub, iss, aud, realm_access, etc.
}M2M Authentication
Use AuthentikClient to get tokens for service-to-service calls:
import { AuthentikClient } from '@comradeweb/nestjs-authentik';
@Injectable()
export class MyService {
constructor(private readonly auth: AuthentikClient) {}
async callOtherService() {
const token = await this.auth.getToken();
return fetch('https://other-service/api/data', {
headers: { Authorization: `Bearer ${token}` },
});
}
}Tokens are cached automatically and refreshed 30 seconds before expiry.
Configuration
| Option | Env var | Default | Description |
|--------|---------|---------|-------------|
| issuerUrl | OIDC_ISSUER_URL | required | OIDC issuer URL |
| clientId | OIDC_CLIENT_ID | - | OAuth2 client ID |
| clientSecret | OIDC_CLIENT_SECRET | - | OAuth2 client secret |
| audience | OIDC_AUDIENCE | - | Expected aud claim. Skipped if not set |
| useIntrospection | OIDC_USE_INTROSPECTION | false | Use introspection instead of local JWT validation |
| introspectionCacheTtl | - | 30 | Introspection cache TTL in seconds. 0 to disable |
| introspectionAuthMethod | - | client_secret_basic | client_secret_basic (RFC 7662) or client_secret_post |
| scope | - | openid | Scope for M2M token requests |
| publicMetadataKey | - | authentik:public | Metadata key for @Public() decorator |
| clockTolerance | - | 5 | Clock tolerance in seconds for JWT exp/nbf checks |
| allowInsecure | - | false | Allow http:// issuer URL (for local dev) |
Token Validation Strategies
Local JWT validation (default)
Validates JWT signature locally via JWKS. Fast, no network call per request. Does not detect revoked tokens until they expire.
Introspection (useIntrospection: true)
Validates tokens by calling the IdP's introspection endpoint. Detects revoked tokens in real-time. Results are cached for introspectionCacheTtl seconds (default 30). Uses HTTP Basic Auth per RFC 7662.
Audience-Based Access Control
For M2M between services, use Keycloak audience scopes to control which service can call which:
- Keycloak: Create Client Scope
my-service-audiencewith Audience mapper pointing tomy-service - Keycloak: Add this scope to calling service's client as Default
- Code: Set
OIDC_AUDIENCE=my-serviceon the receiving service
Tokens without the required audience get 401 Unauthorized.
Exports
// Module
AuthentikModule // .forRoot(overrides?) / .forRootAsync(config)
// Guard & Client
AuthentikGuard // CanActivate guard for endpoints
AuthentikClient // M2M token client (client_credentials)
// Discovery
AuthentikDiscoveryService // OIDC discovery (issuer, JWKS, endpoints)
// Decorator
Public // Mark routes as public (skip auth)
// Types
AuthentikModuleConfig // Config interface
AuthentikUser // JWT payload type (includes Keycloak claims)
// Constants
AUTHENTIK_CONFIG // DI token for config injection
AUTHENTIK_PUBLIC_KEY // Default metadata key for @Public()AuthentikUser Type
Includes standard OIDC claims plus Keycloak-specific fields:
interface AuthentikUser {
sub: string;
iss: string;
aud: string | string[];
exp: number;
iat: number;
scope?: string;
azp?: string; // authorized party (client_id)
preferred_username?: string;
email?: string;
realm_access?: { roles: string[] };
resource_access?: Record<string, { roles: string[] }>;
[key: string]: unknown; // additional claims
}License
MIT
