nestjs-security
v1.0.0
Published
[![npm version](https://badge.fury.io/js/angular2-expandable-list.svg)](https://badge.fury.io/js/angular2-expandable-list) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier
Downloads
59
Maintainers
Readme
Nestjs Security
Table of contents
- Nestjs Security
Prerequisites
This project requires NodeJS (version 16 or later) and NPM. Node and NPM are very easy to install. To make sure you have them available on your machine, try running the following command.
$ npm -v && node -v
6.4.1
v16.1.0
If you've encountered an error, please read it here
Getting Started
The package was developed under the theme of "security" because it wanted to provide a series of functions in one package. Now, we want to provide security features that can be easily applied at the API level, including CSRF protection provided by the deprecated express/csrf package. The core concept is to provide various security functions to be applicable independently of the server infrastructure configuration at the middleware level, the class level, and the method level without modifying the business logic.
By default, you can use the security features that the library provides by linking them to a registered security profile.
Installation
$ npm install nestjs-security
Or if you using Yarn:
$ yarn add nestjs-security
Usage
Before use
(1) import SecurityModule
@Module({
imports: [SecurityModule.forRoot()],
})
export class AppModule {}
You must perform import
on the root module (AppModule
) to use that package.
(2) regist security profile
The package provides security capabilities based on a provider called "Security Profile." Security Profile is a provider that your developer must implement directly and can be implemented by importing and inheriting abstract classes that fit the security features (ex-IP WhiteList) you want to use.
- Security Profile can be multiple in one application (Nestjs Server)
- (ex) Multiple profiles that inherit
IpWhiteListValidationSecurityProfile
can be registered
- (ex) Multiple profiles that inherit
- Because you are a Nestjs provider by default, you must register as a provider in the appropriate module.
In the example above, you have registered with the@Module({ imports: [SecurityModule.forRoot()], providers: [ // <-- Register the security profile configured by the developer as a provider. NaiveBlackListProfile, NaiveWhiteListProfile, EnvBlackListProfile, EnvWhiteListProfile, JwtSignedCSRFTokenProfile, ], }) export class AppModule {}
AppModule
, but the Security Profile must be registered as a provider in the appropriate module, depending on the dependencies required.
The features offered in the current package are the IP White List, the IP Black List, so you must implement a profile for each feature to use. Below, we will discuss how to implement each profile.
IP white list profile
Only the IP addresses in the whitelist are allowed.
Each ip address string supports the CIDR block notation method.
import { ConfigService } from '@nestjs/config';
import {
IpBlackListValidationSecurityProfile,
IpWhiteListValidationSecurityProfile,
SecurityProfileSchema,
} from 'nestjs-security';
@SecurityProfileSchema()
export class NaiveWhiteListProfile extends IpWhiteListValidationSecurityProfile {
getIpWhiteList(): string[] {
// return ip's
return [
'127.0.0.1',
/**
* - start : 192.168.16.0
* - end : 192.168.31.255
*/
'192.168.16.0/20',
/**
* - start : 192.168.0.5
* - end : 192.168.0.5
*/
'192.168.0.5/32',
];
}
}
@SecurityProfileSchema()
export class EnvWhiteListProfile extends IpWhiteListValidationSecurityProfile {
constructor(private readonly configService: ConfigService<any, true>) {
super();
}
getIpWhiteList(): string[] {
// return ip's from ENV file
const ipWhiteList = this.configService.get<string>('testIPaddress');
return [ipWhiteList];
}
}
The IP white list profile inherits the IpWhiteListValidationSecurityProfile
and is a class with the @SecurityProfileSchema()
decorator. getIpWhiteList
should be implemented to return the appropriate IP address arrangement. The IpWhiteListGuard
or IpWhiteListMiddleware
that uses that profile is designed to handle requests pass through if any of the IpWhiteList in a given profile matches any one.
IP Black list profile
Deny all IP addresses in the black list.
Each ip address string supports the CIDR block notation method.
@SecurityProfileSchema()
export class NaiveBlackListProfile extends IpBlackListValidationSecurityProfile {
getIpBlackList(): string[] {
return [
'127.0.0.1',
/**
* - start : 192.168.16.0
* - end : 192.168.31.255
*/
'192.168.16.0/20',
/**
* - start : 192.168.0.5
* - end : 192.168.0.5
*/
'192.168.0.5/32',
];
}
}
@SecurityProfileSchema()
export class EnvBlackListProfile extends IpBlackListValidationSecurityProfile {
constructor(private readonly configService: ConfigService<any, true>) {
super();
}
getIpBlackList(): string[] {
const ipBlackList = this.configService.get<string>('testIPaddress');
return [ipBlackList];
}
}
The IP Black list profile is a class with the IpBlackList ValidationSecurityProfileSchema
decorator. getIpBlackList
should be implemented to return the appropriate IP address arrangement. The IpBlackListGuard
or IpBlackListMiddleware
that uses that profile is designed to reject requests if any of all IpBlackList in a given profile matches.
Signed CSRF Token Profile
for Generate CSRF Token and Validate CSRF Token in request
@SecurityProfileSchema()
export class JwtCSRFTokenProfile extends SignedCSRFTokenSecurityProfile {
getSessionIDforCreate(data: any): string | Promise<string> {
const accessToken = data?.accessToken;
const decoded = decode(accessToken) as any;
return decoded?.jti;
}
getSessionIDforValidate(request: Request): string | Promise<string> {
const accessToken = (request.headers as any).authorization;
const decoded = decode(accessToken) as any;
return decoded?.jti;
}
getSecretKey(): string | Promise<string> {
return 'secretKey';
}
}
You must implement a method that contradicts the secret value required to sign the csrf token. For security purposes, the secret must be imported from configService, etc.
getSessionIDforCreate
This method is used to automatically include signed CSRFs in the response header by applying the @Security.GenSignedCSRFToken
decorator. You must implement that method to return a unique value per authentication session.
The "unique value per session" is included in the CSRF token to be generated and signed. The CSRF token does not have a separate expiration period and must be naturally incorporated into the life cycle of the session. The example above is an example of returning the JWT token to the Response Body upon successful login.
// auth.controller.ts
@Controller('auth')
export class AuthController {
@Post('login')
@Security.GenSignedCSRFToken(JwtCSRFTokenProfile)
login() {
return {
accessToken: <<user jwt access token>>,
...
};
}
}
@Security.GenSignedCSRFToken
calls the getSessionIDforCreate implemented in the JwtCSRFTokenProfile to get a unique value per session to create a CSRF token and include it in the response header.
getSessionIDforValidate
This method is used to compare the authentication information included when signing a CSRF token on a controller or method with @Security.CheckSignedCSRFToken()
and the authentication information included in the current request.
// post.controller.ts
@Controller('post')
export class PostController {
@Post()
@Security.CheckSignedCSRFToken(JwtCSRFTokenProfile)
createPost(...) {}
}
In the above example, getSessionIDforValidate
is configured to decompose the jwt token in the autorization header of the request object and return the jwtId.
Therefore, getSessionIDforCreate
, getSessionIDforValidate
must be implemented to return "same value". However, it is designed to implement two methods separately because the way data is delivered when returning the credentials to Response and when entering Request may differ.
getSecretKey
The method must be implemented to return the key value to be used to sign the CSRF token. In general, it is best implemented to import from configService.
The signed CSRF token shall be signed based on the credentials contained in the request object. When using the JWT-based authentication protocol, one of the following data may be typically selected.
- JWT Access Token String
- Claim value unique to each JWT Access token, (e.g JWT "jti" claim)
If you are using a session-based authentication protocol, it is best to return the session key value.
IP White List
IP White List is a feature used to allow requests that come to a specific IP address only.
- If you want to restrict IP-based access to resources provided by a particular controller
- If you want to dynamically configure IP WhiteList across the Admin Server
- The package's security profile allows you to dynamically configure IP WhiteList by establishing DB connections, Redis connections.
Middleware Level
import { IpWhiteListMiddleware } from 'nestjs-security';
@Module({
imports: [],
controllers: [UserController],
providers: [],
})
class UserModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
IpWhiteListMiddleware.allowProfiles(PrivateIPWhiteListProfile, AdminWebIPWhiteListProfile),
)
.forRoutes(UserController);
}
}
The middleware registration usage is the same as the custom middleware registration method in Nestjs official document. Using IpWhiteListMiddleware.allowProfiles()
, deliver the security profile you want to handle with the middleware you previously registered as a provider.
Controller or API Method Level
// method level
@Controller()
class TestController {
@Get()
@Security.CheckIpWhiteList(NaiveWhiteListProfile)
allowSingleProfile() {
return true;
}
}
// class level
@Controller()
@Security.CheckIpWhiteList(NaiveWhiteListProfile, EnvWhiteListProfile)
class TestController {
@Get()
allowSingleProfile() {
return true;
}
}
If you want to apply a detailed IP White List check on an API endpoint or controller basis, you can use @Security.CheckIpWhiteList()
to apply it to any controller or method you want. Similarly, you can forward a SecurityProfile to set which security profile you want to apply.
Validation Failure
The IP Whitelist returns an error if all applied IpWhiteListProfiles do not pass regardless of where the middleware, class/method level is applied, which means "Request for an IP address that is not allowed." The error object is organized as follows, which can be found here.
NOTE: White List successfully handles at least one security profile if it is applied. (at-least-one)
export class ForbiddenIpAddressError extends SecurityModuleError {
constructor(profileName: string, ipAddress: string) {
super(`Forbidden IP address: ${ipAddress}, profile name: ${profileName}`, HttpStatus.FORBIDDEN);
}
}
IP Black List
Use the IP Black List to reject requests that come to a specific IP address, which is appropriate for security rules that allow you to reject certain IPs but allow all other requests.
- If you want to dynamically (in real time) block certain IPs urgently
- If you want to dynamically configure an IP Black List across the Admin server
- The package's security profile allows you to dynamically configure an IP Black List by building a DB connection, a Redis connection.
As with the IP White List, these features are provided separately by (1) using it for batch application at the middleware level and (2) using it for application at the controller class or API method level.
Middleware Level
import { IpBlackListMiddleware } from 'nestjs-security';
@Module({
imports: [],
controllers: [UserController],
providers: [],
})
class UserModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
IpBlackListMiddleware.allowProfiles(PrivateIPWhiteListProfile, AdminWebIPWhiteListProfile),
)
.forRoutes(UserController);
}
}
The middleware registration usage is the same as the custom middleware registration method in Nestjs official document. Using IpBlackListMiddleware.allowProfiles()
, deliver the security profile you want to handle with the middleware you previously registered as a provider.
Controller or API Method Level
// method level
@Controller()
class TestController {
@Get()
@Security.CheckIpBlackList(NaiveBlackListProfile)
allowSingleProfile() {
return true;
}
}
// class level
@Controller()
@Security.CheckIpBlackList(NaiveBlackListProfile)
class TestController {
@Get()
allowSingleProfile() {
return true;
}
}
If you want to apply a detailed IP White List check on an API endpoint or controller basis, you can use @Security.CheckIpBlackList()
to apply it to any controller or method you want. Similarly, you can forward a SecurityProfile to set which security profile you want to apply.
Validation Failure
The IP BlackList returns an error if one of applied IpBlackListSecurityProfile do not pass regardless of where the middleware, class/method level is applied, which means "Request for an IP address that is denied." The error object is organized as follows, which can be found here.
NOTE: Black List successfully handles all of security profile if it is applied. (for-every)
export class ForbiddenIpAddressError extends SecurityModuleError {
constructor(profileName: string, ipAddress: string) {
super(`Forbidden IP address: ${ipAddress}, profile name: ${profileName}`, HttpStatus.FORBIDDEN);
}
}
CSRF Protection
The package provides the ability to defend CSRF . The CSRF defense capabilities currently available are based on the CSRF tokens signed by the HMAC algorithm, including the authentication information contained in the session or JWT.
The package must be implemented by the developer in SignedCSRFTokenSecurityProfile to ensure security so that one of the following values is included in the CSRF token.
- A session-dependent value that changes with each login session
- A random value (e.g. UUID) within a JWT that changes every time a JWT is created.
The package provides additional security features for CSRF tokens, including random numbers and timestamp.
Controller or API Method Level
CSRF Token Generate
// auth.controller.ts
@Controller('auth')
export class AuthController {
@Post('login')
@Security.GenSignedCSRFToken(JwtCSRFTokenProfile) // set response.header['x-csrf-token']
login() {
return {
accessToken: <<user jwt access token>>,
...
};
}
}
Create a CSRF token using @Security.GenSignedCSRFToken
and automatically include the CSRF token you created in the 'x-csrf-token' response header. Both method level and class level are available.
You can easily issue CSRF tokens just by applying the decorator, but you need to configure the controller method knowing that the return value of the method with the decorator applied is delivered by the argument of getSessionIDforCreate
in SignedCSRFTokenSecurityProfile
.
CSRF Token Validate (Check)
// post.controller.ts
@Controller('post')
@Security.CheckSignedCSRFToken(JwtCSRFTokenProfile) // validate csrf token
export class PostController {
@Post()
createPost(...) {}
}
You can use @Security.CheckSignifiedCSRFToken
to import and verify the CSRF token contained in the 'x-csrf-token' request header. The token is a value hashed into the HMAC algorithm based on the value returned by the getSecretKey
in the SignedCSRFTokenSecurityProfile
, so it cannot be forged by an attacker unless you know the getSecretKey
.
You can also verify that the person who issued the CSRF token sent the request by comparing the authentication information returned by the getSessionIDforValidate
in the SignedCSRFSecurityProfile
with the authentication information contained in the current CSRF token.
Rate Limiting
NOTE : It will be added to the next version.
Contributing
Any additional feature suggestions or error reports and PRs for correcting existing features are all welcome!
- Fork it!
- Create your feature branch:
git checkout -b my-new-feature
- Add your changes:
git add .
- Commit your changes:
git commit -am 'Add some feature'
- Push to the branch:
git push origin my-new-feature
- Submit a pull request :sunglasses:
Error Reports
Only IPv4
Currently, the ipWhiteList and ipBlackList features support only ipv4 address formats. We will provide future versions to automatically distinguish between ipv6 and ipv4 address formats. Therefore, for nestjs systems that do not require an ipv6 address scheme, setting it as below can resolve most incorrect behavior.
// main.ts
await app.listen(3000, '0.0.0.0');
Versioning
We use SemVer for versioning. For the versions available, see the tags on this repository.
Authors
- Kim Beob Woo - Initial work - KimBeobWoo
See also the list of contributors who participated in this project.