XSS 공격에 안전한 소셜 로그인 구현 (카카오)

2021. 12. 12. 17:34프로그래밍/웹관련

반응형

XSS 공격에 안전한 소셜 로그인 구현 (카카오)

카카오 로그인 프로세스는 다음과 같이 진행했다.

  1. 클라이언트 서버 : 카카오 서버에 access_token을 요청하여 전달 받는다.
  2. 클라이언트 서버 : 백엔드 서버로 access_token을 넘겨주며 로그인 API를 호출한다.
  3. 백엔드 서버 : 클라이언트 서버에서 전달받은 access_token을 가지고 카카오 API를 호출하여 사용자 정보를 가져온다.
  4. 백엔드 서버 : JWT 방식을 사용하여 백엔드 서버에서 클라이언트 서버에 JWT token을 전달해준다. 이때 token을 Cookie에 담아서 전달한다.(httpOnly)

이번 포스팅은 백엔드 서버에 초점을 맞춰 3, 4번 내용을 작성하였다.

 

1. 카카오 서버에서 받은 access_token을 가지고 사용자 정보 가져오기

우선, HTTP Request 라이브러리를 알아보자.

  • HTTP (built-in)
  • request
  • axios

HTTP

Node.js에는 기본적으로 http 모듈이 장착되어 있다. 이 모듈을 사용하여 외부 패키지를 추가할 필요없이 http 요청을 할 수 있으나 모듈이 저수준이므로 개발자에게 친숙하지 않다. 또한 비동기/대기 기능을 함께 사용할 수 없다는 단점이 있다.

request

  • 2020년 2월 11일 이후로 deprecated 되었다.
  • (처음에는 request 라이브러리를 사용했으나 이후 axios로 변경하였다.)

axios

  • 대부분의 브라우저를 지원하며, JSON 데이터를 자동으로 변환해준다.
  • 가독성이 좋게 구성되어 있고 Promise 기반이다.
  • 많은 사람들이 사용한다. 대세를 따르기로 했다.

 

코드작성

AuthController.js

import axios from 'axios';

export async function kakaologin(req, res) {
  try {
    // kakao token
    const apiToken = req.headers['authorization'];

    // axios option
    const options = {
        method: 'get',
        url : 'https://kapi.kakao.com/v2/user/me',
        headers: {
          Authorization: `Bearer ${apiToken}`,
        },
        json: true,
      };

    // Kakao API 호출(사용자 정보 가져오기)
    const { data } = await axios(options);
    console.log(data);

    // ... 로그인 처리
    // ... JWT 토큰 생성
    // ... Cookie에 token 담기

    res.status(200).json({ message: 'LOGIN_SUCCESS' });   
  } catch (error) {

    // Kakao server에서 에러를 반환할 경우 isAxiosError의 값이 true
    if (axios.isAxiosError){
        return res.status(404).json({ message: 'USER_NOT_FOUND' });    
    }
    return res.status(404).json({ message: 'USER_NOT_FOUND' });
  }
}

 

2. 클라이언트 서버에 JWT token을 전달할 때, Cookie에 담아서 전달하기

JWT(Json Web Token) 🧐️?

HTTP는 상태를 보관하지 않는 Stateless Protocol이다. 그렇기 때문에 페이지가 이동할 때마다 유저가 매번 로그인하지 않도록 사용자가 로그인 한 상태인지, 사용자에 대한 정보를 어딘가에 담아둘 필요가 있다.

세션과 쿠키를 사용해서 유저에 대한 정보를 서버에 저장해두거나 JWT방식을 사용할 수 있다.

JWT(JSON Web Token)란 JSON파일 안에 필요한 데이터를 넣어서 Web Token으로 주고 받는 것으로 이런 절차의 목적은 해당 유저가 매번 로그인 하지 않도록 하는 것이다.

다음 코드는 JWT방식으로 token을 생성하고, 생성된 token을 Cookie에 담아 보안성을 높이도록 구현하였다.

 

XSS 공격에 안전한 Cookie 방식 (HttpOnly)

XSS Attack : Cross-Site Scripting Attack

  • 스크립트를 주입하는 방식으로 악의적인 코드들을 웹사이트에 몰래 심어놓고, 사용자가 로그인 했을 경우 발급받은 토큰을 로컬 스토리지에 저장해 놓으면 주입된 스크립트가 읽어서 해커에게 보내주는 방식.
  • 사용자가 신뢰하는 웹사이트에 악의있는 스크립트를 주입해서 사용자가 모르게 정보를 빼내는 것.
  • 해커가 Web application을 통해서 악의가 있는 코드를 보내게 되는데, 통상적으로는 input 관련된 form을 통해서 주입한다.

 

Cookie를 사용해서 보안성 높이기 🚀️

  • HttpOnly 옵션을 이용하면 브라우저만 cookie 정보를 읽을 수 있다.
  • 자바스크립트(코드)에서도 cookie의 정보를 읽을 수 없기 때문에 보안에 좋다.
  • 만료기간을 지정할 수 있으므로 유지되는 동안에는 웹사이트를 껐다켜도 cookie안의 토큰 정보가 유효하다.

단점

  • HttpOnly는 웹 클라이언트에 해당하므로, 모바일 클라이언트에서는 HttpOnly옵션을 사용할 수 없다.
  • CSRF 공격에 취약하다. 😂️

 

코드작성

import axios from 'axios';
import jwt from 'jsonwebtoken';

export async function kakaologin(req, res) {
  try {
    // kakao token
    const apiToken = req.headers['authorization'];

    // axios option
    const options = {
        method: 'get',
        url : 'https://kapi.kakao.com/v2/user/me',
        headers: {
          Authorization: `Bearer ${apiToken}`,
        },
        json: true,
      };

    // Kakao API 호출(사용자 정보 가져오기)
    const { data } = await axios(options);

    // ... 로그인 처리: DB에 등록되지 않은 유저라면 등록

    // JWT 토큰 생성
    const token = jwt.sign({ data.id }, config.jwt.secretKey, {
        expiresIn: config.jwt.expiresInSec,
      });

    if (token == null) {
      return res.status(404).json({ message: 'USER_NOT_FOUND' });
    }

    // Cookie에 token 담기
    const options = {
        maxAge: config.jwt.expiresInSec * 1000, // JWT token 만료 시간과 동일하게 cookie 파기
        httpOnly: true,
        sameSite: 'none', // client가 server와 서로 다른 ip라도 동작하도록 설정
        secure: true, // sameSite를 none으로 설정했을 경우 secure : true
      };
      res.cookie('token', token, options);

    res.status(200).json({ message: 'LOGIN_SUCCESS' });   
  } catch (error) {

    // Kakao server에서 에러를 반환할 경우 isAxiosError의 값이 true
    if (axios.isAxiosError){
        return res.status(404).json({ message: 'USER_NOT_FOUND' });    
    }
    return res.status(404).json({ message: 'USER_NOT_FOUND' });
  }
}

 

막혔던 부분 : Cookie 저장 안되는 문제

로컬에서 테스트할 때는 개발자도구/Application 탭/Cookies에서 token이 저장되는 걸 확인했는데 외부 IP로 프론트와 통신해서 테스트 했을 때는 token이 저장 안되는 문제가 발생했었다.

결론부터 말하자면...

cookie에 token을 담을 때 부여한 option 중에 secure 옵션과 관련이 있었다.

secure 속성이 true인 쿠키는 https 프로토콜을 사용할 때만 쿠키를 request header에 담겨서 서버에 보내지고 http 프로토콜일 때는 담겨지지 않기 때문이였다!

다만, localhost에서는 예외적으로 허용되기 때문에 로컬환경에서는 token이 저장됐었던 것이고 프론트 PC와 통신했을 때는 cookie에 token값이 저장되지 않았던 거다.

EC2서버는 https 프로토콜을 사용하기 때문에 테스트 해볼 수 있었는데 cookie에 토큰이 잘 저장되는 것을 확인할 수 있었다.

[MDN]Restrict access to cookies

You can ensure that cookies are sent securely and aren't accessed by unintended parties or scripts in one of two ways: with the Secure attribute and the HttpOnly attribute.

A cookie with the Secure attribute is only sent to the server with an encrypted request over the HTTPS protocol. It's never sent with unsecured HTTP (except on localhost), ...

 

반응형

'프로그래밍 > 웹관련' 카테고리의 다른 글

TIL52 | Unit Test  (0) 2021.11.20
TIL44 | Query Parameters VS Path Parameters  (0) 2021.11.10
TIL43 | REST API  (0) 2021.11.08
TIL40 | Westargram 마무리  (0) 2021.11.01
TIL37 | Westagram 회원가입 & 로그인 실습  (0) 2021.10.27