[NestJS] Session 기반 Passport 로그인 (Naver,Local)

홍성웅·2024년 4월 18일
0

시작하기 앞서

JWT의 대한 정보는 엄청많은데 왜 sessison에 대한 정보는 이리 적은 것입니까?

그래서 열심히 구글링하고 문서를 파면서 방법을 찾아냈습니다 :)

session 이란?

Client로부터 오는 일련의 요청을 하나의 상태로 보고 그 상태를 일정하게 유지하는 기술

클라이언트가 웹 서버에 접속해있는 상태가 하나의 단위

세션은 웹서버에 웹 컨테이너의 상태를 유지하기 위한 정보를 저장합니다. 브라우저를 닫거나 서버에서 세션을 삭제하면 세션이 삭제됩니다. 세션은 각 클라이언트의 고유세션 ID를 부여하는데, 이것으로 클라이언트를 구분하여 각 클라이언트의 요구에 맞는 응답을 반환합니다.

Code

main.ts

main.ts에는 express에서 session을 설정을 해주는 거와 같이 같은방식으로 선언해주면 된다.

/main.ts

... 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const sessionMiddleware = session({
    secret: process.env.SECRET, // 세션을 암호화하기 위한 암호기 설정
    resave: false, // 모든 request마다 기존에 있던 session에 아무런 변경 사항이 없을 시에도 그 session을 다시 저장하는 옵션
    // saveUnitialized: 초기화되지 않은 세션을 저장할지 여부를 나타낸다.
    saveUninitialized: false,
    // 세션 쿠키에 대한 설정을 나타낸다.
    cookie: {
      maxAge: 60000 * 60, // 1 hour
      httpOnly: true,
    },
  });
  app.use(sessionMiddleware);
  
  // Passport를 초기화하는 미들웨어, 이를 통해 Passport의 인증/인가를 사용할 수 있다.
  app.use(passport.initialize());
  // Passport 세션을 사용하기 위한 미들웨어이다. 이를 통해 Passport는 세션을 기반으로 사용자의 인증 상태를 유지 관리 할 수 있다.
  app.use(passport.session());
  await app.listen(3000);
}
bootstrap();

passport-naver

strategy

startegy를 통해 로그인에서 로그인한 정보를 validate를 통해 naver 회원 정보를 넘긴다.

/src/auth/naver-strategy.ts

import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-naver';
import { Injectable } from '@nestjs/common';
import { AuthService } from 'src/auth/auth.service';

@Injectable()
export class NaverStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      clientID: process.env.NAVER_CLIENT_ID,
      clientSecret: process.env.NAVER_CLIENT_SECRET,
      callbackURL: process.env.NAVER_CALLBACK_URL,
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
    done: any,
  ): Promise<any> {
    const id = profile._json.id;
    const provider = 'naver';
    const name = profile._json.nickname;
    const image = profile._json.profile_image;
    const user = {
      id,
      name,
      image,
      provider,
    };
    return done(null, user);
  }
}

guard

controller에 있는 NaverAuthGuard를 넘어가고 네이버 로그인이 성공하면 naver/callback 으로 넘어간다.
/src/auth/auth.controller.ts

export class AuthController {
  constructor(private authService: AuthService) {}

  @Get('naver')
  @UseGuards(NaverAuthGuard)
  async naverLogin(): Promise<void> {}

  @Get('naver/callback')
  @UseGuards(NaverAuthGuard)
  async naverLoginCallback(@Req() req, @Res() res): Promise<void> {
    const user = req.user;
    await this.authService.OAuthLogin(user);
    return res.redirect('/list.html');
  }

jwt토큰 방식에서는 단순하게 코드를 작성할 수 있지만
controller에 접근하였을 때, canActivate에서 request정보를가져와 super.logIn을 통해 로그인을 연결해준다.

/src/auth/naver-auth.guard

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class NaverAuthGuard extends AuthGuard('naver') {
  constructor() {
    super();
  }
  async canActivate(context: ExecutionContext) {
    const activate = (await super.canActivate(context)) as boolean;
    const request = context.switchToHttp().getRequest();
    await super.logIn(request);
    return activate;
  }
}

serialize

serialize를 거치고
deserialize를 통해 req.user을 불러올 수 있게된다.

/serializeUser

import { PassportSerializer } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { User } from './user/entities/user.entity';
import { UserService } from './user/user.service';

@Injectable()
export class SessionSerializer extends PassportSerializer {
  constructor(private readonly userService: UserService) {
    super();
  }

  async serializeUser(
    user: User,
    done: (err: any, user?: any) => void,
  ): Promise<any> {
    //console.log(user, 'serializeUser'); // 테스트 시 확인
    done(null, user);
  }

  async deserializeUser(
    payload: any,
    done: (err: any, user?: any) => void,
  ): Promise<any> {
    const response = await this.userService.findById(payload.id);
    //    console.log(user, 'deserializeUser'); // 테스트 시 확인
    const user = JSON.parse(JSON.stringify(response));
    delete user.password;
    return user ? done(null, user) : done(null, null);
  }
}

번외 local

local strategy

local의 경우 usernamefield와 passwordField를 꼭 선언해줘야하니 잊지 않도록하자 저기서 말하는 field가 client에서 form tag의 name속성을 말한다.

validate에서는 db에 저장되있는지 확인하기위한 직접만든 함수이다.

import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      usernameField: 'email',
      passwordField: 'password',
    });
  }

  async validate(email: string, password: string): Promise<any> {
    const user = {
      id: email,
      password: password,
      provider: 'local',
    };
    return await this.authService.LocalLogin(user);
  }
}

local guard

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalGuard extends AuthGuard('local') {
  constructor() {
    super();
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const result: boolean = (await super.canActivate(context)) as boolean;
    await super.logIn(context.switchToHttp().getRequest());
    return result;
  }
}
profile
Backend Developer

0개의 댓글