JWT - JSON WEB TOKEN
1. JWT
2. PAYLOAD
3. 서명
JWT.PAYLOAD.서명
형식으로 존재
Secret Key - 서버에서 보유
JWT + PAYLOAD + secret = 서명
서버에서 이렇게 토큰을 검증함
DB단에 인가위한 정보를 저장할 필요 없다. 사용자가 들고있는 JWT만 스캔해서 검증 가능
그러나 100% session을 대체할 수는 없다.
session을 이용하면 핸드폰으로 pc카톡을 제어하듯 이용할 수 있다.
맹점:
1. token은 사용자 보유라 제어가 불가능
2. 또한 탈취당하면 임의로 유효성을 삭제할 수 없다.
결론: 실제 서비스 중 JWT만으로 인가를 구현하는 곳은 적다.(session을 공용함)
대체방법: access token, refresh token을 사용한다.
access token, refresh token 전송 -> refresh token DB에 저장 -> access token은 수명은 몇분짜리다. 유횩기간종료되면 -> 서버에 재발행 요청 -> 서버는 DB의 refresh token과 대조 -> 맞다면 새 access token 발행 -> 반복
단어집:
JWT, token, access token, refresh token, authentication, authorization, cookie,
CSRF - Cross Site Resource Forgery(교차 사이트 요청 위조 공격)
XSS,
httpOnly, secure(https) - httpOnly이며 secure 옵션이 켜져있는 cookie는 JS로 읽기 불가능, 네트워크 감청을 통한 쿠키값 읽기를 방어함 -> XSS 공격으로 토큰 탈취 방지
TL;DR - too long; didn't read 즉 생략이라는 뜻이다.
httpOnly secure
cookie에 저장하자.jwt는 authorization과 관련된 것이다.
login을 통해 authentication 이후, 서버측의 secret key를 사용하여 encrypt된 JWT access token을 client에게 전달한다. 이 때, 서버 측에서 session 데이터를 메모리에 저장하지 않는 것이 핵심이다.
이후의 client 측 request에 담아져 전달된JWT token을 decrypt하여, 해당 user로 부터 전달된 request인지 확인한다. JWT token 사용시 user가 단 하나의 서버와 sesstion 관계를 유지하는 것이 아니라, 복수개의 서버에 JWT token을 전달하여 authorization을 할 수 있게 된다.
왜 refresh token이 필요한가?
authentication후, server에서 access token, refresh token을 생성하여 client에게 전달한다. 이 때 access token을 제3자가 취득하여 사용자 정보에 계속해서 접근하는 것을 막기 위해 access token의 만료 기한을 짧게 설정한다.
refresh token을 이용하여 새로운 acess token을 받아 사용하면, 최초 access token 만료 때에 새로운 access token을 받아 사용하면 다시 user 정보에 접근할 수 있게 된다.
access token
의 경우 만료기한이 짧기 때문에 client는 cookie에 저장하고(cookie에는 httpOnly와 secure 옵션이 있어, local stroage에 저장하는 것보다 안전하다. local storage의 경우에는 js로 접근이 쉬운 문제가있다.)사용 한다고 가정하고, refresh token의 만료 기한은 상대적으로 길게 설정하고 server에 저장한다.
refresh token
의 경우, 제3자가 취득하면 access token을 계속해서 발급받을 수 있기 때문에 user가 해당 서비스를 logout
할 때, refresh token을 만료시켜 logout 후에는 refresh token을 사용할 수 없도록 만들어야 한다.
refresh token은 일반적으로 DB에 저장되어 관리되는 opaque string이다. DB에 이 것을 저장하는 것은, refresh token을 DB로부터 삭제하여 폐기할 수 있도록 하는 것이다. (이는 client가 'refresh token을 request에 담아 인증 서버로 전달 했을 때, 인증 서버는 이 refresh token이 유효한 상태인지 확인하기 위해 DB에 접속하여 refresh token이 존재함을 확인해야 한다
는 것을 의미한다.)
어떻게 Frontend에서 JWT access token을 안전하게 저장할 수 있는가?
참조: lcalStorage vs Cookies: All You Need To Know About Storing JWT Tokens Securely in The Front-End
user의 authentication 이후, authorization 서버는 access token과 refresh token을 반환할 것이다. access token은 response body에 포함되어질 것이며, refresh token은 cookie에 포함되어질 것이다.
refresh token cookie setup:
access token을 메모리에 저장한다는 것은 당신이 access token을 frontend site의 변수에 저장한다는 것을 의미한다. 그렇다, 이 것은 user가 다른 탭으로 이동하거나, site를 refresh하면 access token은 사라지는 것을 의미한다. 이 것이 바로 우리가 refresh token이 필요한 이유이다.
access token이 사라지거나 expired 되었을 때, /refresh_token_endpoint를 호출한다. 이 때, step1에서 cookie에 저장되어 있던 refresh token가 request에 포함되어져 있을 것이다. 당신은 새로운 access token을 얻게 될 것이고 당신의 API 호출에 이 access token을 사용할 수 있게 될 것이다.
이 것은 JWT token 이 4KB보다 커질 수 있고, authorization 헤더에 넣어 사용할 수 있다는 것을 의미한다.
웹사이트나 모바일앱에 login flow 를 만들고 있다면, 아래의 article 들이 도움이 될 것이다.
[출처] JWT: JWT Token|작성자 Hyunseok
참고: What on Earth Is OAuth? A Super Simple Intro to OAuth 2.0, Access Tokens, and How to Implement It in Your Site
Passwordless Login with Email and JSON Web Token (JWT) Authentication using Next.js
API 호출시 authorization: Bearer 헤더를 통한 access token 전달 이후, backend 서버로부터 해당 access token이 만료되었다는 response를 받은 후에 access token 갱신을 진행하고, 갱신되 access token을 이용하여 다시 API를 호출하게 되는 것이 기본적인 갱신의 흐름이다.
access token과 refresh token의 만료 기한을 frontend가 알고 있다면, 불필요한 API request를 줄일 수 있는데 흐름은 아래와 같다.
- access token 존재
- access token 미만료
- API 호출
- access token 만료
- refresh token 존재/만료 시간 확인
- refresh token 존재
- refresh token 미만료
- access token 갱신 시도
- access token 갱신 성공
- API 호출 (갱신된 access token 사용)
- access token 갱신 실패
- 로그아웃 처리
- refresh token 만료
- 로그아웃 처리
- refresh token 미존재
- 로그아웃 처리
- access token 미존재
- 로그아웃 처리
문제가 생길텐데 access token이 만료된 시점 이후에 복수개의 API request가 한번에 발생하는 상황을 해결해야 할 것이다.(출처 작성자 이력)
위의 출처에서는 axios.interceptors의 기능을 사용하여 구현하였으나, 출처 작성자는 해당 동작을 원하는 함수들에만 적용할 decorator를 제작하여 이 문제를 해결하였다고 한다.
구체적으로 하나의 해결 방법을 생각했었다는데
1. access token 만료 후 5개의 각각의 비동기 API request 발생
2. 401 error가 반환되는 최초의 response 발생 시점에 access token 갱신을 시작한다. access token 갱신 도중, 401 error를 반환하게 된 request함수들을 별도의 공간에 Promise resolve를 기다리도록 설정하여 저장해 둔다. 이렇게 저장되어 있는 복수개의 함수들은 access token 갱신이 완료되는 시점에 재호출하게 될 것이다.
3. access token 갱신이 완료되면 이 token을 사용하여 호출을 기다리고 있는 Promise들을 resolve 시킨다.
4. 모든 API request들이 재호출될 것이며, 이후 각각의 API request들은 각자의 성공/실패 시나리오를 따른다.
참조: 심심재
보안은 시나리오를 생각해보면 이해가 편하다. 내 사이트에 admin으로 로그인한 상태로(쿠키에 세션ID 혹은 access token이 담겨져 있다고 가정) 네이버 메일을 열었는데, 헤커가 보낸 메일 속 이미지에 내 사이트 서버에 어떤 A 유저의 정보를 삭제하는 API 주소가 적혀있다고 해보자.
나는 그 유저 정보 삭제 API를 호출하고 싶지 않았는데 브라우저는 이미지 태그 안에 있는 스크립트를 자동으로 실행하기 때문에 갑자기 A유저 정보가 삭제되는 대참사가 발생한다. (내 부라우저 쿠키에는 인증 정보를 갖고 있기 때문에 브라우저 입장에서는 해커가 그 요청을 보낸게 아니라 내가 보낸게 되어버림) 이게 바로 Cross Site Resource Forgery(교차 사이트 요청 위조 공격)이다.
이 문제를 방지하려면 항상 서버는 요청이 왔을 때 그 요청이 진짜 내 사이트에서 왔는지를 확인해야 한다.
결론 쿠키라는 것이 결국에는 브라우저에서 자동으로 보내는 값이기 때문에 인증에 활용하기도 하지만 그것 때문에 CSRF 보안에 취약하다. refresh token만 httpOnly secure cookie에 저장하고 access token은 프로그램상 로컬변수에 담아 놓는 것이 그나마 제일 안전하다
이렇게 되면,
1. 해커가 CSRF 공격을 하더라도 쿠키에는 access token이 없기 때문에 인증 불가 상태가 되어 공격이 차단된다.
2. 해커는 httpOnly secure cookie 특성상 refresh token 자체를 털 방법이 없다.
3. access token은 local변수에 저장되어 있기 때문에 XSS를 통해 access token 자체를 털 수 없다.
외부에서 삽입한 JS local변수에 접근하는 것이 불가능하기 때문에
-> 물론 access token 활용하여 유저 정보를 탈취할 수는 있다.(그래서 어찌됐건 XSS 처리는 꼭 해야함)
XSS는 완벽하게 막을 수 없는데 그 이유는 어찌되었건 내가 인증을 한 상태에서는 서버에서 나에게 허용된 어떤 API들이 있는데 그 API들이 있는데 그 API들은 마찬가지로 XSS를 통해 해커도 호출할 수 있다는 말이기 때문이다.
어쨌든 access token은 지속적으로 네트워크를 타며 노출될 가능성이 높기 때문에 만료시간을 짧게 가져가야 한다. 그리고 쿠키에 저장된 refresh token을 가지고 매번 access token을 갱신해가면서 사용하도록 로직을 구성해야 한다.
마지막으로, 사용성을 위해 액세스 토큰이 HTTP HEAD에 없거나 만료된 경우 refresh token으로 조용히 token 재발급 과정이 일어날 수 있도록 로직을 작성해야 한다.(silent token refresh)