$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
$ npm install --save @nestjs/jwt passport-jwt
$ npm install --save-dev @types/passport-jwt
Passport는 커뮤니티에 잘 알려져 있고 성공적으로 많은 app 제품에 사용된 nodeJS의 가장 인기있는 인증관련 라이브러리이다. passport는 @nestjs/passport 모듈에 통합되었습니다.
경로 : /users/dto/create-user.dto.ts
import { IsEmail, IsNumber, IsString } from 'class-validator';
export class CreateUserDto {
@IsEmail()
readonly email: string;
@IsString()
readonly username: string;
@IsString()
readonly password: string;
}
TypeScript는 제네릭 또는 인터페이스에 대한 메타 데이터를 저장하지 않기 때문에 DTO에서 사용할 때 ValidationPipe가 들어오는 데이터의 유효성을 제대로 검사하지 못 할 수 있습니다. 이러한 이유로 DTO에서 구체적인 클래스를 사용하는 것이 좋습니다.
경로 : /users/users.controller.ts
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { IdUserDto } from './dto/id-user.dto';
import { User } from './entities/user.entity';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly userService: UsersService) {}
@Get()
findAll(): Promise<User[]> {
return this.userService.findAll();
}
@Post()
async create(@Body() userData: CreateUserDto): Promise<User> {
return await this.userService.create(userData);
}
}
경로 : /users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
import { UserRepository } from './Repository/UserRepository';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: UserRepository, // 1. DB와의 연결을 정의
) {}
findAll(): Promise<User[]> {
console.log('유저 전체 불러오기');
return this.userRepository.find();
}
async create(userData: CreateUserDto): Promise<User> { // 2.
const { email, username, password } = userData;
const user = new User();
user.email = email;
user.password = password;
user.username = username;
await this.userRepository.save(user);
user.password = undefined;
console.log(user);
return user;
}
}
import { EntityRepository, Repository } from 'typeorm';
import { User } from '../entities/user.entity';
@EntityRepository(User)
export class UserRepository extends Repository<User> {
findByName(firstName: string, lastName: string) { // 1-1
return this.findOne({});
}
1_1.
DB와 같이 사용할 methods를 포함한 custom repository를 만들수 있다.
보통 single entity를 위해 만들어지고 그것의 특정 쿼리들을 포함한다.
Nest : Custom Repository
Typeorm : custom Repository예를 들어 findByName(firstName: string, lastName: string)로 이야기하면 이 method를 놓을 최적의 장소는 Repository(DB와 연결되는 곳)이다. 그래서 우리는 userRepository.findByName(...)와 같이 사용 할 수 있다.
유저 회원가입 -> users.controller.ts의 create -> users.service.ts의 create 함수 실행 -> DB에 저장(this.userRepository.save(user);) -> 출력
경로 : /auth/passport/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from '../auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) { // 1
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
console.log('로컬 스트레티지', username);
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
경로 : /auth/auth.module.ts
imports에 PassportModule를
providers에 LocalStrategy를 추가한다.
@Module({
imports: [
UsersModule,
PassportModule,
],
providers: [AuthService, LocalStrategy],
controllers: [AuthController],
})
export class AuthModule {}
경로 : /auth/auth.controller.ts
@UseGuards(AuthGuard('local')) // 1
@Post('local')
async login(@Req() req) {
return req.user;
}
what is guard? stackoverflow
nestJS guard org
post : localhost:3000/auth/local, {email: "admin@test.com, password: "admin"}
Postman으로 로그인 해보길 바랍니다.
유저 로그인 -> /auth/local 접근 -> Guard 실행 -> req.user 반환
경로 : /auth/passport/google.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { config } from 'dotenv';
import { Injectable } from '@nestjs/common';
config();
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID, // 1
clientSecret: process.env.GOOGLE_SECRET,
callbackURL: 'http://localhost:3000/auth/google/callback',
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
const { name, emails } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
accessToken,
refreshToken,
};
done(null, user);
}
}
@Module({
imports: [
UsersModule,
PassportModule,
],
providers: [AuthService, GoogleStrategy, LocalStrategy],
controllers: [AuthController],
})
@Get('google') // 1
@UseGuards(AuthGuard('google'))
async googleAuth(@Req() req) {}
@Get('google/callback') // 2
@UseGuards(AuthGuard('google'))
googleAuthRedirect(@Req() req) {
return this.authService.googleLogin(req);
}
googleLogin(req) {
if (!req.user) {
return 'No user from google';
}
return {
message: 'User information from google',
user: req.user,
};
}
구글 로그인에 성공 하면 localhost:3000/auth/google/callback 링크에서 해당 함수를 실행한다.(나중에 JWT 발급 처리)
유저 구글 로그인 -> localhost:3000/auth/google 접속 -> 성공 시 localhost:3000/auth/google/callback 접속 -> req.user를 JSON으로 반환(나중에 JWT 발급 추가)
경로 : /auth/passport/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from '../constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
경로 : /auth/auth.module.ts
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService, GoogleStrategy, LocalStrategy, JwtStrategy],
controllers: [AuthController],
})
경로 : /auth/constants.ts
export const jwtConstants = {
secret: 'secretKey',
};
경로 : /auth/auth.controller.ts
@UseGuards(AuthGuard('jwt'))
@Get('profile')
getProfile(@Req() req) {
return req.user;
}
@UseGuards(AuthGuard('local'))
@Post('local')
async login(@Req() req) {
return this.authService.login(req.user); // 1
// return req.user;
}
경로 : /auth/auth.service.ts
async login(user: any) {
const payload = {
username: user.username,
sub: user.userId,
};
return {
user,
access_token: this.jwtService.sign(payload),
};
}
유저 local 로그인 -> authService.login 실행 -> user와 jwt Token반환 -> localhost:3000/auth/profile 접속(Token이 유효한지 확인) -> req.user 반환
NestJS docs pipe
Should use DTO?
User 모듈을 Auth 모듈에서 Import할 때
User 모듈에서 UserService를 Export하면
Auth 모듈에서 사용 가능하다.
DTO가 정확히 뭔가요..? 필수적으로 사용해야 하는건가요??