Javascript/NestJS

NestJS OAuth 인증 두번째

kyoulho 2023. 4. 23. 12:47

 

첫번째 시간에는 Google 클라이언트 ID를 발급받는 방법을 다뤄보았습니다. 이번 시간에는 Passport와 NestJS에 가드를 통해 인증을 진행하겠습니다.

 

패키지 설치

npm i --save @nestjs/passport passport passport-google-oauth20 @nestjs/jwt
			--save-dev @types/passport @types/passport-google-oauth20

각 패키지들에 대해서는 차차 설명하도록 하겠습니다.

 

Passport

Passport는 Node.js에서 사용되는 인증 미들웨어로, 다양한 인증 전략(strategy)을 지원하며, 사용자 정의 전략을 만들어서 사용할 수도 있습니다. @nestjs/passport를 사용하면 NestJS에서 passport를 쉽게 사용할 수 있습니다. 또한 OAuth 서비스 제공자들마다 passport 라이브러리가 존재합니다.

import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy } from 'passport-google-oauth20';
import { ConfigService } from '@nestjs/config';
import { Injectable } from '@nestjs/common';
import { UserDto } from '../dto/user.dto';
import { OAuthProvider } from '../entity/provider.enum';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor(private readonly configService: ConfigService) {
    super({
      clientID: configService.get('GOOGLE_CLIENT_ID'),
      clientSecret: configService.get('GOOGLE_CLIENT_SECRET'),
      callbackURL: configService.get('GOOGLE_CALLBACK_URL'),
      scope: ['email', 'profile'],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: Profile,
  ): Promise<UserDto> {
    return {
	  provider: profile.provider,
      providerId: profile.id,
      nickname: profile.displayName,
      email: profile.emails[0].value,
    };
  }
}

위 코드는 NestJS의 Passport와 passport-google-oauth20 패키지를 사용하여 구글 OAuth 인증을 위한 GoogleStarategy 클래스를 정의하는 코드입니다. PassportStrategy를 상속하고 첫번째 인자로 passport-google-oauth20의 Strategy 객체를, 두번째 인자로 ‘google’ 이라는 이름을 주어 이후에 AuthGuard(’google’) 를 통해 사용할 수 있습니다.

validate()는 Google OAuth 인증이 완료된 후 호출되며, 메서드의 반환값은 request에 user 라는 속성으로 추가됩니다.

 

유저 생일 정보 가져오기

유저의 생일 정보를 가져오기 위해서는 Google API 의 People API를 사용하여야 하며 사용자가 생일 정보를 공개하지 않았다면 api를 요청해도 생일 정보를 받을 수 없을 수도 있습니다.

People API 를 이용하고 싶으시다면 구글 클라우드 콘솔에서 People API 사용하기를 선택하고 googleapis 라이브러리를 설치합니다

import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { Strategy } from 'passport-google-oauth2';
import { google } from 'googleapis';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor() {
    super({
      clientID: GOOGLE_CLIENT_ID,
      clientSecret: GOOGLE_CLIENT_SECRET,
      callbackURL: '<http://localhost:3000/auth/google/callback>',
      scope: ['profile', 'email']
    });
  }

  async validate(accessToken, refreshToken, profile, done) {
    const { id, name, email } = profile;
    const oauth2Client = new google.auth.OAuth2();
    oauth2Client.setCredentials({ access_token: accessToken });
    const people = google.people({ version: 'v1', auth: oauth2Client });
    const { data } = await people.people.get({
      resourceName: 'people/me',
      personFields: 'birthdays'
    });
    const birthday = data.birthdays && data.birthdays[0] && data.birthdays[0].date;
    return { id, name, email, birthday };
  }
}

이런 식으로 사용할 수 있을 것 같네요.

하지만 저는 있을지 없을지도 모르는 데이터를 위해 API를 한번 더 콜하는 거는 낭비라고 생각하여 사용자에게 직접 입력받기로 하였습니다.

 

Provider 등록

@Module({
  controllers: [AuthController],
  providers: [GoogleStrategy],
})
export class AuthModule {}

GoogleStrategy 클래스를 AuthModule에 추가하여 줍니다.

 

Guard와 AuthGuard

 

Guard

NestJS에는 인증, 권한 부여 등과 같은 요청 전처리 로직을 구현하기 위하여 가드(Guard)라는 미들웨어가 있습니다. 가드는 CanActivate, CanActivateContext, CanActivateAsync 인터페이스를 구현하여 커스텀하게 사용할 수도 있습니다.

 

AuthGuard

AuthGuard는 @nestjs/passport 모듈에서 제공하는 가드(Guard) 중 하나로, Passport를 사용하여 인증된 사용자만 허용하는 NestJS용 인증 가드입니다. 로컬, JWT, OAuth 등 여러 인증 방식으로 사용할 수 있으며 구체적으로 어떤 인증 방식을 사용할지는 @nestjs/passport모듈에서 제공하는 AuthGuard 하위 클래스를 사용하여 설정합니다.

예를 들어, AuthGuard('local')를 사용하면 로컬 인증 방식을 사용하여 인증된 사용자만 허용하는 가드를 생성할 수 있습니다. 마찬가지로 AuthGuard('jwt')를 사용하면 JWT 인증 방식을 사용하여 인증된 사용자만 허용하는 가드를 생성할 수 있습니다.

 

AuthController

import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';

@Controller('/api/auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Get('/login/google')
  @UseGuards(AuthGuard('google'))
  async googleLogin(
    @Req() req,
  ): Promise<{ accessToken: string; refreshToken: string }> {
	   return await this.authService.loginOrSignIn(req.user);
  }
}

@UseGuards() 데코레이터는 메소드 위에 선언하며 해당 메소드 이전에 가드를 먼저 실행하여 요청에 대한 검증 또는 조작을 수행합니다. AuthGuard('google') 가 적용된 핸들러가 실행될 때, 해당 가드는 사용자 인증 여부를 확인하고 인증되지 않은 경우 로그인 페이지로 리다이렉트합니다.

리다이렉트를 하기 위해 내부적으로 passport.authenticate('google', options)를 호출합니다. passport.authenticate()는 Passport 라이브러리의 메소드로, 인증 전략(strategy)에 따라 사용자 인증을 처리하고, 인증 실패 시 인증 실패 이유를 리턴합니다. 이때, options 인자에는 scopestate 등의 옵션을 전달할 수 있습니다.

인증이 성공하게 되면 해당 엔드포인트로 다시 리다이렉트 되고 그때 유저 정보를 가지고 와서 데이터베이스에 조회 혹은 생성 후 accessToken과 refreshToken을 생성, refreshToken을 해쉬하여 데이터베이스에 저장 후 프론트엔드로 반환하도록 하였습니다.

'Javascript > NestJS' 카테고리의 다른 글

[NestJS] HTTP 기본 제공 예외 클래스  (0) 2024.09.07
NestJS JWT 발급과 인증  (0) 2023.04.23
NestJS OAuth 인증 첫번째  (1) 2023.04.23
NestJS cookie-parser import 에러  (0) 2023.03.29
NestJS Health Check API  (0) 2023.03.29