Tích Hợp Xác Thực Auth0 với NestJS trong Thiết Lập Đa Thuê
Gần đây, tôi được yêu cầu thiết kế và triển khai một ứng dụng SaaS đa thuê (multi-tenant) trong NestJS với yêu cầu sử dụng Auth0 cho xác thực. Đây là lần đầu tiên tôi làm việc với Auth0, và ban đầu tôi đã khá bối rối với các tính năng đa thuê của nó. Sau một thời gian tìm hiểu, tôi nhận ra một điểm quan trọng: các thuê bao của ứng dụng của tôi không giống như các thuê bao trong Auth0.
Sự Phân Biệt Giữa Thuê Bao và Tổ Chức trong Auth0
Các thuê bao trong Auth0 được thiết kế để tách biệt các môi trường (như phát triển, thử nghiệm và sản xuất), trong khi các tổ chức trong một thuê bao Auth0 duy nhất được sử dụng để đại diện cho các thuê bao thực tế của ứng dụng của bạn.
Các Bước Tích Hợp Auth0 với NestJS
1. Thiết Lập Ứng Dụng NestJS
Nếu bạn chưa thiết lập, hãy tạo một ứng dụng NestJS cơ bản. Tôi sẽ sử dụng mã khởi đầu cho ví dụ này.
2. Tạo Tài Khoản Auth0
Đăng nhập vào Auth0 (hoặc tạo một tài khoản mới). Nếu đây là tài khoản mới, bạn sẽ được hỏi nếu nó là cho công ty hoặc sử dụng cá nhân. Trong trường hợp của tôi, tôi đã chọn cá nhân.
3. Tạo Ứng Dụng
Tiếp theo, bạn sẽ được nhắc tạo ứng dụng đầu tiên của mình, hoặc bạn có thể điều hướng đến Applications ở menu bên trái và tạo từ đó. Nếu bạn đã có một ứng dụng frontend/client, hãy chọn nó. Trong ví dụ này, tôi sẽ sử dụng Regular Web Application.
4. Cấu Hình Cài Đặt Ứng Dụng
Đi thẳng đến tab Settings, ghi lại Domain, Client ID và Client Secret vì bạn sẽ cần chúng để xác thực client với API backend. Tiếp theo, di chuyển đến tab Login Experience, chọn Business Users, giữ No prompt đã chọn và lưu lại.
Cuộn xuống phần Application URIs và cấu hình:
- Application Login URI:
https://127.0.0.1:3000/login
- Allowed Callback URLs:
https://127.0.0.1:3000/callback
- Allowed Logout URLs:
https://127.0.0.1:3000/logout
Hãy sử dụng https và 127.0.0.1 thay vì localhost để tránh lỗi. Sau đó, lưu lại.
5. Tạo API trong Auth0
Đi đến APIs trong menu bên trái. Nhấp vào Create API, đặt tên và xác định danh tính của nó (danh tính này trở thành tham số audience trong các cuộc gọi ủy quyền). Tôi đã đặt tên là https://api.myapp.com
. Bạn có thể đặt tên tùy ý vì nó sẽ không bao giờ được gọi.
6. Tạo Tổ Chức
Đi đến Organizations, sau đó nhấp vào Create Organization. Đặt tên cho nó (tôi đã đặt tên là org1). Tiếp theo, đi đến tab Connections, kích hoạt các phương thức đăng nhập mong muốn (tôi đã sử dụng Username-Password-Authentication).
7. Mời Người Dùng vào Tổ Chức của Bạn
Đi đến tab Invitations và nhấp vào Invite Members. Chọn ứng dụng client mà chúng ta đã tạo ở bước 3, nhập email của người dùng mà bạn muốn mời, chọn kết nối mà bạn đã tạo ở bước trước đó. Tùy chọn, bạn cũng có thể chọn vai trò nếu bạn đã tạo chúng, tôi sẽ để trống cho đến bây giờ. Sau đó, nhấp vào Send Invites. Auth0 sẽ gửi một email với liên kết mời như sau:
https://127.0.0.1:3000/login?invitation=INVITATION_CODE&organization=org_YOUR_ORGANIZATION_ID&organization_name=YOUR_ORGANIZATION_NAME
8. Chấp Nhận Lời Mời
Để chấp nhận lời mời, hãy lấy mã INVITATION_CODE
từ liên kết trên và điều hướng đến:
https://YOUR_DOMAIN/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https://127.0.0.1:3000/callback&organization=org_YOUR_ORGANIZATION_ID&invitation=INVITATION_CODE
Bạn sẽ đến trang ủy quyền của Auth0, tạo mật khẩu của bạn và ủy quyền cho ứng dụng client. Sau đó, bạn sẽ được chuyển hướng đến:
https://127.0.0.1:3000/callback?code=AUTHORIZATION_CODE
Trao Đổi Mã Ủy Quyền Để Lấy Mã Truy Cập
Chúng ta sẽ cấu hình NestJS để chấp nhận và xác thực các token Auth0. Mã khởi đầu của chúng ta có một endpoint /
trả về Hello World!
. Hãy bảo vệ nó.
9. Cài Đặt Các Gói Cần Thiết
npm i passport @nestjs/passport passport-jwt jwks-rsa
npm i --save-dev @types/passport-jwt
10. Tạo Chiến Lược JWT
auth/jwt.strategy.ts
typescript
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'YOUR_ISSUER_URL.well-known/jwks.json',
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: 'YOUR_AUDIENCE',
issuer: 'YOUR_ISSUER_URL',
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
return payload;
}
}
YOUR_ISSUER_URL=https://YOUR_DOMAIN/
YOUR_AUDIENCE=YOUR_IDENTIFIER
11. Tạo Module Xác Thực và Nhập Chiến Lược JWT
auth/auth.module.ts
typescript
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
providers: [JwtStrategy],
exports: [PassportModule],
})
export class AuthModule {}
12. Nhập Module Xác Thực vào app.module.ts
typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [AuthModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
13. Bảo Vệ Endpoint Trong app.controller.ts
typescript
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '@nestjs/passport';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@UseGuards(AuthGuard('jwt'))
@Get()
getHello(): string {
return this.appService.getHello();
}
}
14. Lấy Mã Truy Cập
Sử dụng curl để trao đổi mã ủy quyền:
curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=AUTHORIZATION_CODE \
--data client_id=YOUR_CLIENT_ID \
--data client_secret=YOUR_CLIENT_SECRET \
--data redirect_uri=https%3A%2F%2F127.0.0.1%3A3000%2Fcallback
Bạn sẽ nhận được một token như:
{"access_token":"eyJhb...THE_TOKEN...nA","expires_in":86400,"token_type":"Bearer"}%
15. Truy Cập Thông Tin Người Dùng Trong NestJS
Payload người dùng sẽ được thêm vào ExecutionContext
, bạn có thể truy cập bên trong một controller hoặc guard:
typescript
const request = context.switchToHttp().getRequest<Request & { user: { sub: string; org_id: string}>();
const user = request.user;
Nếu bạn muốn thêm dữ liệu người dùng, hãy thêm &scope=openid profile email
vào URL đăng nhập của bạn:
https://YOUR_DOMAIN/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https://127.0.0.1:3000/callback&organization=org_YOUR_ORGANIZATION_ID&scope=openid profile email
Với các bước này, bạn đã có một thiết lập xác thực đa thuê hoạt động trong NestJS sử dụng tổ chức Auth0, hoàn chỉnh với org_id
trong payload token để thực hiện các logic nhận biết thuê bao.
Kết Luận
Hy vọng hướng dẫn này sẽ giúp ích cho một ai đó. Nếu bạn có bất kỳ câu hỏi nào, hãy để lại bình luận. Tôi sẽ rất vui được hỗ trợ.
Thực Hành Tốt Nhất
- Đảm bảo rằng bạn luôn cập nhật các gói phụ thuộc của mình để bảo mật và hiệu suất tối ưu.
- Sử dụng HTTPS trong tất cả các môi trường sản xuất để bảo mật dữ liệu.
Các Cạm Bẫy Thường Gặp
- Không thiết lập đúng các biến môi trường có thể dẫn đến lỗi trong xác thực.
- Bỏ qua việc kiểm tra token đầu vào có thể tạo ra lỗ hổng bảo mật.
Mẹo Hiệu Suất
- Sử dụng cache cho các token JWT để giảm tải cho máy chủ xác thực.
- Tối ưu hóa lưu trữ và truy xuất thông tin người dùng từ cơ sở dữ liệu để tăng tốc độ phản hồi.
Giải Quyết Vấn Đề
- Nếu gặp lỗi 401 Unauthorized, hãy kiểm tra lại token và các biến môi trường.
- Đảm bảo rằng cấu hình xác thực trong Auth0 tương thích với ứng dụng của bạn.