먼저, Google Oauth를 사용하기 위한 Package를 설치한다.
yarn add @nestjs/passport passport passport-google-oauth20
yarn add -D @types/passport @types/passport-google-oauth20
여기서, 다른 구글 Oauth 관련 라이브러리들이 존재하는데,
왜 passport-google-oauth20
인가?
일단, 평균 다운로드 수가 19만회로 다른 라이브러리보다 3배 이상 높다.
그리고, Github star 갯수를 살펴봤을 때, 가장 높은 passport-google-oauth
라이브러리가 있었는데, 해당 라이브러니는 passport-google-oauth20
의 메타 모듈이라고 되어있다.
Github star 갯수도 높고, 평균 다운로드가 높은 passport-google-oauth20
을 선택하게 되었다.
이제, Passport를 사용해 google 로그인을 적용하기 위해 Strategy를 작성해보자.
먼저, PassportStrategy를 상속받는다.
상속 받을 때, PassportStrategy에 파라미터로 Strategy와 string을 전달한다.
Strategy는 해당 Strategy가 사용할 Strategy를 전달한다. passport-google-oauth20
에 정의된 Strategy를 전달한다.
그리고 string으로는 google을 전달하는데, 이는 나중에 Guard를 사용할 때, 해당 명칭으로 사용하게 된다.
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {}
그리고, 생성자를 통해 Google Oauth를 구현하기 위한 옵션을 전달한다.
super()를 통해 부모 클래스의 생성자를 사용하는데, 상속 관계를 찾아보면 옵션은 passport-google-oauth20
에서 가져온 Strategy로 전달된다.
{
authorizationURL?: string | undefined;
callbackURL?: string | undefined;
clientID: string;
clientSecret: string;
scope?: string | string[] | undefined;
tokenURL?: string | undefined;
userProfileURL?: string | undefined;
passReqToCallback?: false | undefined;
}
옵션이 정의된 값을 보면, clientID, clientSecret을 필수로 전달해야 한다. 해당 값은 GCP에서 Google Oauth를 설정할 때 가져올 수 있다.
여기에 callbackURL로 Google 로그인 후 리다이렉션 될 URL을 전달하고, scope를 통해 가져올 정보를 정의한다.
constructor() {
super({
clientID: process.env.OAUTH_GOOGLE_ID,
clientSecret: process.env.OAUTH_GOOGLE_SECRET,
callbackURL: process.env.OAUTH_GOOGLE_REDIRECT,
scope: ['email', 'profile'],
});
}
현재 설정 값을 .env
에 저장해서 사용했다.
@nestjs/config 의 ConfigModule을 사용하면, ConfigService를 사용할 수 있음.)
scope는 email, profile을 전달해 email과 profile을 받아온다.
이제 validate 메서드를 정의해야 한다.
validate 메서드는 Strategy를 통해 받아온 구글 유저 정보를 해당 Strategy를 사용한 API Controller에 req.user의 형태로 전달할 수 있도록 한다.
validate(
accessToken: string,
refreshToken: string,
profile: Profile,
){
const { id, name, emails } = profile;
return {
provider: 'google',
providerId: id,
name: name.givenName,
email: emails[0].value,
};
}
Profile
타입은 passport-google-oauth20
에 정의되어있다.
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, Profile } from 'passport-google-oauth20';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.OAUTH_GOOGLE_ID,
clientSecret: process.env.OAUTH_GOOGLE_SECRET,
callbackURL: process.env.OAUTH_GOOGLE_REDIRECT,
scope: ['email', 'profile'],
});
}
validate(
accessToken: string,
refreshToken: string,
profile: Profile,
){
const { id, name, emails } = profile;
return {
provider: 'google',
providerId: id,
name: name.givenName,
email: emails[0].value,
};
}
}
이제, Google 로그인과 관련된 Controller를 작성한다.
먼저, 2가지 API가 필요하다.
import { Get, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport';
@Controller('auth')
export class AuthController {
@Get('google')
@UseGuards(AuthGuard('google'))
async googleAuth(): Promise<void> {
// redirect google login page
}
}
리다이렉션 Controller를 간단하게 요청 URL에 맞춰 UseGuards
에 AuthGuard(’google’)
을 전달한다. AuthGuard에 google을 전달하면, Strategy를 작성할 때 작성한 명칭을 찾아 적용한다.
import { Get, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport';
@Controller('auth')
export class AuthController {
// ...
@Get('google/callback')
@UseGuards(AuthGuard('google'))
async googleAuthCallback(
@Req() req: Request,
@Res() res: Response,
): Promise<void> {
// ...
const { user } = req;
}
}
Strategy에 전달된 callbackURL 값대로 Controller를 작성한다.
해당 Controller에서는 req.user를 통해 Strategy의 validate에서 리턴한 값을 사용할 수 있게 된다.
import { Get, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport';
@Controller('auth')
export class AuthController {
@Get('google')
@UseGuards(AuthGuard('google'))
async googleAuth(): Promise<void> {
// redirect google login page
}
@Get('google/callback')
@UseGuards(AuthGuard('google'))
async googleAuthCallback(
@Req() req: Request,
@Res() res: Response,
): Promise<void> {
// ...
const { user } = req;
}
}
이제 작성된 Strategy와 Controller를 Module로 등록한다.
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { GoogleStrategy } from './strategies';
@Module({
controllers: [AuthController],
providers: [GoogleStrategy],
})
export class AuthModule {}
이제 구글 로그인을 통해 구글에서 유저 정보를 가져오게 되었다.
callback URL로 요청되는 Controller에서 가져온 정보를 저장하고, 정보를 통해 JWT을 발급해 유저 기능을 인증을 구현하면 된다.
이어서 Nest.js에서 JWT을 통해 유저 인증을 구현해보자.