Kakao 로그인 / 로그아웃

정윤수·2022년 2월 19일
0

NestJS Kakao Login / Logout

Kakao Developers 설정

카카포 디벨로퍼 접속

  • 내 어플리케이션 추가하기

11.png

세부 설정

키 설정

  • REST API를 사용할 거기 때문에, REST API키를 사용한다.

ㅎㅎㅎㅎ.jpg

Redirect URI 설정

ㅎ.png

scope 설정

ㅎㅎ.png

호출 IP주소 설정

ㅎㅎㅎ.jpg

로그아웃 Redirect URI 설정

로그아웃.png


전체적인 흐름도

123.png

  1. 사용자(클라이언트)가 Nest 서버에 카카오 로그인 요청을 보낸다.
  2. 나는 사용자를 카카오 API로 보낸다.
  3. 사용자가 동의 누르면, 카카오 API는 사용자에게 code를 반환해주며, 다시 서버에게(Redirect URL을 통해) 보낸다.
  4. 로그인이 완료 된 후, Redirect URL주소의 쿼리스트링으로 사용자의 로그인 인증 코드가 들어온다. 서버에서는 그 코드를 통해 다시 카카오 API에 요청(GET)을 보내어, 토큰을 발급받는다.
  5. 발급받은 토큰을 통해 사용자의 정보를 가져온다.
  6. 토큰을 따로 저장하여, 로그아웃을 구현한다.
  • 1~5: 로그인
  • 6: 로그아웃

로그인

클라이언트(유저)의 카카오 로그인 요청

ㅣ.png

  • ‘유저의 카카오 api 로그인 요청’을 처리하기 위한 api를 따로 만들어준다.
  • /auth/kakaoLoginLogic으로 이동시켜, 이곳에서 카카오 로그인을 처리한다.
@Controller('auth')
export class AuthController {
  constructor(private readonly kakaoService: KakaoService) {}

  @Get("kakaoLogin")
  @Header('Content-Type', 'text/html')
  getKakaoLoginPage():string {
    return `
      <div>
        <h1>카카오 로그인</h1>

        <form action="/auth/kakaoLoginLogic" method="GET">
          <input type="submit" value="카카오 로그인" />
        </form>

        <form action="/auth/kakaoLogout" method="GET">
          <input type="submit" value="카카오 로그아웃" />
        </form>
    `;
  }
}

카카오 API로 보내는 과정

ㅣㅣ.png

  • 특정 정보가 포함된 url 로 유저를 redirect 시켜, 유저가 위의 화면을 볼 수 있도록 한다.
  • 특정 정보: 요청을 보낼 곳(_hostName), REST API KEY, redirect_key
@Get('/kakaoLoginLogic')
  @Header('Content-Type', 'text/html')
  kakaoLoginLogic(@Res() res): void {
    const _hostName = 'https://kauth.kakao.com';
    const _restApiKey = '450af5f4744fcb0bc3d057ba542c7d6a';
    // 카카오 로그인 redirectURI 등록
    const _redirectUrl = 'http://127.0.0.1:3000/auth/kakaoLoginLogicRedirect';
    const url = `${_hostName}/oauth/authorize?client_id=${_restApiKey}&redirect_uri=${_redirectUrl}&response_type=code`;
    return res.redirect(url);
  }

사용자의 동의, code 받아오는 과정

  • 카카오 api쪽으로 간 유저가 로그인 과정을 다 거치고, 난 를 처리하는 곳이다.
  • 카카오 api쪽에서는 로그인 완료한 유저는 특정 url로 이동하게 되는데 그 url에는 code가 포함되어 있다. 이 code를 통해 토큰을 발급 받을 수 있다.
  • 그 토큰을 받아오기 위해서 서버에서 처리해준다. (this.kakaoService)
//server/src/auth/auth.controller.ts

@Get('/kakaoLoginLogicRedirect')
  @Header('Content-Type', 'text/html')
  kakaoLoginLogicRedirect(@Query() qs, @Res() res):void {
    console.log(qs.code);  //Query()에서 code를 가져온다

코드를 통해 다시 카카오 API로 보내, 토큰 받아오는 과정(POST)

  • 사용자와 카카오의 로그인이 완료되었을 때, 서버에서 쿼리 스트링으로 넘어오는 인증 코드를 통해 서버에서 카카오로 POST요청을 보내 토큰을 발급 받는다.
  • 토큰 성공적으로 발급 받으면, 로그인 완료 된 것이므로 로그인 완료된 화면을 보여준다.
  • 로그인 완료 후, 로그아웃을 시키기 위해 토큰이 필요하므로 Provider에 저장해둔다. (.setToken)
//server/src/auth/auth.controller.ts
//토큰을 발급받는 과정
  @Get('/kakaoLoginLogicRedirect')
  @Header('Content-Type', 'text/html')
  kakaoLoginLogicRedirect(@Query() qs, @Res() res):void {
    console.log(qs.code);  

    const _restApiKey = '450af5f4744fcb0bc3d057ba542c7d6a';
    const _redirect_uri = 'http://127.0.0.1:3000/auth/kakaoLoginLogicRedirect';
		//code, restApiKey, _redirect_uri를 다시 카카오 api에게 POST요청을 보내준다 
    const _hostName = `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=${_restApiKey}&redirect_uri=${_redirect_uri}&code=${qs.code}`;
		const _headers = {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
      },
    };
		//POST 요청을 위한 kakaoService
    this.kakaoService
      .login(_hostName, _headers)
      .then((e) => {
        // console.log(e);
        console.log(`TOKEN : ${e.data['access_token']}`);
        this.kakaoService.setToken(e.data['access_token']);
        return res.send(`
          <div>
            <h2>축하합니다!</h2>
            <p>카카오 로그인 성공하였습니다!</p>
            <a href="/auth/kakaoLogin">메인으로</a>
          </div>
        `);
      })
      .catch((err)=> {
        console.log(err);
        return res.send('error');
      });

유저의 로그인 과정 담당 Provider

  • 로그인 확인, POST요청, 토큰 지정을 담당한다.
  • 카카오 로그인 관련한 class를 만들어, 코드의 가독성을 높였다.
//server/src/auth/kakao.service.ts

import { Injectable } from "@nestjs/common";
import { HttpService } from "@nestjs/axios";
import { lastValueFrom } from "rxjs";

@Injectable()
export class KakaoService {
    check: boolean;
    accessToken: string;
    private http: HttpService;
    constructor() {
        this.check = false;
        this.http = new HttpService();
        this.accessToken = '';
    }
    loginCheck(): void {
        this.check = !this.check;
        return ;
    }
    async login(url: string, headers: any): Promise<any> {
        return await lastValueFrom(
            this.http.post(url, '', { headers })
        );
    }
    setToken(token: string): boolean {
        this.accessToken = token;
        return true;
    }
    
}

zzz.jpg

  • 앞서, POST요청을 통해 카카오 api에서 json 형태로 data를 보내준다.
  • data에는 위와 같은 정보들이 담겨있는데, 내가 필요한 건 access_token이므로 해당 정보만 가져와서 setToken을 통해, Token으로 지정한다.

POST요청을 할 때, 발생한 에러

*기존에 사용했었던 toPromise() method가 deprectaed 되었다는 것이다.

  • rxjs 공식 홈페이지를 확인해보니, RxJS 7부터는 toPromise()의 보안 이슈 때문에 삭제되고 더 나은 기술인 lastValueFrom으로 바뀌었다는 것이다.
  • 이런 이슈는 처음이라, 솔직히 당황했지만 차근차근 읽어나가며 해결해나갔다.

화면캡처.png

해결 코드

import { lastValueFrom } from "rxjs";

...
async login(url: string, headers: any): Promise<any> {
        return await lastValueFrom(
            this.http.post(url, '', { headers })
        );
    }
...

결과

ㅣㅣㅣ.png


유저의 정보 가져오기

  • 토큰을 발급 받았으니, 이를 통해 유저의 정보를 얻을 수 있다.
  • 이 토큰을 다시 카카오 api로 GET요청을 보냄으로써 얻는다.
  • GET요청으로 보낼 때, 필요한 정보는 kakao developers의 아래 내용을 참고한다.

ㄴㅇㅂ.png

//server/src/auth/auth.controller.ts

@Get('/kakaoLoginLogicRedirect')
  @Header('Content-Type', 'text/html')
  kakaoLoginLogicRedirect(@Query() qs, @Res() res):void {
		...
    this.kakaoService
      .login(_hostName, _headers)
      .then((e) => {
        // console.log(e);
        console.log(`TOKEN : ${e.data['access_token']}`);
        this.kakaoService.setToken(e.data['access_token']);
        this.kakaoUserInfo(e.data);
       ...
}

@Get('/kakaoLogin')
@Header('Content-Type', 'text/html')
  async kakaoUserInfo(@Res() res) {
		//GET요청을 보내기 위해 필요한 정보들
    const _url = "https://kapi.kakao.com/v2/user/me";
    // console.log(res);
    const _headers = {
      headers: {
        'Authorization': `Bearer ${res.access_token}`,
      },
    }
    // console.log(`토큰: ${res.access_token}`)
    this.kakaoService
      .showUserInfo(_url, _headers.headers)
      .then((e)=> {
        console.log(e);
      })
      .catch((err)=> {
        console.log(err);
        return res.send('error');
      })

GET 요청

//server/src/auth/kakao.service.ts

...
async showUserInfo(url: string, headers: any): Promise<any> {
        // console.log(`헤더: ${JSON.stringify(headers.headers)}`)
        return await lastValueFrom(
            this.http.get(url, { headers })
        );
    }
...

출력 데이터 형태

화면캡처 3.png

성공적으로, user의 data를 얻어 올 수 있다.


로그아웃

  • 로그아웃 하기 전, kakao Developer 에서 로그아웃 시 Redirect URL를 설정해둬야 한다.

  • 앞서 access_token을 setToken() 메서드를 통해class에 저장해두었기 때문에, 이를 활용한다.

  • 로그아웃의 종류에는 토큰 만료와 로그 삭제가 있다.

      -  토큰 만료: 일반적으로 생각하는 로그아웃
    
      -  로그 삭제: *탈퇴 or 다른 카카오 아이디로 로그인을 유도하는 경우의 로그아웃*

토큰 만료

//server/src/auth/auth.controller.ts

// 로그아웃 (일반적인 로그아웃, 토큰 만료)
    @Get('/kakaoLogout')
    @Header('Content-Type', 'text/html')
    kakaoLogout(@Res() res): void {
      
      this.kakaoService
        .logout()
        .then((e)=>{
          console.log(e);
          return res.send(`
          <div>
            <h2>로그아웃 완료(토큰만료)</h2>
            <a href="/auth/kakaoLogin">메인 화면으로</a>
          </div>
        `);
        })
        .catch((err)=>{
          console.log(err);
          return res.send('logout error');
        })
    }
//server/src/auth/kakao.service.ts
async logout(): Promise<any> {
    const _url = 'https://kapi.kakao.com/v1/user/logout';
    const _headers = {
        Authorization: `Bearer ${this.accessToken}`,
    };
    console.log(this.accessToken);
    //console.log(JSON.stringify(_headers));
    return await lastValueFrom(
        this.http.post(_url, '', { headers: _headers })
    );
  }

결과

ㅣㅣㅣㅣㅣ.png


로그 삭제

//로그 아웃 (탈퇴 or 다른 카카오 아이디로 로그인을 유도하는 경우, 로그 삭제)
    @Get('/kakaoLogout')
    @Header('Content-Type', 'text/html')
    kakaoLogout(@Res() res): void {
      
      this.kakaoService
        .deleteLog()
        .then((e)=>{
          console.log(e);
          return res.send(`
          <div>
            <h2>로그아웃 완료(로그삭제)</h2>
            <a href="/auth/kakaoLogin">메인 화면으로</a>
          </div>
        `);
        })
        .catch((err)=>{
          console.log(err);
          return res.send('logout error');
        })
    }
//server/src/auth/kakao.service.ts
 async deleteLog(): Promise<any> {
      const _url = "https://kapi.kakao.com/v1/user/unlink";
      const _headers = {
        Authorization: `Bearer ${this.accessToken}`,
      }
      return await lastValueFrom(
          this.http.post(_url, '', { headers: _headers})
      )
  }

결과

ㅣㅣㅣㅣ.png


참고자료: https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

            [https://www.youtube.com/watch?v=p5LASg_uUb0&t=261s](https://www.youtube.com/watch?v=p5LASg_uUb0&t=261s)

https://devtalk.kakao.com/t/access-token-should-not-be-null-or-empty/110977

https://devtalk.kakao.com/t/oauth/49647

https://devtalk.kakao.com/t/rest-api-ip-mismatched/105585

profile
https://www.youtube.com/watch?v=whoDs0KRc7k&t=1s

0개의 댓글

관련 채용 정보