@sftech/nestjs-auth
v1.5.1
Published
Authentication and authorization library for NestJS applications with OAuth2/OIDC support, JWT validation, and CASL-based authorization.
Downloads
598
Readme
@sftech/nestjs-auth
Authentication and authorization library for NestJS applications with OAuth2/OIDC support, JWT validation, and CASL-based authorization.
Installation
npm install @sftech/nestjs-authPeer Dependencies
@sftech/nestjs-core >= 1.0.0@sftech/nestjs-db >= 1.0.0@nestjs/common@casl/ability
Features
- OAuth2/OIDC token validation
- JWT token verification via JWKS (JSON Web Key Set)
- CASL-based authorization
- Auth-aware CRUD operations (extends nestjs-db)
- User context injection throughout application layers
- Guards and decorators for route protection
Configuration
import { NestjsAuthModule } from '@sftech/nestjs-auth';
@Module({
imports: [
NestjsAuthModule.register({
authProvider: 'oauth',
oauthValidationUrl: 'https://your-oauth-provider/userinfo',
}),
],
})
export class AppModule {}Configuration Options
Add to your app.config.json:
{
"AUTH_PROVIDER": "oauth",
"AUTH_OAUTH_VALIDATION_URL": "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/userinfo"
}| Parameter | Type | Description |
|-----------|------|-------------|
| AUTH_PROVIDER | 'oauth' \| 'local' | Authentication provider type |
| AUTH_OAUTH_VALIDATION_URL | string | OAuth userinfo endpoint for token validation |
API Reference
Application Layer
| Class | Purpose |
|-------|---------|
| BaseCrudAuthBundle<TDbModel> | Auth-aware CRUD bundle with user context |
| AuthGetByIdUsecase | Get by ID with auth check |
| AuthGetAllUsecase | Get all with auth filtering |
| AuthGetByUserUsecase | Get entities for current user |
| AuthInsertUsecase | Insert with user context |
| AuthUpdateUsecase | Update with auth check |
| AuthDeleteUsecase | Delete with auth check |
| AuthBulkSaveUsecase | Bulk save with auth |
| AuthOdataUsecase | OData with auth filtering |
Infrastructure Layer
| Class | Purpose |
|-------|---------|
| BaseAuthDbEntity | Base entity with userId field |
| BaseAuthDbRepository<TDbModel, TDbEntity> | Repository with auth filtering |
| BaseAuthDbMapper<TDbModel, TDbEntity> | Mapper with auth fields |
Presentation Layer
| Class | Purpose |
|-------|---------|
| BaseCrudAuthController | Controller with auth-aware CRUD |
Base Classes & Interfaces
BaseCrudAuthBundle
Auth-aware bundle containing all CRUD use cases with user context injection. Extends BaseCrudBundle with CASL authorization.
Generic type parameters:
TDbModel extends IBaseAuthDbModel- Your domain model (must include userId)
Provides out of the box:
- All methods from
BaseCrudBundle getAllByUserUsecase- Get entities for current user only- User context automatically injected via
RequestContextStorage - CASL ability checks on all operations
Constructor parameters:
_repo: IBaseAuthDbRepository<TDbModel>- Auth-aware repository_abilityFactory: IBaseCaslFactory- CASL ability factory_rcs: RequestContextStorage- Request context storage
Usage example:
import { BaseCrudAuthBundle, IBaseAuthDbModel } from '@sftech/nestjs-auth';
import { RequestContextStorage } from '@sftech/nestjs-core';
import { Injectable } from '@nestjs/common';
// 1. Define your domain model with userId
export class Article implements IBaseAuthDbModel {
id?: number;
createdAt?: Date;
userId: string; // Required for auth-aware entities
title: string;
content: string;
}
// 2. Create CASL ability factory
@Injectable()
export class ArticleAbilityFactory implements IBaseCaslFactory {
createForUser(user: UserInfo) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Users can read all articles
can('read', 'Article');
// Users can only modify their own articles
can(['update', 'delete'], 'Article', { userId: user.sub });
return build();
}
}
// 3. Create the auth bundle
@Injectable()
export class ArticleCrudAuthBundle extends BaseCrudAuthBundle<Article> {
constructor(
repository: ArticleRepository,
abilityFactory: ArticleAbilityFactory,
rcs: RequestContextStorage,
) {
super(repository, abilityFactory, rcs);
}
}
// 4. Use in controller
@Controller('articles')
@UseGuards(JwtAuthGuard)
export class ArticleController {
constructor(private readonly articleBundle: ArticleCrudAuthBundle) {}
@Get()
async getAll() {
// Returns all articles (filtered by CASL abilities)
return this.articleBundle.getAllUsecase.execute();
}
@Get('my')
async getMyArticles() {
// Returns only current user's articles
return this.articleBundle.getAllByUserUsecase.execute();
}
@Post()
async create(@Body() dto: CreateArticleDto) {
const article = new Article();
article.title = dto.title;
article.content = dto.content;
// userId is automatically set from RequestContextStorage
return this.articleBundle.insertUsecase.execute(article);
}
@Delete(':id')
async delete(@Param('id') id: number) {
// CASL checks if user owns this article
return this.articleBundle.deleteUsecase.execute(id);
}
}BaseAuthDbRepository<TDbModel, TDbEntity>
Abstract repository extending BaseDbRepository with auth-aware filtering. Extend this for entities that belong to users.
Generic type parameters:
TDbModel extends IBaseAuthDbModel- Domain model with userIdTDbEntity extends IBaseAuthDbEntity- TypeORM entity with userId
Provides out of the box:
- All methods from
BaseDbRepository getAllByUser(userId)- Get all entities for a specific usergetByIdAndUser(id, userId)- Get by ID with user ownership check
You must implement:
_mapper: IBaseAuthDbMapper<TDbModel, TDbEntity>- Auth-aware mapper
Usage example:
import { BaseAuthDbRepository, IBaseAuthDbMapper } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class ArticleRepository extends BaseAuthDbRepository<Article, ArticleEntity> {
protected readonly _mapper: IBaseAuthDbMapper<Article, ArticleEntity>;
constructor(
@InjectRepository(ArticleEntity)
repo: Repository<ArticleEntity>,
mapper: ArticleMapper,
) {
super(repo);
this._mapper = mapper;
}
// Custom auth-aware queries
async findPublishedByUser(userId: string): Promise<Article[]> {
return this.findBy({
where: { userId, publishedAt: Not(IsNull()) },
});
}
}BaseAuthDbEntity
Base TypeORM entity with userId field. Extend this for all user-owned entities.
Provides:
id?: number- Primary key (from BaseDbEntity)createdAt?: Date- Creation timestamp (from BaseDbEntity)userId: string- Owner user ID
Usage example:
import { BaseAuthDbEntity } from '@sftech/nestjs-auth';
import { Entity, Column } from 'typeorm';
@Entity('articles')
export class ArticleEntity extends BaseAuthDbEntity {
@Column()
title: string;
@Column('text')
content: string;
@Column({ nullable: true })
publishedAt?: Date;
}BaseAuthDbMapper<TDbModel, TDbEntity>
Abstract mapper extending BaseDbMapper with auth field handling.
You must implement:
mapFromDb(from: TDbEntity, to?: TDbModel): TDbModelmapToDb(from: TDbModel, to?: TDbEntity): TDbEntity
Usage example:
import { BaseAuthDbMapper } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ArticleMapper extends BaseAuthDbMapper<Article, ArticleEntity> {
mapFromDb(from: ArticleEntity, to?: Article): Article {
const article = to ?? new Article();
article.id = from.id;
article.createdAt = from.createdAt;
article.userId = from.userId;
article.title = from.title;
article.content = from.content;
return article;
}
mapToDb(from: Article, to?: ArticleEntity): ArticleEntity {
const entity = to ?? new ArticleEntity();
if (from.id) entity.id = from.id;
entity.userId = from.userId;
entity.title = from.title;
entity.content = from.content;
return entity;
}
}BaseCrudAuthController<TDbModel, TResponseDto, TCreateDto, TUpdateDto>
Abstract controller extending BaseCrudController with authentication. Extend for auth-protected CRUD APIs.
Provides out of the box:
- All endpoints from
BaseCrudController GET /my- Get current user's entities
Usage example:
import { BaseCrudAuthController } from '@sftech/nestjs-auth';
import { Controller, UseGuards } from '@nestjs/common';
@Controller('articles')
@UseGuards(JwtAuthGuard)
export class ArticleController extends BaseCrudAuthController<
Article,
ArticleResponseDto,
CreateArticleDto,
UpdateArticleDto
> {
protected readonly _converter: ArticleDtoConverter;
constructor(
articleBundle: ArticleCrudAuthBundle,
converter: ArticleDtoConverter,
) {
super(articleBundle);
this._converter = converter;
}
}IBaseAuthDbModel
Interface for auth-aware domain models. Implement for all user-owned models.
Required properties:
id?: number- Entity IDcreatedAt?: Date- Creation timestampuserId: string- Owner user ID
Usage example:
import { IBaseAuthDbModel } from '@sftech/nestjs-auth';
export class Comment implements IBaseAuthDbModel {
id?: number;
createdAt?: Date;
userId: string;
articleId: number;
text: string;
}IBaseCaslFactory
Interface for CASL ability factories. Implement to define authorization rules.
You must implement:
createForUser(user: UserInfo): MongoAbility- Create abilities for a user
Usage example:
import { IBaseCaslFactory } from '@sftech/nestjs-auth';
import { AbilityBuilder, createMongoAbility, MongoAbility } from '@casl/ability';
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppAbilityFactory implements IBaseCaslFactory {
createForUser(user: UserInfo): MongoAbility {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
if (user.roles.includes('admin')) {
// Admins can do everything
can('manage', 'all');
} else {
// Regular users
can('read', 'Article');
can('create', 'Article');
can(['update', 'delete'], 'Article', { userId: user.sub });
can('read', 'Comment');
can('create', 'Comment');
can(['update', 'delete'], 'Comment', { userId: user.sub });
}
return build();
}
}Complete Example
// === DOMAIN ===
// article.model.ts
import { IBaseAuthDbModel } from '@sftech/nestjs-auth';
export class Article implements IBaseAuthDbModel {
id?: number;
createdAt?: Date;
userId: string;
title: string;
content: string;
}
// === INFRASTRUCTURE ===
// article.entity.ts
import { BaseAuthDbEntity } from '@sftech/nestjs-auth';
import { Entity, Column } from 'typeorm';
@Entity('articles')
export class ArticleEntity extends BaseAuthDbEntity {
@Column()
title: string;
@Column('text')
content: string;
}
// article.mapper.ts
import { BaseAuthDbMapper } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ArticleMapper extends BaseAuthDbMapper<Article, ArticleEntity> {
mapFromDb(from: ArticleEntity): Article {
const a = new Article();
a.id = from.id;
a.createdAt = from.createdAt;
a.userId = from.userId;
a.title = from.title;
a.content = from.content;
return a;
}
mapToDb(from: Article, to?: ArticleEntity): ArticleEntity {
const e = to ?? new ArticleEntity();
if (from.id) e.id = from.id;
e.userId = from.userId;
e.title = from.title;
e.content = from.content;
return e;
}
}
// article.repository.ts
import { BaseAuthDbRepository } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class ArticleRepository extends BaseAuthDbRepository<Article, ArticleEntity> {
protected readonly _mapper: ArticleMapper;
constructor(
@InjectRepository(ArticleEntity) repo: Repository<ArticleEntity>,
mapper: ArticleMapper,
) {
super(repo);
this._mapper = mapper;
}
}
// === APPLICATION ===
// article-ability.factory.ts
import { IBaseCaslFactory } from '@sftech/nestjs-auth';
import { AbilityBuilder, createMongoAbility } from '@casl/ability';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ArticleAbilityFactory implements IBaseCaslFactory {
createForUser(user: UserInfo) {
const { can, build } = new AbilityBuilder(createMongoAbility);
can('read', 'Article');
can(['create', 'update', 'delete'], 'Article', { userId: user.sub });
return build();
}
}
// article.bundle.ts
import { BaseCrudAuthBundle } from '@sftech/nestjs-auth';
import { RequestContextStorage } from '@sftech/nestjs-core';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ArticleCrudAuthBundle extends BaseCrudAuthBundle<Article> {
constructor(
repo: ArticleRepository,
abilityFactory: ArticleAbilityFactory,
rcs: RequestContextStorage,
) {
super(repo, abilityFactory, rcs);
}
}
// === MODULE ===
@Module({
imports: [TypeOrmModule.forFeature([ArticleEntity])],
providers: [
ArticleMapper,
ArticleRepository,
ArticleAbilityFactory,
ArticleCrudAuthBundle,
],
exports: [ArticleCrudAuthBundle],
})
export class ArticleModule {}Development
# Build
npx nx build nestjs-auth
# Test
npx nx test nestjs-auth
# Lint
npx nx lint nestjs-authSee Also
- @sftech/nestjs-auth-backend - Backend OAuth service for session management
- @sftech/nestjs-db - Base database layer
Changelog
See CHANGELOG.md
License
MIT
