OAuth2 토큰 — 액세스 토큰과 리프레시 토큰¶
OAuth 흐름이 끝나면 앱은 "이 사용자가 이만큼 허락했다"는 증거로 토큰을 받아요. 그런데 토큰이 하나가 아니라 두 종류가 와서 처음엔 헷갈리죠. 하나는 짧게 살아있는 액세스 토큰, 다른 하나는 오래 살아있는 리프레시 토큰이에요. 왜 굳이 두 개로 나누는지부터 이해하면 나머지는 따라옵니다.
개요¶
액세스 토큰은 앱이 API를 호출할 때 보여주는 "입장권"이고, 리프레시 토큰은 그 입장권이 만료됐을 때 새 입장권을 받으러 가는 데 쓰는 "재발급 증서"예요. 둘을 나눠두면 액세스 토큰의 수명을 짧게 유지할 수 있어서, 토큰이 유출돼도 피해가 오래가지 않게 됩니다.
핵심 개념¶
액세스 토큰(Access Token). 리소스 서버(API)가 실제로 확인하는 토큰이에요. 유효기간이 짧게(보통 수십 분에서 몇 시간) 설정되죠. 그래서 만약 이 토큰이 탈취돼도 짧은 시간 안에 죽으니까 피해가 커지기 전에 무효화돼요.
액세스 토큰은 클라이언트가 "읽어서 해석"하는 대상이 아니에요. oauth.net에서도 강조하듯, 액세스 토큰의 주된 수신자(audience)는 리소스 서버이지 OAuth 클라이언트가 아니에요. 클라이언트는 그저 토큰을 들고 API를 호출하는 역할이죠. 그래서 액세스 토큰에 사용자 식별 정보를 담아 클라이언트가 해석하게 하는 건 스펙 의도와 어긋나요. 사용자 정보가 필요하면 별도의 ID 토큰이나 프로필 조회로 얻어요.
토큰 형식은 OAuth가 정하지 않아요. 액세스 토큰이 불투명한 문자열(opaque)일 수도 있고, JWT처럼 내용을 들여다볼 수 있는 형식일 수도 있어요. OAuth 2.0 스펙 자체는 토큰 형식에 대해 "리소스 서버와 클라이언트가 합의해서 쓴다" 정도로만 두고, 형식 규칙은 다른 스펙(예: JWT 기반이라면 RFC 9068)에 맡깁니다.
리프레시 토큰(Refresh Token). 액세스 토큰이 만료되면, 사용자에게 다시 로그인을 시키는 대신 이 토큰으로 새 액세스 토큰을 발급받아요. 수명이 길기 때문에, 유출되면 오래 문제가 될 수 있어서 서버(백엔드)에 안전하게 보관해야 합니다. 그리고 일부 흐름(client credentials 등)은 리프레시 토큰을 아예 발급하지 않아요. 필요할 때 재발급하면 되니까요.
토큰 만료(expiry)는 여러 층으로. 액세스 토큰의 짧은 수명, 리프레시 토큰의 재발급, 그리고 만료된 리프레시 토큰 거부까지, 이 세 단계가 합쳐져서 "토큰이 탈취돼도 오래 못 쓴다"는 보안 축이 만들어져요.
실제 적용(데이터스케쳐스)¶
외부 서비스 연동에서 받은 토큰은 우리 백엔드에 안전하게 보관해요. 액세스 토큰의 수명이 짧다면 리프레시 토큰으로 자동 재발급되도록 하고, 리프레시 토큰은 서버 측 저장소(DB 또는 보안 저장소)에만 두도록 설계해요. 브라우저(클라이언트)에 긴 수명의 리프레시 토큰을 노출하지 않는 게 원칙이에요.
고객이 연결을 해제하면, 액세스 토큰뿐 아니라 리프레시 토큰까지 함께 무효화돼야 해요. 그래야 "연결을 끊었는데 우리가 계속 데이터를 읽는다"는 상태가 생기지 않아요.
토큰 폐기와 무효화도 챙겨요. 리프레시 토큰은 만료뿐 아니라 폐기(revocation)도 가능해요. 고객 연결 해제나 의심스러운 활동이 감지되면 해당 리프레시 토큰을 무효화하고, 이후 재발급 요청을 거부하도록 처리해요. 이렇게 "토큰을 언제까지 쓸 수 있는지"를 만료와 폐기 두 축으로 통제하면, 탈취된 토큰의 수명을 더 좁힐 수 있어요.
토큰 마다 만료·재발급·폐기 정책을 감사 로그와 함께 두는 게 운영의 핵심이에요.
더 알아보기¶
- 공식 문서 (1차): Access Tokens, OAuth 2.0 (RFC 6749), Refresh Token (RFC 6749 §1.5)
- 큐레이션/블로그 (2차): Auth0 — Access Tokens, OAuth.net 블로그