[AlgoHelper] 인증 로직에 대한 고찰

Jay ·2023년 5월 25일
0
post-custom-banner

AlgoHelper 서비스에서 0Auth 2.0을 이용한 인증 기능을 구현하면서 만난 문제들을 기록해 보았다.

주요 문제들은 다음과 같다.

  1. JWT vs Session
  2. 구글에서 받은 access, refresh token을 그대로 재사용 해야 할까?
  3. 프론트 단에서 Access token, refresh token을 어디에 저장해야 하는가
  4. userState는 어떻게 관리해야 할까.

이다.

1. JWT vs Session

세션 방식과 비교하여 JWT가 가지는 장점은 다음과 같다.

1) 별도의 서버 저장소가 필요하지 않음.
2) 확장성

물론 JWT를 좀 더 안전하게 사용하기 위해 access, refresh token을 사용하게 되면 서버 측에서 별도의 저장소가 필요해진다.

하지만 그럼에도 불구하고 JWT 방식을 사용하면, 요청마다 메모리 혹은 디스크에 접근하는 세션 방식보다, refresh token이 만료되었을 때만 I/O 작업이 일어나기 때문에 서버측의 부담이 크게 줄어든다.

한계점

물론 access, refresh token을 사용한다고 모든 보안적 결점이 해결되는 것은 아니다.

access token 혹은 refresh 토큰이 탈취당하는 경우를 완벽히 방어하지는 못한다.

결론

이런 점들을 고려하여 jwt를 사용하기로 결정하였다.

Access & Refresh token ?

토큰 기반 인증 방식에서 사용자가 서버 리소스에 액세스 하기위해 서버에서는 유저가 로그인 할 때 access token을 발급한다.

클라이언트는 이 토큰을 클라이언트 단에 저장하여 사용하게 되는데, 이 때문에 해커의 표적이 된다.

따라서 유효기간을 짧게 하는 방식으로 해킹 위험을 최소화 하는 것이 보통이지만, 유저가 유효기간이 끝날 때마다 재로그인 해야하는 귀찮은 상황이 일어난다.

그렇게 해서 도입된 방식이 access & refresh token 방식이다.

refresh 토큰이라는 개념을 도입하여 access token의 짧은 유효기간도 유지하면서, 유저가 매번 재로그인해야하는 상황을 방지할 수 있다.

사용성과 보안을 모두 고려한 절충안인 것이다.

2. access, refresh token 재사용?

최초 프론트에서 구글 로그인을 하면 다시 client로 query에 client Id 등 임시 로그인용 credential을 발급 받는다.

프론트가 해야할 일은 이 임시코드를 백엔드로 넘겨주기만 하면 되지만, 백엔드에서 이 코드로 구글 resource server에서 access token, refresh token을 응답받은 이후가 문제가 발생하는 지점이었다.

사실 이 토큰들을 재사용 하지 않고, access, refresh token을 백엔드에서 따로 새로 발급하여 프론트와 데이터를 주고 받는 게 가장 이상적이다. 왜냐하면 이렇게 하면, 최초 유저정보를 받아오는 데에만 구글과 데이터 교환이 일어나고, 그 이후에는 주도권이 클라이언트로 넘어와 추후 서비스 확장에 용이하기 때문이다.

하지만 결론적으로 프로젝트 규모를 생각했을 때, 구글에서 받아온 토큰을 그대로 사용하기로 결정하였다.

3. 프론트 단에서 access, refresh token 관리

기본적으로 refresh 토큰은 서버에서 관리하는 것이 가장 이상적이다.
하지만 그렇게 되면 access token이 만료될 때마다, 서비스 이용자는 새로 로그인 해야하는 문제가 발생한다.

따라서 보안성과 사용편의성 측면에서 절충한 결과물이 access, refresh token 이용한 인증 로직이다.

어디에 토큰을 저장해야하나

그렇게 aceess, refresh 토큰을 이용해 로그인 로직을 구현한다고 하면, 과연 이 토큰들을 어디에, 어떻게 관리해야 할까?

조사한 결과 토큰들을 관리하는 방식은 크게 3가지로 나뉜다.

1) access token을 localStorage 에 저장

  • refresh token은 localStorage 나 httpOnly cookies에 저장
  • access token은 XSS attack으로부터 취약

2) access token을 httpOnly cookies에 저장

  • CSRF 공격에는 취약하지만 어느정도 예방 가능하고, XSS의 노출 면에서는 조금 더 나음.

3) access token을 메모리(js variable)에 저장

  • CSRF 공격으로부터 안전하고, XSS 노출에 대해서는 조금 더 낫다.
  • accees token은 브라우저에 저장하지 않고 프론트 상태로 관리

만료된 access token을 갱신하는 3가지 방법

1) client에서 access token 만료 여부를 확인하고, 만료되었을 시 refresh token을 함께 전송

2) client에서 access token 만료 여부를 확인하고, 만료되었을 시 refresh 토큰 발급 받는 요청을 따로 보내고, 뒤이어 발급받은 access token으로 다시 본 요청.

3) client에서 api 요청을 보낸 후 server가 만료 여부를 확인해 401 response를 보내면, 응답을 받은 client가 refresh api 요청을 보내 access token을 발급받아 다시 재 요청.

refresh token을 갱신하는 2가지 방법

1) refresh token은 갱신하지 않고, access token만 갱신

2) server에서 refresh 요청을 받았을 때, 받은 refresh token의 만료 기간이 임박하면 access token의 갱신과 더불어 refresh token도 갱신.

결론

이렇게 access token과 refresh token을 관리하는 방법은 매우 다양하다.

보안적인 측면을 고려해, access token은 메모리에 저장하고, access token이 만료되면 401 response를 보내서, 응답받은 client가 refresh api를 보내도록 로직을 구현하였다.

4. UserState 관리

그렇다면 useState는 어떻게 관리하여야 할까?

유저 정보는 access token을 사용하여 서버로부터 받아오게 되는데, 이 정보를 브라우저 저장소에 저장하면 정보가 탈취될 수 있는 위험이 있다.

따라서 user 정보가 필요할 때 마다 server에 데이터를 요청하여 사용하되, 프론트 상태관리 라이브러리를 사용해 메모리 단위에 정보를 저장하기로 하였다.

최초 로그인 시 응답받은 access token을 전역상태에 저장하고, 이 access token을 SWR을 사용해 전송해서 user정보를 요청하고, 응답받은 유저정보(캐싱된)를 사용함으로써 userState가 관리되게 하였다.

결론

결국 refresh token을 클라이언트 측에 저장해야, 새로고침을 하더라도 로그인을 유지할 수 있다.

따라서 refresh token은 상대적으로 가장 안전한 http only cookies에 저장하고, aceess token은 recoil을 사용하여 전역상태로 관리하며, 유저 정보는 매번 서버에 request를 요청하되 SWR을 사용해 데이터를 전역상태로 유지하는 방식으로 로그인 로직을 구현하였다.

profile
Jay입니다.
post-custom-banner

0개의 댓글