使用 Auth0 保护您的 NestJS 应用程序
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
简而言之:本文将解释如何使用Auth0保护基于NestJS的 API 。此外,还包含 Auth0 管理 API,用于接收当前登录用户的个人资料。 如果您不想阅读本文,可以直接访问GitHub 代码库。
上周我遇到了将 Auth0 连接到我的 NestJS 项目的问题。
我希望限制 API 的某些端点对公众开放。
由于我不想将用户数据和密码存储在自己的服务器上,所以我决定使用 Auth0。
我使用 NestJS 作为后端应用程序框架已经有一段时间了。NestJS 让新手用户很容易上手。
它提供了一个成熟的命令行界面 (CLI),并且已经提供了许多实用功能,可以根据需要集成到项目中(例如数据库连接、验证、日志记录或 HTTP 请求)。
我当时非常绝望,找不到可行的解决方案。我在 Twitter 和 dev.to 上都寻求过帮助:
文章已失效
我收到了一些私信,里面有很多非常有用的建议。
可惜我还是没能成功,所以我决定休息几天,不再想这件事。
事实证明这是个非常好的主意,最终我解决了这个问题🥳。
配置 Auth0
如果您还没有 Auth0 账号,请前往他们的注册页面创建一个。Auth0
提供慷慨的免费套餐,包括无密码用户账号、两个社交身份提供商(例如 Google、Facebook、Twitter 或 GitHub)以及社区支持。对于
本教程和一些小型项目而言,免费套餐应该足够用了。
创建 API
登录控制面板后,导航至API部分,然后点击“创建 API”按钮创建一个新的 API。
接下来,系统会提示您输入 API 名称和标识符。Auth0
建议使用 URL 作为标识符,并使用一个易于理解的 API 名称。
签名算法应保留默认选项 (RS256)。
创建应用程序
Auth0 在创建新应用时会要求指定应用类型。
在本例中,应用类型无关紧要。对于公开应用,您可能需要了解更多应用类型信息,以便选择符合您需求的类型。
应用程序创建完成后,您需要配置允许的回调 URL。回调 URL 是用户成功通过身份验证后可以被重定向到的位置。
启用 Auth0 管理 API
由于我们之后还需要读取已认证用户的个人资料,因此必须为刚刚创建的应用程序启用 Auth0 管理 API。启用 Auth0 管理 API 的配置信息可以在“API”
选项卡 中找到。 此外,还必须选择read:users权限范围。
登录
稍后我们需要生成一个访问令牌才能使用 API。该令牌通过 Auth0 登录表单生成。
登录表单由 Auth0 提供,可通过您的 Auth0 租户域访问。
您还需要 Auth0 客户端 ID。这两个值都可以从您的应用程序配置页面复制。
替换网址中的值,然后在浏览器中打开它。
https://$AUTH0_DOMAIN/authorize?audience=http://localhost:3000&scope=SCOPE&response_type=code&client_id=$AUTH0_CLIENT_ID&redirect_uri=http://localhost:4200/login&state=STATE?prompt=none
使用单点登录 (SSO) 提供商(默认配置为 Google)或电子邮件和密码登录后,
登录表单会将您重定向到http://localhost:4200/login,该地址已预先配置。
即使没有 Web 应用程序运行也没关系,但您需要复制code查询参数的值并保存以备后用。
此code值用于生成 Bearer 令牌。
更新用户元数据
Auth0 的一个优点是,您可以将元数据附加到用户配置文件。
您可以使用用户元数据将用户配置文件链接到其他服务(例如 GitHub 或 dev.to 配置文件)——我相信您会发现许多其他用例。
创建新的 NestJS 应用程序
$ npm i -g @nestjs/cli
$ nest new nestjs-auth0-jwt
如果一切顺利,将会创建一个新的 NestJS 应用程序,其目录结构如下:
nestjs-auth0-jwt
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json
运行应用程序
导航到新目录并启动 API:
$ cd nestjs-auth0-jwt
$ npm run start:dev
应用程序运行后,您可以打开浏览器并访问http://localhost:3000。
添加依赖项
$ npm install --save @nestjs/passport passport passport-jwt jwks-rsa auth0
$ npm install --save-dev @types/passport-jwt @types/auth0
创建身份验证模块
$ nest generate module auth
创建 JWT 策略
$ touch auth/jwt.strategy.ts
import { passportJwtSecret } from 'jwks-rsa';
import { ExtractJwt, Strategy, VerifiedCallback } from 'passport-jwt';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 1
audience: 'http://localhost:3000',
issuer: `https://${process.env.AUTH0_DOMAIN}/`
});
}
validate(payload: any, done: VerifiedCallback) {
if (!payload) {
done(new UnauthorizedException(), false); // 2
}
return done(null, payload);
}
}
1:ExtractJwt.fromAuthHeaderAsBearerToken()创建一个新的提取器,用于在授权标头中查找方案为“bearer”的 JSON Web Token。
2:如果没有收到有效载荷,则身份验证失败。
更新身份验证模块
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [PassportModule],
providers: [JwtStrategy],
exports: [JwtStrategy]
})
export class AuthModule {}
创建 API 端点
import { ManagementClient, User } from 'auth0';
import * as express from 'express';
import { Controller, Get, Request, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello(); // 1
}
@Get('secret')
@UseGuards(AuthGuard('jwt')) // 2
secretEndpoint(@Request() req: express.Request): string {
return 'this endpoint should be protected';
}
@Get('profile')
@UseGuards(AuthGuard('jwt')) // 2
async profile(@Request() req: express.Request): Promise<any> {
const authZero = new ManagementClient({
// 3
domain: process.env.AUTH0_DOMAIN,
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
scope: 'read:users update:users'
});
const response = await authZero
.getUser({ id: req.user.sub }) // 4
.then((user: User) => {
return user;
})
.catch(err => {
return err;
});
return response;
}
}
1:公开可用的 API 端点。
2:@UseGuards(AuthGuard('jwt'))
3:创建 Auth0 管理 API 客户端。
4:JWT响应中的主题与Auth0用户ID相同。因此,该sub属性用作调用的id参数getUser,该调用会返回完整的用户配置文件。
使用登录码检索 Bearer Token
$ export AUTH0_DOMAIN=${AUTH0_DOMAIN}
$ export AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID}
$ export AUTH0_CLIENT_SECRET=${AUTH0_CLIENT_SECRET}
$ export CODE=${CODE}
$ curl -X POST -H 'content-type: application/json' -d '{
"grant_type": "authorization_code",
"client_id": "'$AUTH0_CLIENT_ID'",
"client_secret": "'$AUTH0_CLIENT_SECRET'",
"code": "'$CODE'",
"redirect_uri": "http://localhost:4200"
}' https://$AUTH0_DOMAIN/oauth/token
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik9EQXpOMEZHTWpBd1FVUTJORFpETXpBMVJETXdSRUUyTjBSRE5qRXpNemRFUWtNMk5URTFNUSJ9.eyJpc3MiOiJodHRwczovL25lc3Rqcy1hdXRoMC1qd3QuYXUuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTEzMDc2NzYwNTUyNTQ5MTY1ODQ5IiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaWF0IjoxNTY1OTg1MzU3LCJleHAiOjE1NjYwNzE3NTcsImF6cCI6IjR0eFZVSm5ZVlRhbzRsczA2dFozRGNCdm5JVk9uY01RIn0.U3dVcaFPY5ZHlz9sntx3O2Svz_HfapBpryrUB9ipIdoIHgu_Skp40FGaLu0Fx-_Uo2GE4pfvM-XR1bQgQGyVbMhALpe2CZrKKCe9k1v4VZ_zxwhYDdV8WNr99jmbMtnm_I9rZIz3YU9dyjWlV_ktHV0bPHj1wIjBUrUc9P_EF5Vw3CeNMxlFXZ2xYldT9XdYUotJHIoJ-e_KWo0hMn_qF5xvWxD-RJIQL7G2ZxEcsmMe9JovwZoAnoqaIyutMP8g7X1UfoTGs-Fa6B1xXhtrYBms--sm_FrM5w0rjIyOuyujulPTeXO8_CbuL1Yz5kCcBsuJdXTiyTcTV9R8W0f4Rg",
"expires_in": 86400,
"token_type": "Bearer"
}
运行应用程序
将变量替换为您之前保存的值${AUTH0_DOMAIN},然后启动 NestJS 应用程序。${AUTH0_CLIENT_ID}${AUTH0_CLIENT_SECRET}
$ export AUTH0_DOMAIN=${AUTH0_DOMAIN}
$ export AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID}
$ export AUTH0_CLIENT_SECRET=${AUTH0_CLIENT_SECRET}
$ npm run start:dev
无需身份验证即可发出请求
应用程序运行后,使用 cURL 访问 API 端点/secret。 由于这两个端点都已标记了注解,因此无法公开访问,/profile您将收到 401(未授权)错误。AuthGuard
$ curl http://localhost:3000/secret
$ curl http://localhost:3000/profile
使用身份验证发出请求
现在您可以运行相同的请求,但通过标头提供访问令牌Authorization。
由于您之前已授权自己,现在您可以访问两个私有端点/secret。/profile
该/profile端点还会返回您从 Auth0 收到的个人资料信息,这些信息来自 Auth0 管理 API。
Auth0 管理 API 使用subJWT 响应中的 (subject) 字段来检索您的用户个人资料。
为了便于文档记录,示例应用程序中的 JWT 响应已记录到控制台。
$ curl http://localhost:3000/secret \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik9EQXpOMEZHTWpBd1FVUTJORFpETXpBMVJETXdSRUUyTjBSRE5qRXpNemRFUWtNMk5URTFNUSJ9.eyJpc3MiOiJodHRwczovL25lc3Rqcy1hdXRoMC1qd3QuYXUuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTEzMDc2NzYwNTUyNTQ5MTY1ODQ5IiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaWF0IjoxNTY1OTg1MzU3LCJleHAiOjE1NjYwNzE3NTcsImF6cCI6IjR0eFZVSm5ZVlRhbzRsczA2dFozRGNCdm5JVk9uY01RIn0.U3dVcaFPY5ZHlz9sntx3O2Svz_HfapBpryrUB9ipIdoIHgu_Skp40FGaLu0Fx-_Uo2GE4pfvM-XR1bQgQGyVbMhALpe2CZrKKCe9k1v4VZ_zxwhYDdV8WNr99jmbMtnm_I9rZIz3YU9dyjWlV_ktHV0bPHj1wIjBUrUc9P_EF5Vw3CeNMxlFXZ2xYldT9XdYUotJHIoJ-e_KWo0hMn_qF5xvWxD-RJIQL7G2ZxEcsmMe9JovwZoAnoqaIyutMP8g7X1UfoTGs-Fa6B1xXhtrYBms--sm_FrM5w0rjIyOuyujulPTeXO8_CbuL1Yz5kCcBsuJdXTiyTcTV9R8W0f4Rg'
$ curl http://localhost:3000/profile \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik9EQXpOMEZHTWpBd1FVUTJORFpETXpBMVJETXdSRUUyTjBSRE5qRXpNemRFUWtNMk5URTFNUSJ9.eyJpc3MiOiJodHRwczovL25lc3Rqcy1hdXRoMC1qd3QuYXUuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTEzMDc2NzYwNTUyNTQ5MTY1ODQ5IiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaWF0IjoxNTY1OTg1MzU3LCJleHAiOjE1NjYwNzE3NTcsImF6cCI6IjR0eFZVSm5ZVlRhbzRsczA2dFozRGNCdm5JVk9uY01RIn0.U3dVcaFPY5ZHlz9sntx3O2Svz_HfapBpryrUB9ipIdoIHgu_Skp40FGaLu0Fx-_Uo2GE4pfvM-XR1bQgQGyVbMhALpe2CZrKKCe9k1v4VZ_zxwhYDdV8WNr99jmbMtnm_I9rZIz3YU9dyjWlV_ktHV0bPHj1wIjBUrUc9P_EF5Vw3CeNMxlFXZ2xYldT9XdYUotJHIoJ-e_KWo0hMn_qF5xvWxD-RJIQL7G2ZxEcsmMe9JovwZoAnoqaIyutMP8g7X1UfoTGs-Fa6B1xXhtrYBms--sm_FrM5w0rjIyOuyujulPTeXO8_CbuL1Yz5kCcBsuJdXTiyTcTV9R8W0f4Rg'
我希望这篇文章能让你了解如何在 NestJS 应用程序中实现像 Auth0 这样的身份验证提供程序。
将 Auth0 和身份验证集成到我的项目中非常麻烦,但最终我还是成功了,感觉真的很棒。
我也认为 NestJS 的身份验证文档可以改进,以便用户更容易正确实现并避免安全漏洞。不过,我并不抱怨,毕竟 NestJS 是开源的,任何人都可以参与文档的改进。
如果您对 NestJS、Auth0 或其他主题有任何疑问,请随时通过Twitter、dev.to或邮件yo@fullstack.to私信我。我很乐意为您提供帮助!
链接
如果你喜欢我的内容,不妨在推特上关注我:@fullstack_to
封面图片来自Unsplash的Jason Blackeye
文章来源:https://dev.to/matthias/use-auth0-to-secure-your-nestjs-application-mbo













我无法在我的 NestJS 项目中配置 Auth0 身份验证。我也在这里寻求过帮助:
使用 NestJS 进行 JWT 身份验证 (Auth0) 🔐
Matthias 🤖 ・ 8月14日 ・ 阅读时长:1分钟
这个问题困扰了我好几天,然后我休息了一天左右,没碰代码。之后
我头脑清醒地重新开始,一个小时就解决了问题🥳。
这就是我热爱编程的原因,你总能体验到取得成就的成就感💪。
而且通常每隔几个小时或几天(取决于项目规模)就会有小小的突破。对我来说,这就是工作中最大的动力!
顺便说一句:我正在写一篇文章,介绍我是如何解决这个问题的😉。