[Nest.js 학습일기] 로그인/회원가입 구현 (-토큰 발급까지)
[Nest.js 학습일기] 로그인/회원가입 구현 (-토큰 발급까지)
진행 과정
- CLI활용해서 auth 관련 module을 만든다.
- @nestjs/jwt와 bcrypt 설치
- service => controller 순으로 코드를 작성한다. (import는 module파일 확인)
auth.service.ts
만들어야하는 기능은 다음과 같다.
- register (회원가입)
- 이메일, 닉네임, 비밀번호를 입력받고 회원가입
- 회원가입 이후 토큰을 발급하여 바로 로그인 처리한다.
- login
- 이메일과 비밀번호를 통해 사용자 검증을 진행한다.
- 검증이 완료되면 토큰을 반환한다.
- 토큰 반환
- 토큰을 반환하는 로직이다.
- 토큰 생성
- 3에서 필요한 토큰을 생성하는 로직이다.
- 사용자 검증 과정
- 사용자가 존재하는지 확인한다.
- 비밀번호가 일치하는지 확인한다.
- 둘다 유효하다면 사용자 정보와 토큰을 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
@Injectable()
export class AuthService {
constructor(
// jwtService를 주입하기 위해선 summary.module에 imports 해야한다.
private readonly jwtService: JwtService,
private readonly usersService: UsersService
) {}
/**
* 만드려는 기능
* 1. register (email 기반)
* - 이메일 닉네임 password 입력 받는다.
* - 생성이 완료된 경우 token을 반환하여 바로 로그인 처리
*
* 2. 로그인
* - 이메일과 pw로 사용자 검증
* - 검증이 완료되면 토큰 반환
*
* 3. 토큰 반환 (LoginUser)
* - 토큰을 반환하는 로직
*
* 4. 토큰 생성
* - 3에서 필요한 access, refreshToken을 sign하는 로직
*
* 5. 로그인 검증 과정 (authenticateWithEmailAndPassword)
* - 사용자가 존재하는지 확인
* - 비밀번호 일치여부 확인
* - 통과된 경우 사용자 정보 반환 + 토큰
*/
/**
* Payload에 들어갈 정보
* 1. 이메일
* 2. sub => id
* 3. type (access or refresh)
* signToken => 토큰을 발급하는 함수.
*/
// Pick 유틸리티를 사용하기 위해선 UsersModel을 가져와야하는데.
// 이는 해당 모듈을 export하는 코드를 작성해야함. (usersModule 에서 exports)
/*
@Module({
imports: [
TypeOrmModule.forFeature([UsersModel]),
],
// export 해야 다른 모듈에서 사용 가능
exports: [
UsersService,
],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
*/
signToken(user: Pick<UsersModel, 'email' | 'id'>, isRefreshToken: boolean) {
const payload = {
email: user.email,
id: user.id,
type: isRefreshToken ? 'refresh' : 'access',
};
return this.jwtService.sign(payload, {
secret: JWT_SECRET,
expiresIn: isRefreshToken ? 3600 : 300,
});
}
// 로그인시 토큰을 발급하는 함수
loginUser(user: Pick<UsersModel, 'email' | 'id'>) {
return {
accessToken: this.signToken(user, false),
refreshToken: this.signToken(user, true)
}
}
// 사용자 검증 과정
async authenticateWithEmailAndPassword(user: Pick<UsersModel, 'email' | 'password'>) {
// 1. 사용자 정보 확인
const existingUser = await this.usersService.getUserByEmail(user.email);
if (!existingUser) {
throw new UnauthorizedException('존재하지 않는 사용자입니다.');
}
// 2. 비밀번호의 비교
// 앞엔 일반 비밀번호 뒤엔 hash값
const checkPass = await bcrypt.compare(user.password, existingUser.password);
if (!checkPass) {
throw new UnauthorizedException('비밀번호가 틀렸습니다.')
}
return existingUser;
}
// 로그인시 토큰 발급 함수와 사용자 검증 과정을 둘다 활용한 함수로 로그인 기능
async loginWithEmail(user: Pick<UsersModel, 'email' | 'password'>) {
const existingUser = await this.authenticateWithEmailAndPassword(user);
return this.loginUser(existingUser);
}
// 회원가입
async registerWithEmail(user: Pick<UsersModel, 'email' | 'password' | 'nickname'>) {
// bcrypt의 경우 해시화 하고 싶은 패스워드 , round를 변수로 적는다.
// rounds는 hash에 소요되는 시간을 의미한다.
const hash = await bcrypt.hash(user.password, HASH_ROUNDS);
const newUser = await this.usersService.createUser({
...user,
password: hash,
});
return this.loginUser(newUser);
}
}
auth.controller.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Controller('summary')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login/email')
loginEmail(
@Body('email') email: string,
@Body('password') password: string,
) {
return this.authService.loginWithEmail({email, password});
}
@Post('register/email')
registerEmail(
@Body('email') email: string,
@Body('password') password: string,
@Body('nickname') nickname: string,
) {
return this.authService.registerWithEmail({email, password, nickname})
}
}
This post is licensed under CC BY 4.0 by the author.