Nest.js で Cookie 認証API を実装する
Nest.js で Cookie 認証 API を実装する
Nest.js で 認証用の API を実装していきます。
構成
- Nest.js
- Postgres
作成する 認証向けの REST API では、
SPA での利用を考慮し Cookie ベースの認証を採用しています。
作成するエンドポイント
- GET /api/profile
- POST /api/signup
- POST /api/login
インストール
Nest.js のプロジェクトに以下のモジュールをインストールします。
.env
を用いた環境構築のために @nestjs/config を利用します。
$ npm i @nestjs/config
Cookie 認証を利用するために express-session を利用します。
$ npm i express-session
DB に Postgres を利用するために typeorm と pg を導入します。
$ npm i @nestjs/typeorm typeorm pg
DB のセットアップ
typeorm.ts
を作成してプロジェクトをセットアップします。
// need for typeorm cli
require('dotenv').config();
const config = {
type: 'postgres',
url: process.env.DATABASE_URL,
entities: [
"./dist/src/entities/*.js"
],
migrations: [
"./dist/src/migrations/*.js"
],
cli: {
migrationsDir: './src/migrations',
},
}
module.exports = config
src/entities
フォルダを作成して、user.entity.ts
を作成します。
import {Entity, PrimaryGeneratedColumn, Column, Index} from "typeorm";
@Entity("t_users")
export class UserEntity {
@PrimaryGeneratedColumn("uuid")
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
@Index({unique:true})
email: string;
@Column()
password: string;
}
Entity を用意したあとで、migration:generate
を実行すると、migration ファイルが作成されます。
$ npx ts-node ./node_modules/.bin/typeorm migration:generate -n UserTables
migration:run
を実行して、データベーステーブルを作成します。
$ npx ts-node ./node_modules/.bin/typeorm migration:run
会員登録の実装
会員登録の流れは以下のとおりです。
- 会員データを DB に登録
- セッションにユーザデータを格納
Nest.js におけるセッションのセットアップは以下を確認してください。
session 管理用の session.service.ts
を作成し、app.module.ts
に登録します。
import {Inject, Injectable, Scope} from "@nestjs/common";
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
import {UserEntity} from "./entities/user.entity";
@Injectable({ scope: Scope.REQUEST })
export class SessionService {
constructor(@Inject(REQUEST) private request: Request) {}
private session(): any{
return this.request.session
}
get user():UserEntity{
return this.session().user
}
set user(user:UserEntity){
this.session().user = user
}
}
app.service.ts
にユーザ作成用の signup 関数を追加します。
import {Injectable} from '@nestjs/common';
import {Connection, getConnection} from "typeorm";
import {UserEntity} from "./entities/user.entity";
@Injectable()
export class AppService {
// ...
async signup(
email: string,
password: string,
firstName: string,
lastName: string,
): Promise<any> {
const connection: Connection = await getConnection();
const user = new UserEntity()
user.password = password //TODO hashed
user.email = email
user.firstName = firstName
user.lastName = lastName
await connection.manager.save(user)
return {user}
}
}
app.controller.ts
に signup を追加します。
import {Body, Controller, Get, Post} from '@nestjs/common';
import { AppService } from './app.service';
import {UserEntity} from "./entities/user.entity";
import {SessionService} from "./session.service";
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly session: SessionService,
) {}
// ...
@Post("signup")
async signup(
@Body() body
): Promise<any> {
const {user} = await this.appService.signup(
body.email,
body.password,
body.first_name,
body.last_name
);
this.session.user = user
return {
user
};
}
}
curl で API を実行して、DB にデータが格納されれば成功です。
プロフィールの実装
プロフィール API では、以下の処理を実装します。
- セッションにデータが格納されているかをチェック(Guard)
- セッションのデータを返却
まずは、guard/auth.guard.ts
を作成します。
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import {SessionService} from "../session.service";
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private session: SessionService) {
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const user = this.session.user
if(user){
// セッション内のユーザデータが信用できない運用のときはここでデータを更新する
return true
}else{
return false
}
}
}
app.controller.ts
に以下の形で login を追加します。
import {Body, Controller, Get, Post, Session, UseGuards} from '@nestjs/common';
// ...
import {AuthGuard} from "./guard/auth.guard";
@Controller()
export class AppController {
//...
@UseGuards(AuthGuard)
@Get("/profile")
async profile(){
return {
user: this.session.user
}
}
}
curl で signup 後に profile を叩いて、ユーザデータが取得できるかを確認します。
curl で cookie を有効にするには、以下のように -cb で クッキーファイルを指定します。
curl http://localhost:4200/api/profile -X GET -d {} -b cookie.txt -c cookie.txt
ログインの実装
最後に ログインの実装です。ログインAPIでは以下のような実装を追加します。
- DB からユーザの取得
- ユーザデータの返却
app.service.ts
に以下のような形で login 関数を追加します。
//...
@Injectable()
export class AppService {
// ...
async login(email: string, password: string): Promise<any> {
const connection: Connection = await getConnection();
const user = await connection.manager.findOne(
UserEntity,
{
email,password
}
)
return user
}
}
app.controller.ts
に login 関数を追加します。
//...
@Controller()
export class AppController {
//...
@Post("/login")
async login(@Body() body){
const {user,auth} = await this.appService.login(
body.email,
body.password
);
this.session.user = user
return {
user: this.session.user
}
}
}
注記
記載しているコードは、実装のフローを紹介するためのサンプルコードです。
Discussion