[AccessToken, RefreshToken] OAuth2.0 기반 로그인/인증 구현하기 (Node.js Vue.js) #1편

Seok·2021년 3월 17일
0

OAuth2.0 개발기록

목록 보기
5/5
post-thumbnail

지금까지 진행했었던 프로젝트에서 JWT 토큰을 발급하는 로직은 여러번 작성했었지만 그 토큰이 흐르는 전체의 로직은 구현해 본적이 없없다. 이번 기회에 로그인 로직전체를 구현해 보면서 전체의 흐름을 파악하는 기회를 가졌다.


OAuth2.0 이란

OAuth2.0은 서드파티 어플리케이션이 제한된 접근 권한을 얻기 위한 인증 프레임워크 이다. AccessTokenRefreshToken을 이용해 인증을 하는 역할을 한다. RFC 6749에 소개된 AccessTokenRefreshToken을 사용하는 방식을 채택해 적용했다.

AccessToken은 사용자의 아이디와 비밀번호가 맞는다면 그 사용자에게 접근 권한을 부여하는 역할을 가지고 RefreshTokenAccessToken의 짧은 유효시간을 보완하는 역할로써 사용했다.

AccessTokenRefreshToken을 활용한 인증 프로세스 및 는 아래와 같다.

인증

(A~B) 사용자가 아이디와 패스워드를 통해 인증을 요청한다. 올바른 정보라면 AccessTokenRefreshToken을 요청한다.

인가

(C~F) 사용자가 자원을 요청할 때 AccessToken을 같이 전송하게 하여 해당 자원에 대한 접근 권한이 있는지 확인하고, 있다면 자원을 제공하고, 그렇지 않다면 자원을 제공하지 않는다.

토큰 재발급

(G~H) AccessToken이 만료되었다면 사용자가 가지고있는 RefreshToken을 Authorization서버에 요청하여 올바른 RefreshToken을 가지고 있다면 새로운 AccessTokenRefreshToken을 반환한다.


개발 환경

  • Backend : Node.js(Express)
  • Frontend : Vue.js
  • Database : MongoDB
  • OS : macOS 11.2

개발 시작 전 고려 사항

인증서버와 자원 서버의 분리

모놀릭의 쓴맛을 봤었기 때문에 그 이후 MSA방식을 사용하려고 노력하고 있다. RFC 6749에서도 분리가 필수가 아니지만 인증서버와 자원서버를 분리하여 개발하기로 했다.

RFC 6749 내용
The authorization server may be the same server as the resource server or a separate entity. A single authorization server may issue access tokens accepted by multiple resource servers.

AccessTokenRefreshToken 저장/관리에 대한 고민

이 부분은 서칭을 많이 하게 했던 고민이다. 처음엔RefreshToken의 역할을 짧은 AccessToken을 보완하는 역할과 AccessToekn 탈취시 보안 문제를 보완하기 위함으로 사용하는 것으로 주로 적혀있었고, 그렇다고 생각했었다. 하지만

AccessToekn이 탈취가 가능하다면 RefreshToken도 똑같이 탈취가 가능하지 않는가❓ 그럼 똑같지 않은가❓

라는 생각으로 날 곤란하게 만들었다.😂
이는 각 토큰의 역할을 명확하게 정의하지 않아 생기는 혼돈이었고, 역할을 정확하게 정의하고 난다면 저장방식에 대한 고민을 해야하는 문제였다. 그래서 각 토큰의 역할 부터 다시 명확히 했다.

역할 재정의

AccessToekn은 사용자에게 접근 권한을 부여한다.

RefreshToken은 사용자가 가진 AccessToken의 짧은 유효 시간을 보완하는 역할로 사용한다.
사용자에게 권한을 부여하지 않는다‼️ 단지 AccessToken을 재발급하는 것 뿐이다‼️

이렇게 역할을 다시 정리하고 나니 위의 똑같이 탈취가능하다 라는 부분을 해결하기 위해 두 토큰의 저장 혹은 관리 방식을 다르게 하면 해결할 수 있겠다 라는 흐름까지 올 수 있었다.

토큰 관리 방식

AccessToken의 탈취 가능성을 보완하는 RefreshToken이라면 두 토큰은 같은 방식으로 관리하면 안된다. 토큰을 저장 하는 방식은 아래와 같은 방법이 있었다.

  • localstorage에 저장 하는 방법
  • 쿠키 사용
  • secure 쿠키 사용
  • httponly 쿠키 사용

쿠키에 refreshToken만 저장하고 새로운 accessToken을 받아와 인증에 이용하는 구조에서는 CSRF 취약점 공격을 방어할 수 있기 때문에 refreshToken은 쿠키로, accessToken은 localstorage에 저장하는 방식을 선택했다.
그 후 refreshToken을 어떤 쿠키로 저장할지 선택해야 했는데 secure 쿠키는 https 프로토콜이 필요했으나 아직 https를 적용하지 않았고, httpOnly쿠키는 자바스크립트로 접근이 불가능한 반면에 일반 쿠키는 접근이 가능했으므로 httpOnly 쿠키를 사용하기로 했다.

프론트에서 두개의 서버로 요청을?

인증서버와 자원서버가 분리됨에 따라서 프론트에서 요청을 두 곳으로 적절하게 보낼 수 있어야했다.
물론 각 요청마다 주소를 전부 적어주면 가능한 일이지만 그렇게 중복되고 보기 싫은 코드는 작성하고 싶지 않았다.
그래서 Axios instance를 사용해서 인증서버에 요청을 보내는 인스턴스, 자원서버에 요청을 보내는 인스턴스를 생성해서 사용하는 방법을 선택했다.

사용자가 토큰 만료를 알아채지 못하도록 하려면?

풀스택으로 개발을 진행해야 하다 보니 토큰을 단순 발급/관리 하는 고민 뿐만아니라 사용자의 경험까지도 고민했어야 했다. 그리고 고민과정 중에서 사용자가 웹서비스를 사용할 때에 토큰이 만료됨을 눈치 채지 못하고 사용이 가능했으면 좋겠다! 라는 생각이 있었다.

그에 따라 사용자가 서비스 사용시 토큰의 상태를 나열해 보았다.

  • AccessToken, RefreshToken 모두 문제 없는경우
  • AccessToken만 만료 된경우
  • Accesstoken은 없고, RefreshToken만 존재하는 경우
  • 둘다 없는 경우

이 부분은 프론트엔드에서의 모든 요청에 적용되는 공통되는 내용이므로 이전 고민에서 선택하기로한 Axios instance를 활용해서 처리할 수 있는 방법이 있는지 서칭했다. 이전 Spring boot 프로젝트를 진행할 때 interceptor를 활용한적이 있어서 axios도 interceptor가 있는지 봤고, 제공하고있었다!
request, response interceptor를 제공하고 있어서 요청 전, 요청 후 처리를 가능하게 했다.
따라서 axios interceptor를 이용해 각 경우에 대해 예외 처리도 가능하게 했다.

로그아웃시 두 토큰을 모두 사용할 수 없도록 하려면?

RefreshToken이 쿠키 형태로 발급됨에 따라서 브라우저에 저장이 될꺼고, 해당 토큰이 만료되지 않았다면 사용자는 사이트를 나간 후 다시 접속을 해도 인터셉터를 통해 새로운 AccessToken을 요청할 것이고, 사용자는 로그인 과정없이 계속 사이트를 이용할 수 있게된다. 사용자가 로그아웃을 요청하고 사이트를 떠났다면 다시 로그인을 하기 전까진 사이트를 이용할 수 없어야한다. 하지만 RefreshToken을 httpOnly cookie 방식을 사용했고, 이 방식은 코드단에서 쿠키에 접근할 수 없었다. 따라서 RefreshToken에 대한 정보를 인증서버에서 관리하고 있어야했다. 따라서 RefreshToken에 대한 정보는 인증서버에서 관리할 수 있도록 설계해서 사용자가 로그아웃 요청시 해당 토큰은 브라우저에 남아있겠지만 인증서버에 만료되었다는 정보를 유지하게되어 사용자가 다시 로그인 시도를 하기 전까진 서비스를 사용못하도록 할 수 있었다.


개발 시작은 2편에 계속

profile
🦉🦉🦉🦉🦉

1개의 댓글

comment-user-thumbnail
2021년 12월 20일

다음편 안올려주시나요 ㅠㅠ

답글 달기