사전 ID 토큰
표준

ID 토큰

gabury1고친 사람 github-actions[bot]

ID 토큰은 사용자가 누구이고 언제 로그인했는지를 앱에 알려 줍니다. 로그인을 대신 맡아 준 서버가 이 내용을 적고 서명해서 건넵니다. 앱은 비밀번호를 한 번도 보지 않고 이 토큰 하나로 사용자를 알아봅니다. 다른 서비스의 계정으로 로그인하는 버튼 뒤에서 오가는 것이 대개 이것입니다.

쉽고 빠른 이해

ID 토큰은 로그인 결과를 앱에 전하는 서명된 문자열입니다. 회사 계정으로 사내 위키에 들어가면 위키는 비밀번호 대신 이 토큰을 받습니다. 그리고 「사번 248 번 직원이 방금 이 위키에 로그인했다」를 읽어 냅니다.

이게 없으면 앱은 로그인을 맡아 준 인증 서버에게서 받은 출입증(액세스 토큰)을 로그인의 증거로 삼게 됩니다. 출입증은 누가 들고 왔는지도, 어느 앱에 주려고 만든 것인지도 앱에 알려 주지 않습니다. 옆 앱이 받은 출입증을 내 앱에 내밀어도 가려내지 못합니다. 그러면 옆 앱이 그 사용자인 척 내 앱에 들어옵니다.

어떻게 도나:

  1. 앱이 사용자를 인증 서버로 보내 로그인시킵니다
  2. 인증 서버가 누가, 어느 앱에, 언제 로그인했는지를 적고 서명해서 앱에 줍니다
  3. 앱은 서명과 적힌 값을 맞춰 본 뒤 사용자를 들여보냅니다

대가는 맞춰 보는 일을 앱이 전부 떠맡는다는 것입니다. 하나라도 빠뜨리면 남의 토큰이 통합니다. 토큰 안의 값은 누구나 풀어 읽을 수 있어서 비밀을 적어 두지도 못합니다.

상세

ID 토큰은 OpenID Connect 라는 로그인 규약이 정한 토큰입니다. OpenID Connect 는 로그인을 남의 서버에 맡기고 그 결과만 받아 오는 방법입니다. 이름의 ID 는 신원(identity)을 뜻합니다.

OpenID Connect 는 OAuth 라는 규약 위에 로그인을 얹었습니다. OAuth 는 사용자를 대신해 앱이 남의 서비스를 부르게 해 주는 규약입니다. 그래서 OpenID Connect 로 로그인하면 OAuth 가 주던 것과 로그인 결과가 한 번에 옵니다.

로그인을 시켜 주는 서버를 이 편에서는 인증 서버라고 부릅니다. OAuth 쪽에서는 같은 서버를 인가 서버라고 합니다. 앱에 무엇을 해도 되는지 허락을 내주는 서버라서 붙은 이름입니다. 로그인을 맡기는 쪽 서비스는 앱이라고 부릅니다.

액세스 토큰이 알려 주지 않는 신원

OAuth 에서 사용자가 허락하면 앱은 액세스 토큰을 하나 받습니다. 액세스 토큰은 데이터를 요청할 때 내미는 출입증입니다.

이 출입증을 받아 읽는 쪽은 데이터를 들고 있는 자원 서버입니다. 앱은 출입증을 열어 보지 않고 건네기만 합니다. 출입증의 생김새는 정해져 있지 않아서, 앱이 열어 봐도 누구의 것인지 읽어 낸다는 보장이 없습니다.

그래서 액세스 토큰을 로그인의 증거로 쓰면 구멍이 생깁니다. 같은 인증 서버를 쓰는 옆 앱이 자기가 받은 액세스 토큰을 내 앱에 내밀 수 있습니다. 내 앱은 그 토큰이 나를 위해 발급된 것인지 가리지 못합니다. 그러면 옆 앱이 그 사용자인 척 내 앱에 들어옵니다.

ID 토큰은 이 빈칸을 채웁니다. 앱이 직접 열어 읽으라고 만든 토큰입니다. 안에는 누가 로그인했는지와 어느 앱을 위해 발급했는지가 적혀 있습니다. OpenID Connect 가 OAuth 위에 서 있어서 두 토큰은 한 번의 로그인에서 함께 건너옵니다.

둘을 나란히 놓으면 이렇습니다.

ID 토큰 액세스 토큰
읽는 쪽 앱 자원 서버
알려 주는 것 누가 언제 로그인했나 무엇을 해도 되나
내미는 상대 없다. 앱이 읽고 끝난다 자원 서버

토큰의 생김새

ID 토큰은 JWT(JSON Web Token, JSON 웹 토큰) 형식을 씁니다. JWT 는 값 몇 개를 적고 서명을 붙여 한 줄 문자열로 만든 토큰입니다. 점 두 개가 이 문자열을 헤더, 내용, 서명의 세 토막으로 가릅니다.

실물은 아래처럼 생겼습니다. 한 줄 문자열을 점 앞에서 끊어 세 줄로 놓고 뒷부분은 줄였습니다.

eyJhbGciOiJSUzI1NiIs…   // 헤더
.eyJpc3MiOiJodHRwczo…   // 내용
.dBjftJeZ4CVP-mB92K2…   // 서명

앞의 두 토막은 Base64url 로 적혀 있습니다. Base64url 은 바이트를 주소창에 넣어도 깨지지 않는 글자로 바꾸는 방식입니다. 암호가 아니어서 누구든 풀면 원래 글자가 나옵니다.

그래서 ID 토큰은 대개 내용을 가리지 않습니다. 토큰을 손에 넣은 사람은 안에 적힌 값을 다 읽습니다. 남이 보면 안 되는 값을 여기 적지 않는 까닭입니다.

토큰에 적히는 값

가운데 토막을 풀면 JSON(JavaScript Object Notation, 자바스크립트 객체 표기법) 객체 하나가 나옵니다. 그 안의 값 하나하나를 클레임이라고 부릅니다. 클레임 이름이 세 글자 안팎으로 짧은 것은 토큰을 짧게 유지하려는 것입니다.

로그인에 쓰이는 대표 클레임은 이렇습니다. 오른쪽 주석은 설명을 위해 붙인 것이라 실제 토큰에는 없습니다.

{
  "iss": "https://id.example", // 발급자
  "sub": "248289761001",   // 사용자 번호
  "aud": "wiki-app",       // 받을 앱
  "exp": 1781000600,       // 만료 시각
  "iat": 1781000000,       // 발급 시각
  "nonce": "n-0S6WzA2Mj"   // 요청과 짝
}

iss 는 토큰을 발급한 인증 서버의 주소입니다. sub 는 그 인증 서버 안에서 사용자 한 사람에게 붙는 번호입니다. 한 번 붙은 번호는 바뀌지 않습니다. 다른 사람에게 다시 가지도 않습니다.

그래서 내 데이터베이스에서 사용자를 가려내는 값은 iss 와 sub 의 짝입니다. sub 하나만 쓰지 않는 까닭은 인증 서버가 둘 이상이면 번호가 겹칠 수 있어서입니다.

사용자를 가려내는 데 이메일은 쓰지 않습니다. 사람이 주소를 바꾸면 다른 사람으로 보입니다. 조직에서는 나간 사람의 주소가 새 사람에게 다시 가기도 합니다.

aud 는 이 토큰을 받을 앱입니다. 앱이 인증 서버에 처음 등록할 때 받은 앱 번호가 여기 적힙니다. 앞 소절의 구멍을 막는 값이 이것입니다.

exp 와 iat 는 만료 시각과 발급 시각입니다. 둘 다 유닉스 시간으로 적습니다. 유닉스 시간은 정해진 기준 시각부터 센 초입니다. ID 토큰은 로그인하는 순간에 한 번 읽고 끝나서 기한을 대개 짧게 잡습니다.

nonce 는 앱이 로그인을 청할 때 지어 보낸 한 번짜리 값입니다. 인증 서버는 받은 값을 토큰에 옮겨 적습니다. 쓰임은 뒤의 「맞춰 보는 다섯 가지」에서 봅니다.

이 밖에 이름, 이메일, 프로필 사진 같은 값을 더 받을 수 있습니다. 앱이 로그인을 청하면서 원한 만큼만 받습니다. 이런 값을 토큰에 바로 싣는 인증 서버가 있습니다. 사용자 정보를 내주는 주소를 앱이 따로 불러야 주는 곳도 있습니다.

토큰이 도착하는 순서

ID 토큰은 로그인 한 번의 끝에서 앱에 도착합니다. 중간에 인가 코드라는 교환권이 한 번 오갑니다. 브라우저와 앱과 인증 서버 셋이 주고받는 순서는 이렇습니다.

sequenceDiagram
    participant 브라우저
    participant 앱
    participant 인증서버 as 인증 서버
    브라우저->>앱: 로그인 버튼을 누른다
    앱-->>브라우저: 인증 서버로 보낸다 · nonce 를 실어서
    브라우저->>인증서버: 아이디와 비밀번호를 낸다
    인증서버-->>브라우저: 앱으로 돌려보낸다 · 인가 코드를 붙여서
    브라우저->>앱: 인가 코드를 전한다
    앱->>인증서버: 인가 코드를 내고 토큰을 청한다
    인증서버-->>앱: ID 토큰과 액세스 토큰
    Note over 앱: 다섯 가지를 맞춰 본 뒤 세션을 연다

비밀번호는 인증 서버의 화면에만 들어갑니다. 앱은 그 화면을 보지 못합니다.

인증 서버는 로그인이 끝나면 사용자를 앱으로 돌려보내면서 인가 코드를 붙입니다. 인가 코드는 토큰으로 바꿔 받는 한 번짜리 교환권입니다.

앱은 이 교환권을 들고 인증 서버를 직접 부릅니다. 그래서 ID 토큰은 브라우저 주소창을 지나지 않고 앱으로 바로 건너옵니다. 주소창을 지나간 값은 기록에 남습니다. 옆 사람 눈에도 들어갑니다.

맞춰 보는 다섯 가지

ID 토큰은 문자열일 뿐이라 누구든 비슷한 것을 지어 보낼 수 있습니다. 그래서 앱은 받자마자 다섯 가지를 맞춰 봅니다. 하나라도 어긋나면 로그인을 거절합니다.

첫째가 서명입니다. 인증 서버는 자기만 아는 개인 키로 토큰에 서명합니다. 앱은 인증 서버가 누구에게나 공개해 둔 공개 키로 그 서명을 확인합니다. 두 키는 짝이라서 개인 키로 한 서명은 그 짝인 공개 키로만 확인됩니다.

내용이 한 글자라도 바뀌면 서명 확인이 실패합니다. 다른 서버가 만든 토큰도 실패합니다. 토큰을 지어낸 쪽은 인증 서버의 개인 키를 갖고 있지 않기 때문입니다.

공개 키는 인증 서버가 정해진 주소에 모아 올려 둡니다. 인증 서버는 이따금 키를 바꿉니다. 그래서 앱은 그 주소를 주기적으로 다시 읽어 받아 둔 키를 갱신합니다.

나머지 넷은 앞에서 본 클레임입니다. 다섯을 모아 놓으면 이렇습니다.

맞춰 보는 것 무엇을 확인하나 빠뜨리면
서명 인증 서버의 공개 키로 확인되나 아무나 지어낸 토큰이 통한다
iss 내가 믿는 인증 서버가 발급했나 모르는 서버의 토큰이 통한다
aud 내 앱의 번호가 적혔나 옆 앱에 발급된 토큰이 통한다
exp 만료 시각이 지나지 않았나 오래전에 새 나간 토큰이 계속 통한다
nonce 내가 보낸 값과 같은가 가로챈 토큰을 다른 로그인에 끼워 넣는다

aud 줄이 액세스 토큰에 없던 확인입니다. 옆 앱이 받은 ID 토큰에는 옆 앱의 번호가 적혀 있어서 내 앱에서 걸립니다.

nonce 는 앱이 로그인을 청할 때마다 새로 지어 보내는 값입니다. 앱은 보낸 값을 기억해 둡니다. 돌아온 토큰에 같은 값이 있어야 방금 내가 낸 요청의 답입니다.

예전에 가로챈 토큰을 다시 밀어 넣는 공격을 재전송 공격이라고 부릅니다. 가로챈 토큰에는 지난 요청의 nonce 가 적혀 있어서 이 확인에서 걸립니다.

exp 를 볼 때는 두 서버의 시계가 조금 어긋난다는 것을 감안합니다. 대개 몇 분 안쪽의 여유를 두고 판정합니다.

로그인 뒤의 쓰임

ID 토큰의 일은 로그인하는 순간에 끝납니다. 앱은 다섯 가지를 맞춰 본 뒤 자기 세션을 엽니다. 세션은 앱이 자기 사용자에게 따로 붙이는 로그인 상태입니다. 대개 쿠키에 실려 요청마다 따라옵니다.

그 뒤의 요청은 세션으로 판정합니다. ID 토큰의 기한이 짧아도 사용자가 곧바로 로그아웃되지 않는 까닭입니다. 세션을 얼마나 유지할지는 앱이 따로 정합니다.

서버가 밖에 열어 둔 호출 창구를 API(Application Programming Interface, 응용 프로그램 인터페이스)라고 부릅니다. ID 토큰은 다른 서버의 API 를 부를 때 내밀지 않습니다. ID 토큰의 aud 에는 앱이 적혀 있지 그 API 를 연 서버가 적혀 있지 않습니다.

남의 서비스를 부르는 일은 함께 받은 액세스 토큰이 맡습니다. 앞의 비교표에서 ID 토큰의 「내미는 상대」가 「없다」였던 까닭이 이것입니다.

쓰는 경우와 안 쓰는 경우

로그인을 남의 인증 서버에 맡길 때 씁니다. 회사 계정으로 사내 도구에 들어가는 경우가 그렇습니다. 한 번 로그인해 여러 서비스를 함께 쓰는 싱글 사인온도 여기 듭니다.

계정을 내 서비스가 직접 들고 있으면 ID 토큰이 필요 없습니다. 아이디와 비밀번호를 받아 바로 세션을 여는 쪽이 부품이 적습니다.

사람이 없는 통신에도 쓰지 않습니다. 서버끼리 부르는 일에는 로그인한 사용자가 없으므로 알려 줄 신원도 없습니다. 이때는 앱 자신의 자격으로 액세스 토큰만 받습니다.

관련 항목

ID 토큰과 함께 발급되는 토큰

액세스 토큰 · 리프레시 토큰 · 인가 코드 · Bearer 토큰 · 불투명 토큰 · 토큰

ID 토큰을 정의하는 표준·프로토콜

OpenID Connect · OAuth 2.0 · JWT · JWS · JWE · OpenID Connect 디스커버리

ID 토큰을 이루는 구성 요소

클레임 · JSON · Base64url · 유닉스 시간 · nonce

ID 토큰을 검증하는 수단

전자 서명 · 공개 키 암호 · JWK · 키 교체 · 해시 함수 · 인증서

ID 토큰을 주고받는 역할

인가 서버 · 인증 서버 · 신뢰 당사자 · 자원 서버 · 클라이언트 · 브라우저 · 사용자 에이전트

ID 토큰이 발급되는 로그인 단계

인가 코드 흐름 · PKCE · 리다이렉트 · 토큰 엔드포인트 · UserInfo 엔드포인트 · state 파라미터 · 스코프

ID 토큰을 읽은 뒤 로그인 상태를 잇는 수단

세션 · 쿠키 · 세션 쿠키 · 로그아웃 · 슬라이딩 만료 · 재인증

ID 토큰이 속하는 상위 분류

인증 · 인가 · 인증과 인가 · 싱글 사인온 · 페더레이션 · 신원 공급자

ID 토큰을 노리는 공격

재전송 공격 · 토큰 탈취 · 크로스 사이트 요청 위조 · 중간자 공격 · 혼동된 대리인 문제 · 알고리즘 혼동 공격

ID 토큰을 발급하거나 검증하는 제품

Keycloak · Auth0 · Okta · Microsoft Entra ID · Spring Security · Passport.js

다른 이름: ID token · id_token · 아이디 토큰