Search

Passport Strategy

트러블 슈팅

처음에는 아래와 같이 Passport Strategy를 사용하여 redirect를 하고 난 후
code를 받아와 https://kauth.kakao.com/oauth/token 로 요청을 해 accessToken을 받아오려 시도했었다.
하지만 401, 400 에러가 발생하였고 원인을 찾아보았다.
@Get('/kakao/redirect') @UseGuards(AuthGuard('kakao')) async kakaoAuthCallBack(@Query('code') code: string): Promise<any> { // code 받아오는것까지 성공 console.log('code : ' + code); const accessToken = await this.authService.getKakaoAccessToken(code); console.log('accessToken : ' + accessToken); return null; } async getKakaoAccessToken(code: string) { console.log('getKakaoAccessToken 진입'); console.log('KAKAO_CLIENT_SECRET:', process.env.KAKAO_CLIENT_ID); const KAKAO_TOKEN_URL = 'https://kauth.kakao.com/oauth/token'; const params = new URLSearchParams({ grant_type: 'authorization_code', client_id: process.env.KAKAO_CLIENT_ID, redirect_uri: process.env.KAKAO_REDIRECT_URL, code: code, client_secret: process.env.KAKAO_CLIENT_SECRET, }); try { const response = await firstValueFrom( this.httpService.post(KAKAO_TOKEN_URL, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', }, }), ); return response.data.access_token; // 액세스 토큰 반환 } catch (e) { console.log(JSON.stringify(e)); } }
TypeScript
복사

Passport Strategy 방식의 로그인 플로우

1.
사용자가 /auth/kakao 엔드포인트로 요청을 보낸다.
@Get('/kakao') @UseGuards(AuthGuard('kakao')) // 🔥 Passport가 자동으로 Kakao 로그인 페이지로 리디렉트 async kakaoAuth() { // 이 메서드는 직접 실행되지 않음 (자동 리디렉션) }
TypeScript
복사
AuthGuard(’kakao’)가 실행되며, Kakao 로그인 페이지로 리다이렉트 된다.
사용자가 Kakao로그인 후, callBackURL로 자동 이동됨.
2.
Kakao 로그인 성공 후, callBackURL로 Authorization Code를 반환한다.
사용자가 Kakao 로그인을 마치면, Kakao가 인가 코드 (Authorization Code) 를 서버로 보낸다.
(kakao에서 리다이렉트 할 때 code가 포함되어 있음.)
GET /auth/kakao/redirect?code=123456
TypeScript
복사
3.
Passport Kakao Strategy가 자동으로 AccessToken을 요청한다.
Passport가 자동으로 code를 Kakao API에 전달하여 AccessToken을 요청한다.
super() 내부에서 Kakao OAuth 서버에 요청이 발생한다.
@Injectable() export class KakaoStrategy extends PassportStrategy(Strategy, 'kakao') { constructor() { super({ clientID: process.env.KAKAO_CLIENT_ID, clientSecret: process.env.KAKAO_CLIENT_SECRET, callbackURL: process.env.KAKAO_REDIRECT_URL, // 🔥 Kakao가 리디렉트할 URL }); } }
TypeScript
복사
4.
Passport 클래스의 validate() 실행하여 유저 정보와 AccessToken을 반환한다.
super() 에서 요청한 Kakao API에서 응답이 오면 Passport가 자동으로 validate() 메서드를 실행한다.
async validate(accessToken: string, refreshToken: string, profile: any) { console.log('Kakao Access Token:', accessToken); // 🔥 여기서 Access Token 확인 가능 return { provider: 'kakao', providerId: profile.id, email: profile._json.kakao_account.email, // 카카오 이메일 nickname: profile.displayName, // 카카오 닉네임 profileImage: profile._json.properties.profile_image, // 카카오 프로필 이미지 accessToken, // 🔥 Access Token을 포함해서 반환 }; }
TypeScript
복사
Kakao에서 받은 accessToken유저 정보를 req.user에 저장
5.
컨트롤러에서 req.user를 받아서 응답
validate()가 반환한 객체는 req.user에 저장되며 컨트롤러에서 확인이 가능하다.
@Get('/kakao/redirect') @UseGuards(AuthGuard('kakao')) async kakaoAuthCallBack(@Req() req) { console.log('User:', req.user); // 🔥 여기서 Access Token과 유저 정보를 확인 가능 console.log('Access Token:', req.user.accessToken); return { accessToken: req.user.accessToken }; // 클라이언트에 accessToken 반환 가능 }
TypeScript
복사
클라이언트에서 이 AccessToken을 저장 후 API 요청에 활용하면 된다.

원인분석

결국 Passport super() 내부에서 code를 사용하여 kakao API를 요청하여 유저정보와 토큰정보를 받아오는데 여기서 한번 더 getKakaoAccessToken() 를 호출하였기 때문에 이미 만료된 code를 사용하여 에러가 발생했던 것.
code는 보안을 위해 생명주기가 짧고 한번 사용하면 만료되도록 설계되어 있다.
async getKakaoAccessToken(code: string) { console.log('getKakaoAccessToken 진입'); console.log('KAKAO_CLIENT_SECRET:', process.env.KAKAO_CLIENT_ID); const KAKAO_TOKEN_URL = 'https://kauth.kakao.com/oauth/token'; const params = new URLSearchParams({ grant_type: 'authorization_code', client_id: process.env.KAKAO_CLIENT_ID, redirect_uri: process.env.KAKAO_REDIRECT_URL, code: code, client_secret: process.env.KAKAO_CLIENT_SECRET, }); try { const response = await firstValueFrom( this.httpService.post(KAKAO_TOKEN_URL, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', }, }), ); return response.data.access_token; // 액세스 토큰 반환 } catch (e) { console.log(JSON.stringify(e)); } }
TypeScript
복사