상똥이의 Back-End 공부방

[Nest.js] 카카오 로그인 API (회원가입, 로그인, 카카오 프로필 가져오기) 본문

Nest.JS

[Nest.js] 카카오 로그인 API (회원가입, 로그인, 카카오 프로필 가져오기)

상똥백 2024. 6. 23. 19:34

목차

0. 사전 준비 : 내 애플리케이션 등록
1. 카카오 로그인 로직 설명
2. 로직 구현 1 - 인가 코드 받아오기
3. 로직 구현 2 - 토큰 받아오기
4. 로직 구현 3 - 회원가입 및 프로필 생성
5. 전체 코드


[0. 사전 준비 : 내 애플리케이션 등록]

1. 실제 사업자 등록은 필요 없고 대충 작성만 하면 됨
2. https://developers.kakao.com/ 에 들어가서 로그인
3. 상단 목록 중 '내 애플리케이션'으로 이동
4. '내 애플리케이션 추가하기' 클릭
5. 앱 이름, 사업자 명, 카테고리 작성 및 동의사항 체크 후 저장 누르면 생성됨
6. 생성된 애플리케이션을 클릭하고, 제품 설정 > 카카오 로그인 으로 들어가 환경설정
- '활성화 설정'의 상태를 ON으로 바꾸기
- 'OpenId Connect 활성화 설정'의 상태를 ON으로 바꾸기
- 'Redirect URI' 등록하기 (카카오 로그인 후 랜딩 페이지로 이동할 수 있도록 함 (나는 'http://localhost:3000/kakao-auth/callback' 으로 입력했음)
- '동의항목'으로 이동하여 개인정보 중 필요한 항목(이 글에서는 닉네임)에 대한 상태를 '필수 동의'로 변경


[1. 카카오 로그인 로직 설명]

※ https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api 에 접속해서 API설명을 볼 수 있음
 
0. 로그인 요청
- 서비스 클라이언트에서 서비스 서버로 카카오 로그인 요청한다.
 
1. 인가 코드 받기
- 서비스 서버에서 카카오 인증 서버에 인가 코드를 요청한다 (Get 메서드)
- 카카오 인증 서버는 서비스 클라이언트에 카카오계정 로그인 요청 한다.
- 카카오 인증 서버는 서비스 클라이언트에 동의 화면을 출력하고,
- 서비스 클라이언트에서 동의하면 카카오 인증 서버에서 서비스 서버에 302 Redirect URI로 인가 코드를 전달한다


2. 토큰 받기
- 서비스 서버에서 POST 메서드로 카카오 인증 서버에 토큰을 요청한다. 경로는 /oauth/token
- 카카오 인증 서버에서 서비스 서버로 토큰을 발급한다.

 
3. 사용자 로그인 처리
- 서비스 서버에서 ID 토큰 유효성 검사를 진행한다.
- 서비스 서버에서 발급받은 사용자 토큰으로 사용자 정보를 조회한다.서비스 회원정보 확인 또는 회원가입 처리한다.
- 로그인 완료


[2. 로직 구현 1 - 인가 코드 받아오기]

0. 초기 설정

더보기

폴더 구조

database(prisma)

model User {
  id          String          @id
  userProfile UserProfile?
}

model UserProfile {
  id       String    @id
  user     User   @relation(fields: [id], references: [id])
  nickname String
}

 
1. .env파일 설정
- REST_API_KEY는 내 애플리케이션 > 앱 설정 > 요약 정보에서 확인 가능
- REDIRECT_URI는 로그인 완료 후 랜딩 페이지로 설정
- REST_API_KEY와 REDIRECT_URI는 .env파일에 담아 관리한다.

REST_API_KEY = 내_애플리케이션_요약정보에서_받아온_REST_API_키
REDIRECT_URI = https://localhost:3000/kakao-auth/callback

- 잘 설정되었는지 확인하기 위해, 아래 url에 본인의 REST_API_키와 REDIRECT_URI를 적절히 삽입해서 사이트로 이동해보자

https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}    

- 나는 잘 이동됨
 
2. 로직 구현
- kakao-auth.service.ts에 url을 반환하는 로직을 작성한다.
- 작성된 링크는 인가 코드를 받아오는 url+필수 파라미터를 조합한 것

// kakao-auth.service.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from 'src/database/prisma/prisma.service';

@Injectable()
export class KakaoAuthService {
  private readonly restApiKey: string;
  private readonly redirectUri: string;

  constructor(
    private readonly configService: ConfigService,
    private readonly prismaService: PrismaService,
  ) {}

  getCode() {
    return `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${this.restApiKey}&redirect_uri=${this.redirectUri}`;
  }
}

- kakao-auth.controller.ts를 통해 인가 코드를 받아온다.

// kakao-auth.controller.ts

import { Controller, Get, Res } from '@nestjs/common';
import { KakaoAuthService } from './kakao-auth.service';
import { Response } from 'express';

@Controller('kakao-auth')
export class KakaoAuthController {
  constructor(private readonly kakaoAuthService: KakaoAuthService) {}

  @Get()
  getKakakoCode(@Res() res: Response) {
    const url = this.kakaoAuthService.getCode();

    res.redirect(url);
  }

  @Get('callback')
  kakaoCallback() {
    return 'callback page';
  }
}

 
- kakao-auth 경로에 직접 접근하면 아래와 같이 로그인 화면이 뜨고

- 로그인하면 자동으로 callback 페이지로 이동하며 uri에 코드가 포함되어 있는 것을 확인할 수 있다.

- 카카오 API 설명 보기 (접은 글)

 


[3. 로직 구현2 - 토큰 받아오기]

1. 로직 설정
- 서비스 로직에서 url, param, header를 설정해서 조합한 다음, post메서드를 통해 엑세스 토큰을 가져와야 한다.
- url : 카카오 API에 나와있는대로 작성한다
- params : 카카오 API에 나와있는대로 작성한다. client_id는 REST_API 키와 같고 redirect uri는 REDIRECT_URI와 같다.
- header : 카카오 API에 나와있는대로 작성한다.
- response의 데이터 안에 들어있는 엑세스토큰을 반환한다.

// kakao-auth.service.ts

  async getKakaoAccessToken(code: string) {
    const url = 'https://kauth.kakao.com/oauth/token';
    const params = new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: this.restApiKey,
      redirect_id: this.redirectUri,
      code,
    });
    const header = {
      'Content-type': 'application/x-www-form-urlencoded;charset=utf-8',
    };

    const response = await axios.post(url, params, {
      headers: header,
    });

    return response.data.access_token;
  }

- 컨트롤러에서 엑세스 토큰을 반환한다.

// kakao-auth.controller.ts

  @Get('callback')
  async kakaoCallback(@Query('code') code: string, @Res() res: Response) {
    const accessToken = await this.kakaoAuthService.getKakaoAccessToken(code);

    return res.send({ accessToken }); //화면에 액세스토큰 출력
  }

- 화면에 엑세스토큰이 출력된다

- 카카오 API 설명 보기 (접은 글)


[4. 로직구현3 - 회원가입 및 프로필 생성]

1. 카카오 프로필을 가져온다
- controller의 kakaoCallback에 사용자 정보(userInfo)를 가져오는 로직을 추가한다
- 아이디: userInfo.data.id를 통해 호출 가능하다
- 닉네임: userInfo.data.properties.nickname (properties 안에 프로필사진과 썸네일이 들어있음)

// kakao-auth.controller.ts

  @Get('callback')
  async kakaoCallback(@Query('code') code: string, @Res() res: Response) {
    const accessToken = await this.kakaoAuthService.getKakaoAccessToken(code);
    
    const url = 'https://kapi.kakao.com/v2/user/me';
    const userInfo = await axios.get(url, {
      headers: {
        Authorization: `Bearer ${kakaoAccessToken}`,
      },
    });
    
    return res.send({ accessToken }); //화면에 액세스토큰 출력
  }

- 회원가입과 동시에 프로필을 생성하는 로직을 service에 작성한다

// kakao-auth.service.ts

  async createUser(kakaoId: number, nickname: string) {
    await this.prismaService.user.upsert({
      where: { id: kakaoId },
      update: {},
      create: {
        id: kakaoId,
        userProfile: {
          create: {
            nickname: nickname,
          },
        },
      },
      include: { userProfile: true },
    });
  }

- kakaoCallback에서 createUser를 호출하여 액세스토큰 발급과 동시에 회원가입이 가능해지도록 한다

// kakao-auth.controller.ts

  @Get('callback')
  async kakaoCallback(@Query('code') code: string, @Res() res: Response) {
    const accessToken = await this.kakaoAuthService.getKakaoAccessToken(code);

    const url = 'https://kapi.kakao.com/v2/user/me';

    const userInfo = await axios.get(url, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    const newUser = await this.kakaoAuthService.createUser(
      userInfo.data.id.toString(),
      userInfo.data.properties.nickname,
    );

    console.log(newUser);

    return res.send({ accessToken });
  }

- console 결과

- 이로써 카카오를 통한 회원가입, 프로필 호출까지 성공하였습니다


[5. 전체 코드]

1. kakao-auth.service.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import { PrismaService } from 'src/database/prisma/prisma.service';

@Injectable()
export class KakaoAuthService {
  private readonly restApiKey: string;
  private readonly redirectUri: string;

  constructor(
    private readonly configService: ConfigService,
    private readonly prismaService: PrismaService,
  ) {
    this.restApiKey = this.configService.getOrThrow('REST_API_KEY');
    this.redirectUri = this.configService.getOrThrow('REDIRECT_URI');
  }

  getCode() {
    return `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${this.restApiKey}&redirect_uri=${this.redirectUri}`;
  }

  async getKakaoAccessToken(code: string) {
    const url = 'https://kauth.kakao.com/oauth/token';
    const params = new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: this.restApiKey,
      redirect_id: this.redirectUri,
      code,
    });
    const header = {
      'Content-type': 'application/x-www-form-urlencoded;charset=utf-8',
    };

    const response = await axios.post(url, params, {
      headers: header,
    });

    return response.data.access_token;
  }

  async createUser(kakaoId: string, nickname: string) {
    return await this.prismaService.user.upsert({
      where: { id: kakaoId },
      update: {},
      create: {
        id: kakaoId,
        userProfile: {
          create: {
            nickname: nickname,
          },
        },
      },
      include: { userProfile: true },
    });
  }
}

 
2. kakao-auth.controller.ts

import { Controller, Get, Post, Query, Res } from '@nestjs/common';
import { KakaoAuthService } from './kakao-auth.service';
import { Response } from 'express';
import axios from 'axios';

@Controller('kakao-auth')
export class KakaoAuthController {
  constructor(private readonly kakaoAuthService: KakaoAuthService) {}

  @Get()
  getKakakoCode(@Res() res: Response) {
    const url = this.kakaoAuthService.getCode();

    res.redirect(url);
  }

  @Get('callback')
  async kakaoCallback(@Query('code') code: string, @Res() res: Response) {
    const accessToken = await this.kakaoAuthService.getKakaoAccessToken(code);

    const url = 'https://kapi.kakao.com/v2/user/me';

    const userInfo = await axios.get(url, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    const newUser = await this.kakaoAuthService.createUser(
      userInfo.data.id.toString(),
      userInfo.data.properties.nickname,
    );

    console.log(newUser);

    return res.send({ accessToken });
  }
}