NestJS触ってみる(JWT認証)
こちらの続きで書いてます↓
セットアップ
Auth モジュールを作成
$ yarn nest g module auth
↪︎ src/auth/auth.module.ts
が作成
↪︎ app.module.ts
の imports
に AuthModule
が追加
Auth コントローラーを作成
$ yarn nest g controller auth --no-spec
↪︎ src/auth/auth.controller.ts
が作成
↪︎ auth.module.ts
の controllers
に AuthController
が追加
Auth サービスを作成
$ yarn nest g service auth --no-spec
↪︎ src/auth/auth.service.ts
が作成
↪︎ auth.module.ts
の providers
に AuthService
が追加
usersテーブルの作成
schema.prisma
に User モデルを定義
// --------- 略 ---------
model User {
id String @id @default(uuid())
name String @unique
password String
status UserStatus
created_at DateTime @default(dbgenerated("NOW()")) @db.Timestamp(0)
updated_at DateTime @default(dbgenerated("NOW() ON UPDATE CURRENT_TIMESTAMP")) @db.Timestamp(0)
@@map("users")
}
enum UserStatus {
FREE // 無料会員
PREMIUM // 有料会員
}
// --------- 略 ---------
マイグレーションファイル作成とテーブル作成
$ yarn prisma migrate dev --name create_users_table
ユーザー作成機能の実装
DTOクラスの作成(バリデーション)
src
└── auth
└── dto
└── create-user.dto.ts ←新規作成
import { UserStatus } from '@prisma/client'
import {
IsEnum,
IsNotEmpty,
IsString,
MaxLength,
MinLength,
} from 'class-validator'
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string
@IsString()
@MinLength(8)
@MaxLength(32)
password: string
@IsEnum(UserStatus)
status: UserStatus
}
ユーザー作成機能
auth.module.ts
に PrismaService
を追加
import { Module } from '@nestjs/common'
+ import { PrismaService } from 'src/prisma.service'
import { AuthController } from './auth.controller'
import { AuthService } from './auth.service'
@Module({
controllers: [AuthController],
- providers: [AuthService],
+ providers: [AuthService, PrismaService],
})
export class AuthModule {}
auth.service.ts
に signUp
メソッドを実装
import { Injectable } from '@nestjs/common'
import { Prisma, User } from '@prisma/client'
import { PrismaService } from 'src/prisma.service'
import { CreateUserDto } from './dto/create-user.dto'
@Injectable()
export class AuthService {
constructor(private prisma: PrismaService) {}
async signUp(createUserDto: CreateUserDto): Promise<User> {
const { name, password } = createUserDto
const data: Prisma.UserCreateInput = {
name,
password,
status: 'FREE',
}
return this.prisma.user.create({ data })
}
}
auth.controller.ts
に @Post
を追加
import { Body, Controller, Post } from '@nestjs/common'
import { User } from '@prisma/client'
import { AuthService } from './auth.service'
import { CreateUserDto } from './dto/create-user.dto'
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signup')
async signup(@Body() createUserDto: CreateUserDto): Promise<User> {
return this.authService.signUp(createUserDto)
}
}
データベースに登録できるか確認
$ curl -X POST -d "name=山田 太郎&password=test1234&status=FREE" http://localhost:3000/auth/signup | jq
{
"id": "a7c9507c-6add-4b82-bdd6-d8c2………",
"name": "山田 太郎",
"password": "test1234",
"status": "FREE",
"created_at": "2022-04-11T18:16:55.000Z",
"updated_at": "2022-04-11T18:16:55.000Z"
}
パスワードのハッシュ化
$ yarn add bcrypt
$ yarn add --dev @types/bcrypt
auth.service.ts
を編集
import { Injectable } from '@nestjs/common'
import { Prisma, User } from '@prisma/client'
+ import * as bcrypt from 'bcrypt'
import { PrismaService } from 'src/prisma.service'
import { CreateUserDto } from './dto/create-user.dto'
@Injectable()
export class AuthService {
constructor(private prisma: PrismaService) {}
async createUser(createUserDto: CreateUserDto): Promise<User> {
const { name, password, status } = createUserDto
+ const salt = await bcrypt.genSalt()
+ const hashPassword = await bcrypt.hash(password, salt)
const data: Prisma.UserCreateInput = {
name,
- password,
+ password: hashPassword,
status,
}
return this.prisma.user.create({ data })
}
}
データベースにハッシュ化されて保存されるか確認
$ curl -X POST -d "name=山田 二郎&password=test1234&status=FREE" http://localhost:3000/auth/signup | jq
{
"id": "be06c8b1-7542-4c28-8340-97587ace801e",
"name": "山田 二郎",
"password": "$2b$10$r9CI3pNgnlJYRT………",
"status": "FREE",
"created_at": "2022-04-12T11:50:09.000Z",
"updated_at": "2022-04-12T11:50:09.000Z"
}
JWTの導入
$ yarn add @nestjs/jwt @nestjs/passport passport passport-jwt
$ yarn add --dev @types/passport-jwt
passport
↪︎ Node.js のための認証ミドルウェア
passport-jwt
↪︎ JWT の検証処理用
JWTの設定
.env
ファイルに JWT_SECRET_KEY を追加
JWT_SECRET_KEY="secretKey123"
@nestjs/configパッケージを使用して環境変数を読み込む
$ yarn add @nestjs/config
app.module.ts
で .env
ファイルを読み込む
import { Module } from '@nestjs/common'
+ import { ConfigModule } from '@nestjs/config'
import { ItemsModule } from './items/items.module'
import { AuthModule } from './auth/auth.module'
@Module({
- imports: [ItemsModule, AuthModule],
+ imports: [
+ ConfigModule.forRoot({ isGlobal: true }),
+ ItemsModule,
+ AuthModule
+ ],
controllers: [],
providers: [],
})
export class AppModule {}
auth.module.ts
を編集
import { Module } from '@nestjs/common'
+ import { ConfigService } from '@nestjs/config'
+ import { JwtModule } from '@nestjs/jwt'
+ import { PassportModule } from '@nestjs/passport'
import { PrismaService } from 'src/prisma.service'
import { AuthController } from './auth.controller'
import { AuthService } from './auth.service'
@Module({
+ imports: [
+ PassportModule.register({ defaultStrategy: 'jwt' }),
+ JwtModule.registerAsync({
+ useFactory: async (configService: ConfigService) => {
+ return {
+ secret: configService.get<string>('JWT_SECRET_KEY'),
+ signOptions: {
+ expiresIn: 3600,
+ },
+ }
+ },
+ inject: [ConfigService],
+ }),
+ ],
controllers: [AuthController],
providers: [AuthService, PrismaService],
})
export class AuthModule {}
ログイン機能
ユーザー認証用の DTO クラスを作成
src
└── auth
└── dto
├── create-user.dto.ts
└── credentials.dto.ts ←新規作成
import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'
export class CredentialsDto {
@IsString()
@IsNotEmpty()
name: string
@IsString()
@MinLength(8)
@MaxLength(32)
password: string
}
ログイン機能の実装
auth.service.ts
に signIn
メソッドを追加
// --------- 略 ---------
import { Injectable, UnauthorizedException } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { CredentialsDto } from './dto/credentials.dto'
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService
) {}
// --------- 略 ---------
async signIn(
credentialsDto: CredentialsDto,
): Promise<{ accessToken: string }> {
const { name, password } = credentialsDto
const user = await this.prisma.user.findUnique({
where: { name },
})
if (user && (await bcrypt.compare(password, user.password))) {
const payload = { id: user.id, name: user.name }
const accessToken = this.jwtService.sign(payload)
return { accessToken }
}
throw new UnauthorizedException(
'ユーザー名またはパスワードを確認してください',
)
}
}
auth.controller.ts
に sighIn
コントローラーを追加
// --------- 略 ---------
import { CredentialsDto } from './dto/credentials.dto'
@Controller('auth')
export class AuthController {
// --------- 略 ---------
@Post('signin')
async signIn(
@Body() credentialsDto: CredentialsDto,
): Promise<{ accessToken: string }> {
return this.authService.signIn(credentialsDto)
}
}
動作確認
間違ったパスワードでエラーが返ってくるか確認
$ curl -X POST -d "name=山田 二郎&password=test123123" http://localhost:3000/auth/signin | jq
{
"statusCode": 401,
"message": "ユーザー名またはパスワードを確認してください",
"error": "Unauthorized"
}
正しいパスワードで accessToken が返ってくるか確認
$ curl -X POST -d "name=山田 二郎&password=test1234" http://localhost:3000/auth/signin | jq
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR………"
}
JWTトークンの確認
↪︎ jwt.io
なんとなくわかったので一旦クローズ