Skip to content

Commit 7e67450

Browse files
2 parents 173c1b9 + d8d1bf5 commit 7e67450

File tree

2 files changed

+133
-21
lines changed

2 files changed

+133
-21
lines changed

content/security/rate-limiting.md

Lines changed: 131 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,145 @@
1-
### Rate limiting
1+
### Rate Limiting
22

3-
A common technique to protect applications from brute-force attacks is **rate-limiting**. Many Express packages exist to provide a rate-limiting feature. A popular one is [express-rate-limit](https://github.com/nfriedly/express-rate-limit).
3+
A common technique to protect applications from brute-force attacks is **rate-limiting**. To get started, you'll need to install the `@nestjs/throttler` package.
44

5-
#### Getting started
5+
```bash
6+
$ npm i --save @nestjs/throttler
7+
```
68

7-
Start by installing the required package:
9+
Once the installation is complete, the `ThrottlerModule` can be configured as any other Nest package with `forRoot` or `forRootAsync` methods.
810

9-
```bash
10-
$ npm i --save express-rate-limit
11+
```typescript
12+
@Module({
13+
imports: [
14+
ThrottlerModule.forRoot({
15+
ttl: 60,
16+
limit: 10,
17+
}),
18+
]
19+
})
20+
export class AppModule {}
1121
```
1222

13-
Once the installation is complete, apply the rate-limiter as global middleware.
23+
The above will set the global options for the `ttl`, the time to live, and the `limit`, the maximum number of requests within the ttl, for the routes of your application that are guarded.
24+
25+
Once the module has been imported, you can then choose how you would like to bind the `ThrottlerGuard`. Any kind of binding as mentioned in the [guards](https://docs.nestjs.com/guards) section is fine. If you wanted to bind the guard globally, for example, you could do so by adding this provider to any module:
1426

1527
```typescript
16-
import * as rateLimit from 'express-rate-limit';
17-
// somewhere in your initialization file
18-
app.use(
19-
rateLimit({
20-
windowMs: 15 * 60 * 1000, // 15 minutes
21-
max: 100, // limit each IP to 100 requests per windowMs
22-
}),
23-
);
28+
{
29+
provide: APP_GUARD,
30+
useClass: ThrottlerGuard
31+
}
2432
```
2533

26-
When there is a load balancer or reverse proxy between the server and the internet, Express may need to be configured to trust the headers set by the proxy in order to get the correct IP for the end user. To do so, first use the `NestExpressApplication` platform [interface](https://docs.nestjs.com/first-steps#platform) when creating your `app` instance, then enable the [trust proxy](https://expressjs.com/en/guide/behind-proxies.html) setting:
34+
#### Customization
35+
36+
There may be a time where you want to bind the guard to a controller or globally, but want to disable rate limiting for one or more of your endpoints. For that, you can use the `@SkipThrottle()` decorator, to negate the throttler for an entire class or a single route. The `@SkipThrottle()` decorator can also take in a boolean for if there is a case where you want to exclude _most_ of a controller, but not every route.
37+
38+
There is also the `@Throttle()` decorator which can be used to override the `limit` and `ttl` set in the global module, to give tighter or looser security options. This decorator can be used on a class or a function as well. The order for this decorator does matter, as the arguments are in the order of `limit, ttl`.
39+
40+
#### Websockets
41+
42+
This module can work with websockets, but it requires some class extension. You can extend the `ThrottlerGuard` and override the `handleRequest` method like so:
43+
44+
```typescript
45+
@Injectable()
46+
export class WsThrottlerGuard extends ThrottlerGuard {
47+
async handleRequest(context: ExecutionContext, limit: number, ttl: number): Promise<boolean> {
48+
const client = context.switchToWs().getClient();
49+
const ip = client.conn.remoteAddress;
50+
const key = this.generateKey(context, ip);
51+
const ttls = await this.storageService.getRecord(key);
52+
53+
if (ttls.length >= limit) {
54+
throw new ThrottlerException();
55+
}
56+
57+
await this.storageService.addRecord(key, ttl);
58+
return true;
59+
}
60+
}
61+
```
62+
63+
> info **Hint** If you are using the `@nestjs/platform-ws` package you can use `client._socket.remoteAddress` instead.
64+
65+
#### GraphQL
66+
67+
The `ThrottlerGuard` can also be used to work with GraphQL requests. Again, the guard can be extended, but this tme the `getRequestResponse` method will be overridden
2768

2869
```typescript
29-
const app = await NestFactory.create<NestExpressApplication>(AppModule);
30-
// see https://expressjs.com/en/guide/behind-proxies.html
31-
app.set('trust proxy', 1);
70+
@Injectable()
71+
export class GqlThrottlerGuard extends ThrottlerGuard {
72+
getRequestResponse(context: ExecutionContext) {
73+
const gqlCtx = GqlExecutionContext.create(context);
74+
const ctx = gql.getContext();
75+
return { req, ctx.req, res: ctx.res }
76+
}
77+
}
3278
```
3379

34-
> info **Hint** If you use the `FastifyAdapter`, use the [fastify-rate-limit](https://github.com/fastify/fastify-rate-limit) package instead.
80+
#### Configuration
81+
82+
The following options are valid for the `ThrottlerModule`:
83+
84+
<table>
85+
<tr>
86+
<td><code>ttl</code></td>
87+
<td>the number of seconds that each request will last in storage</td>
88+
</tr>
89+
<tr>
90+
<td><code>limit</code></td>
91+
<td>the maximum number of requests within the TTL limit</td>
92+
</tr>
93+
<tr>
94+
<td><code>ignoreUserAgents</code></td>
95+
<td>an array of regular expressions of user-agents to ignore when it comes to throttling requests</td>
96+
</tr>
97+
<tr>
98+
<td><code>storage</code></td>
99+
<td> the storage setting for how to keep track of the requests</td>
100+
</tr>
101+
</table>
102+
103+
#### Async Configuration
104+
105+
You may want to get your rate-limiting configuration asynchronously instead of synchronously. You can use the `forRootAsync()` method, which allows for dependency injection and `async` methods.
106+
107+
One approach would be to use a factory function:
108+
109+
```typescript
110+
@Module({
111+
imports: [
112+
ThrottlerModule.forRootAsync({
113+
imports: [ConfigModule],
114+
inject: [ConfigService],
115+
useFactory: (config: ConfigService) => ({
116+
ttl: config.get('THROTTLE_TTL'),
117+
limit: config.get('THROTTLE_LIMIT'),
118+
}),
119+
}),
120+
],
121+
})
122+
export class AppModule {}
123+
```
124+
125+
You can also use the `useClass` syntax:
126+
127+
```typescript
128+
@Module({
129+
imports: [
130+
ThrottlerModule.forRootASync({
131+
imports: [ConfigModule],
132+
useClass: ThrottlerConfigService,
133+
}),
134+
],
135+
})
136+
export class AppModule {}
137+
```
138+
139+
This is doable, as long as `ThrottlerConfigService` implements the interface `ThrottlerOptionsFactory`.
140+
141+
#### Storages
142+
143+
The built in storage is an in memory cache that keeps track of the requests made until they have passed the TTL set by the global options. You can drop in your own storage option to the `storage` option of the `ThrottlerModule` so long as the class implements the `ThrottlerStorage` interface.
144+
145+
> info **Note** `ThrottlerStorage` can be imported from `@nestjs/throttler`.

renovate.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"automerge": true
77
}],
88
"extends": [
9-
"config:base"
9+
"config:base",
10+
"schedule:earlyMondays"
1011
]
1112
}

0 commit comments

Comments
 (0)