Authentication Module Documentation
Project
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
Table of Contents
- Tutorials
- How to Guides
- Explanation
Tutorials
Introduction
Overview of the Library
This module is designed to manage JWT authentication processes within a NestJS application. It includes services for issuing JWTs, validating user credentials, and verifying tokens. The services handle the generation of access and refresh tokens, ensure users are active and meet authentication criteria, and perform token validity checks, including additional validations if necessary. This comprehensive approach ensures secure user authentication and efficient token management.
Purpose and Key Features
- Secure Token Management: Provides robust mechanisms for issuing and managing access and refresh tokens, ensuring secure and efficient token lifecycle management.
- Abstract User Validation Service: Offers an abstract service to validate user credentials and check user activity status, ensuring that only eligible users can authenticate. This abstract nature requires implementations to define specific validation logic, allowing flexibility across different user models and authentication requirements.
- Token Verification: Includes capabilities to verify the authenticity and validity of tokens, with support for additional custom validations to meet specific security requirements.
- Customizable and Extensible: Designed to be flexible, allowing customization of token generation, user validation, and token verification processes to suit different application needs.
- Integration with NestJS Ecosystem: Seamlessly integrates with other NestJS modules and services, leveraging the framework's features for enhanced functionality and performance.
Installation
To get started, install the AuthenticationModule
package:
yarn add @concepta/nestjs-authentication
Getting Started
Overview
This section covers the basics of setting up the AuthenticationModule
in a NestJS application.
Basic Setup
The @concepta/nestjs-authentication
module is designed to integrate
seamlessly with other modules in the authentication suite, such as
@concepta/nestjs-auth-jwt
, @concepta/nestjs-auth-local
,
@concepta/nestjs-auth-recovery
, and @concepta/nestjs-auth-refresh
.
For optimal functionality, it is recommended to use these modules together to address various aspects of authentication and token management in your NestJS application.
To set up the @concepta/nestjs-authentication
module, begin by installing
the necessary packages using your package manager.
Here is a basic example using yarn
:
yarn add @concepta/nestjs-authentication @concepta/nestjs-auth-jwt @concepta/nestjs-auth-local @concepta/nestjs-auth-recovery @concepta/nestjs-auth-refresh
Basic Setup in a NestJS Project
Scenario: Users have a list of pets
To demonstrate this scenario, we will set up an application where users can have a list of pets. We will create the necessary entities, services, module configurations to simulate the environment.
Note: The
@concepta/nestjs-user
module can be used in place of our exampleUser
related prerequisites.
Step 1: Create Entities
First, create the User
and Pet
entities.
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Pet } from './pet.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@OneToMany(() => Pet, pet => pet.user)
pets: Pet[];
}
// pet.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user.entity';
@Entity()
export class Pet {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@ManyToOne(() => User, user => user.pets)
user: User;
}
Step 2: Create Services
Next, create services for User
and Pet
to handle the business logic.
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async findAll(): Promise<User[]> {
return this.userRepository.find({ relations: ['pets'] });
}
async findOne(id: string): Promise<User> {
return this.userRepository.findOne({
where: { id },
relations: ['pets'],
});
}
async create(userData: Partial<User>): Promise<User> {
const newUser = this.userRepository.create(userData);
await this.userRepository.save(newUser);
return newUser;
}
}
// pet.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Pet } from './pet.entity';
@Injectable()
export class PetService {
constructor(
@InjectRepository(Pet)
private petRepository: Repository<Pet>,
) {}
findAll(): Promise<Pet[]> {
return this.petRepository.find();
}
findByUserId(userId: number): Promise<Pet[]> {
return this.petRepository.find({ where: { user: { id: userId } } });
}
}
Create the Lookup Service for AuthJwtModule
// my-jwt-user-lookup.service.ts
import { AuthJwtUserLookupServiceInterface } from '@concepta/nestjs-auth-jwt/dist/interfaces/auth-jwt-user-lookup-service.interface';
import { ReferenceIdInterface, ReferenceSubject } from '@concepta/ts-core';
export class MyJwtUserLookupService implements AuthJwtUserLookupServiceInterface {
async bySubject(subject: ReferenceSubject): Promise<ReferenceIdInterface> {
// return authorized user
return {
id: '5b3f5fd3-9426-4c4d-a06d-b4d55079034d',
};
}
}
Create the Lookup Service for Auth Local
// my-auth-local-user-lookup.service.ts
import { Injectable } from '@nestjs/common';
import { ReferenceUsername } from '@concepta/ts-core';
import { AuthLocalUserLookupServiceInterface } from '@concepta/nestjs-auth-local';
import { AuthLocalCredentialsInterface } from '@concepta/nestjs-auth-local/dist/interfaces/auth-local-credentials.interface';
@Injectable()
export class MyAuthLocalUserLookupService implements AuthLocalUserLookupServiceInterface {
async byUsername(
username: ReferenceUsername,
): Promise<AuthLocalCredentialsInterface | null> {
// make sure this method will return a valid user with
// correct passwordHash and passwordSalt
return {
id: '5b3f5fd3-9426-4c4d-a06d-b4d55079034d',
username: username,
passwordHash:
'$2b$12$9rQ4qZx8gpTaTR4ic3LQ.OkebyVBa48DP42jErL1zfqF17WeG4hHC',
passwordSalt: '$2b$12$9rQ4qZx8gpTaTR4ic3LQ.O',
active: true,
};
}
}
Let's create a password validation service and overwrite the validate method, for demo purposes only.
// my-auth-local-user-password-validation.service.ts
import {
PasswordStorageInterface,
PasswordValidationService,
} from '@concepta/nestjs-password';
import { Injectable } from '@nestjs/common';
@Injectable()
export class MyAuthLocalPasswordValidationService extends PasswordValidationService {
constructor() {
super();
}
async validate(options: {
password: string;
passwordHash: string;
passwordSalt: string;
}): Promise<boolean> {
// you should call super.validate to use the default password validation
return true;
}
async validateObject<T extends PasswordStorageInterface>(
password: string,
object: T,
): Promise<boolean> {
return true;
}
}
Let's create a verify service to validate the token that will be received in the request. If we need a custom logic to validate the access token you can overwrite this service.
// jwt-verify-token.service.ts
import { JwtService } from '@nestjs/jwt';
import { Injectable } from '@nestjs/common';
import { VerifyTokenServiceInterface } from '@concepta/nestjs-authentication';
@Injectable()
export class MyJwtVerifyTokenService implements VerifyTokenServiceInterface {
accessToken() {
// your custom logic to sign and validate the the token
return { accessToken: 'access-token' };
}
refreshToken(...args) {
// your custom logic to sign and validate the the token
return { accessToken: 'refresh-token' };
}
}
Step 3: Create Controller
Create a controller to handle the HTTP requests.
Note: Use the
@AuthPublic
decorator from@concepta/nestjs-authentication
on the controller or individual routes if you want to override the global JWT guard to make the route public.
// user.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { UserService } from './user.service';
import { PetService } from './pet.service';
import { AuthJwtGuard } from '@concepta/nestjs-auth-jwt';
@Controller('user')
export class UserController {
constructor(
private userService: UserService,
private petService: PetService,
) {}
@Get()
async findAll(): Promise<User[]> {
return this.userService.findAll();
}
@Post()
async create(@Body() userData: Partial<User>): Promise<User> {
return this.userService.create(userData);
}
@Get(':id/pets')
async getPets(@Param('id') userId: number) {
return this.petService.findByUserId(userId);
}
}
Step 4: Configure the Module
Configure the module to include the necessary services, controllers, and guards.
import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext';
import { Module } from '@nestjs/common';
import { Pet } from './entity/pet.entity';
import { User } from './user/user.entity';
import { JwtModule } from '@concepta/nestjs-jwt';
import { AuthenticationModule } from '@concepta/nestjs-authentication';
import { AuthJwtModule } from '@concepta/nestjs-auth-jwt';
import { MyJwtUserLookupService } from './services/my-jwt-user-lookup.service';
import { AuthLocalModule } from '@concepta/nestjs-auth-local';
import { MyAuthLocalUserLookupService } from './services/my-auth-local-user-lookup.service';
import { MyAuthLocalPasswordValidationService } from './services/my-auth-local-password-validation.service';
import { MyJwtVerifyTokenService } from './services/jwt-verify-token.service';
@Module({
imports: [
TypeOrmExtModule.forRoot({
type: 'sqlite',
database: ':memory:',
synchronize: true,
entities: [User, Pet],
}),
AuthLocalModule.forRoot({
// this service contains the byUsername method
userLookupService: new MyAuthLocalUserLookupService(),
// this service contains the validate the password logic
passwordValidationService: new MyAuthLocalPasswordValidationService(), //
}),
AuthJwtModule.forRoot({
// this contains the bySubject method that will get user based on the token
userLookupService: new MyJwtUserLookupService(),
// service to validate the jwt token
verifyTokenService: new MyJwtVerifyTokenService(),
}),
AuthenticationModule.forRoot({}),
JwtModule.forRoot({}),
],
})
export class AppModule {}
First Authentication with JWT
Validating the Setup
To validate the setup, you can use curl
commands to simulate frontend requests.
By following these steps, you can validate that the setup is working correctly
and that authenticated requests to the user
endpoint return the
expected list of pets for a given user.
Here are the steps to test the user
endpoint:
Step 1: Obtain a JWT Token
The AuthLocalModule
provide a controller with an authentication
endpoint to obtain a JWT token, use curl
to get the token.
Replace [auth-url]
with your actual authentication URL, and
[username]
and [password]
with valid credentials. For our demo,
since we overwrote the passwordValidationService
, we can use any password.
curl -X POST [auth-url] \
-H "Content-Type: application/json" \
-d '{"username": "[username]", "password": "[password]"}'
This should return a response with a JWT token, which you'll use for authenticated requests.
Step 2: Make an Authenticated Request
Use the JWT token obtained in the previous step to make an authenticated request
to the user
endpoint. Replace [jwt-token]
.
curl -X GET http://localhost:3000/user \
-H "Authorization: Bearer [jwt-token]"
Example Curl Calls
Here is an example sequence of curl commands:
Obtain a JWT token
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "testuser", "password": "testpassword"}'
Example JWT response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Make an authenticated request using the token
Assuming that are alerady inserted the user and its pets, let's try to retrieve it.
curl -X GET http://localhost:3000/user/5b3f5fd3-9426-4c4d-a06d-b4d55079034d/pets \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Example authenticated response
[
{
"id": 1,
"name": "Fluffy",
"user": {
"id": 1,
"name": "John Doe",
"pets": []
}
}
]
To get authenticated user, we can use the decorator @AuthUser()
this will return whatever was defined at bySubject
method from
MyJwtUserLookupService
.
@Get(':id/pets')
async getPets(
@AuthUser() user: User,
@Param('id') userId: number
) {
if (user.id !== userId) throw new UnauthorizedException()
return this.petService.findByUserId(userId);
}
How to Guides
1. How to Set Up AuthenticationModule with forRoot and JwtModule from @concepta/nestjs-jwt
The @concepta/nestjs-authentication
module is designed to integrate
seamlessly with other modules in the authentication suite, such as
@concepta/nestjs-auth-jwt
, @concepta/nestjs-auth-local
,
@concepta/nestjs-auth-recovery
, and @concepta/nestjs-auth-refresh
.
For optimal functionality, it is recommended to use these modules together to address various aspects of authentication and token management in your NestJS application.
To set up the nestjs-authentication
module, begin by installing the
necessary packages using your package manager.
Here is a basic example using yarn
:
yarn add @nestjs-authentication @concepta/nestjs-jwt
Example Setup
To set up the AuthenticationModule
and JwtModule
, follow these steps:
Import the modules in your application module:
//...
AuthenticationModule.forRoot({}),
AuthJwtModule.forRoot({
// this lookup contains bySubject method
userLookupService: new JwtUserLookupService(),
}),
JwtModule.forRoot({
secret: 'your-secret-key',
signOptions: { expiresIn: '60s' },
}),
//...
This setup configures the AuthenticationModule
with global
settings and integrates the JwtModule
for JWT-based authentication.
2. How to Configure AuthenticationModule Settings
The AuthenticationModule
provides several configurable settings to
customize its behavior. Each setting can be defined in the module
configuration and will create default services to be used in other modules.
Settings Example
Here is an example of how to configure each property of the settings:
1. enableGuards: Enables or disables guards globally
//...
AuthenticationModule.forRoot({
settings: {
enableGuards: true, // Enables guards globally
},
}),
//...
2. issueTokenService: Custom service for issuing tokens
//...
AuthenticationModule.forRoot({
issueTokenService: new MyIssueTokenService(), // Custom token issuance service
}),
//...
Implementation :
import { Injectable } from '@nestjs/common';
import { JwtIssueService } from '@concepta/nestjs-jwt';
import { AuthenticationResponseInterface } from '@concepta/ts-common';
import { IssueTokenServiceInterface } from '../interfaces/issue-token-service.interface';
@Injectable()
export class MyIssueTokenService implements IssueTokenServiceInterface {
constructor(protected readonly jwtIssueService: JwtIssueService) {}
async accessToken(...args: Parameters<JwtIssueService['accessToken']>) {
return this.jwtIssueService.accessToken(...args);
}
async refreshToken(...args: Parameters<JwtIssueService['refreshToken']>) {
return this.jwtIssueService.refreshToken(...args);
}
async responsePayload(
id: string,
): Promise<AuthenticationResponseInterface> {
const payload = { sub: id };
const dto = new AuthenticationJwtResponseDto();
dto.accessToken = await this.accessToken(payload);
dto.refreshToken = await this.refreshToken(payload);
return dto;
}
}
3. verifyTokenService: Custom service for verifying tokens
//...
AuthenticationModule.forRoot({
verifyTokenService: new MyVerifyTokenService(), // Custom token verification service
}),
//...
Implementation:
import { Injectable } from '@nestjs/common';
import { JwtVerifyService } from '@concepta/nestjs-jwt';
import { ValidateTokenServiceInterface } from '../interfaces/validate-token-service.interface';
import { VerifyTokenServiceInterface } from '../interfaces/verify-token-service.interface';
import { BadRequestException } from '@nestjs/common';
@Injectable()
export class MyVerifyTokenService implements VerifyTokenServiceInterface {
constructor(
protected readonly jwtVerifyService: JwtVerifyService,
protected readonly validateTokenService?: ValidateTokenServiceInterface,
) {}
async accessToken(...args: Parameters<JwtVerifyService['accessToken']>) {
const token = await this.jwtVerifyService.accessToken(...args);
if (await this.validateToken(token)) {
return token;
} else {
throw new BadRequestException(
'Access token was verified, but failed further validation.',
);
}
}
async refreshToken(...args: Parameters<JwtVerifyService['refreshToken']>) {
const token = await this.jwtVerifyService.refreshToken(...args);
if (await this.validateToken(token)) {
return token;
} else {
throw new BadRequestException(
'Refresh token was verified, but failed further validation.',
);
}
}
private async validateToken(
payload: Record<string, unknown>,
): Promise<boolean> {
if (this.validateTokenService) {
return this.validateTokenService.validateToken(payload);
} else {
return true;
}
}
}
4. validateTokenService: Custom service for validating tokens
//...
AuthenticationModule.forRoot({
validateTokenService: new MyValidateTokenService(), // Custom token validation service
}),
//...
Implementation:
import { Injectable } from '@nestjs/common';
import { ValidateTokenServiceInterface } from '../interfaces/validate-token-service.interface';
@Injectable()
export class MyValidateTokenService implements ValidateTokenServiceInterface {
async validateToken(payload: Record<string, unknown>): Promise<boolean> {
// Custom logic to validate the token
return true;
}
}
Explanation
Conceptual Overview
What is This Library?
The @concepta/nestjs-authentication
library is a comprehensive
solution for managing authentication processes within a NestJS application.
It provides services for issuing JWTs, validating user credentials, and
verifying tokens.
The library integrates seamlessly with other modules in the authentication
suite, such as @concepta/nestjs-auth-jwt
, @concepta/nestjs-auth-local
,
@concepta/nestjs-auth-recovery
, and @concepta/nestjs-auth-refresh
, making
it a versatile choice for various authentication needs.
Benefits of Using This Library
- Secure Token Management: Robust mechanisms for issuing and managing access and refresh tokens.
- Abstract User Validation Service: Flexible user validation service that can be customized to meet specific requirements.
- Token Verification: Capabilities to verify the authenticity and validity of tokens, with support for additional custom validations.
- Customizable and Extensible: Designed to be flexible, allowing customization of token generation, user validation, and token verification processes.
- Integration with NestJS Ecosystem: Seamlessly integrates with other NestJS modules and services, leveraging the framework's features for enhanced functionality and performance.
Design Choices
Why Use NestJS Guards?
NestJS guards provide a way to control access to various parts of the
application by checking certain conditions before the route handler is executed.
In the nestjs-authentication
module, guards are used to implement
authentication and authorization logic. By using guards, developers can apply
security policies across routes efficiently, ensuring that only authenticated
and authorized users can access protected resources.
Global, Synchronous vs Asynchronous Registration
The nestjs-authentication
module supports both synchronous and asynchronous
registration:
- Global Registration: Makes the module available throughout the entire application. This approach is useful when JWT authentication is required across all or most routes in the application.
- Synchronous Registration: This method is used when the configuration options are static and available at application startup. It simplifies the setup process and is suitable for most use cases where configuration values do not depend on external services.
- Asynchronous Registration: This method is beneficial when configuration options need to be retrieved from external sources, such as a database or an external API, at runtime. It allows for more flexible and dynamic configuration but requires an asynchronous factory function.
Integration Details
Integrating with Other Modules
The nestjs-authentication
module integrates smoothly with other modules in the
authentication suite. Here are some integration details:
- @concepta/nestjs-auth-jwt: Use
@concepta/nestjs-auth-jwt
for JWT-based authentication. Configure it to handle the issuance and verification of JWT tokens. - @concepta/nestjs-auth-local: Use
@concepta/nestjs-auth-local
for local authentication strategies such as username and password. - @concepta/nestjs-auth-recovery: Use
@concepta/nestjs-auth-recovery
for account recovery processes like password reset. - @concepta/nestjs-auth-refresh: Use
@concepta/nestjs-auth-refresh
for handling token refresh mechanisms.
By combining these modules, you can create a comprehensive authentication system that meets various security requirements and user needs.