인가 코드
고친 사람 github-actions[bot]
인가 코드는 사용자가 내린 허락을 앱에 건네 줍니다. 앱은 이 값을 한 번만 써서 액세스 토큰으로 바꿔 받습니다. 액세스 토큰은 앱이 사용자의 데이터를 읽을 때 내미는 값입니다. 로그인 화면을 거쳐 앱으로 돌아오는 길에는 토큰 대신 이 값이 실려 옵니다.
쉽고 빠른 이해
인가 코드는 액세스 토큰으로 바꿔 받는 데만 쓰는 짧은 문자열입니다. 액세스 토큰은 앱이 남의 서비스에서 내 데이터를 읽을 때 내미는 입장권입니다. 사진 편집 앱에 내 사진첩 읽기를 허락하면, 브라우저가 앱으로 돌아오면서 주소 끝에 이 문자열을 달고 옵니다.
이게 없으면 입장권을 브라우저 주소에 실어 보내야 합니다. 주소는 방문 기록과 서버 로그에 남습니다. 그 기록이 새면 입장권도 함께 샙니다.
어떻게 도나:
- 사용자가 로그인 화면에서 앱에 허락을 내립니다
- 브라우저가 앱으로 돌아오면서 인가 코드를 전합니다
- 앱의 서버가 인가 코드와 비밀값을 함께 내고 입장권을 받습니다. 비밀값은 앱이 등록할 때 받아 서버에만 두는 문자열입니다
사람이 브라우저에서 허락할 때 씁니다. 사람 없이 서버끼리 통하는 경우에는 쓰지 않습니다.
대가는 왕복이 한 번 늘어난다는 것입니다. 비밀값을 숨길 수 없는 앱은 코드를 빼앗기지 않게 장치를 하나 더 얹어야 합니다.
상세
공연을 예매하면 문자로 예매 번호가 옵니다. 매표소에 그 번호와 신분증을 함께 내밀어야 입장권으로 바꿔 줍니다. 한 번 바꾼 번호는 다시 내밀어도 입장권이 나오지 않습니다.
예매 번호가 인가 코드입니다. 신분증은 앱의 비밀값, 곧 앱이 등록할 때 받아 서버에만 두는 문자열입니다. 입장권이 액세스 토큰입니다.
이 절은 먼저 인가 코드가 어떤 길로 오가는지 따라갑니다. 이어서 토큰을 바로 보내지 않고 코드를 거치는 까닭과, 코드에 매이는 조건을 봅니다. 끝으로 비밀값을 못 숨기는 앱을 위한 보강과 쓰는 경우를 가릅니다.
인가 코드가 오가는 길
인가 코드는 OAuth 에서 나옵니다. OAuth(Open Authorization)는 사용자의 비밀번호를 앱에 넘기지 않고도 앱이 그 사용자의 데이터를 쓰게 해 주는 권한 위임 규격입니다. 사진 편집 앱이 내 사진첩을 읽는 경우가 그렇습니다.
이 흐름에는 네 쪽이 등장합니다.
| 이름 | 하는 일 |
|---|---|
| 사용자 | 데이터의 임자입니다. 앱에 허락을 내립니다 |
| 클라이언트 | 사용자 대신 데이터를 쓰려는 앱입니다. 이 글에서는 앱이라고 부릅니다 |
| 인가 서버 | 사용자에게 허락을 묻고 인가 코드와 액세스 토큰을 내줍니다 |
| 자원 서버 | 데이터를 들고 있습니다. 액세스 토큰을 보고 요청을 받아 줍니다 |
액세스 토큰은 앱이 자원 서버에 데이터를 청할 때 내미는 문자열입니다. 무엇을 얼마 동안 해도 되는지가 이 값에 매여 있습니다. 인가 코드는 이 토큰을 받아 오는 데 쓰입니다.
앱은 인가 서버에 미리 등록합니다. 그때 아이디와 비밀값을 받아 둡니다. 비밀값은 앱의 서버만 아는 문자열입니다. 인가 서버는 이 둘을 보고 누가 찾아왔는지 확인합니다.
인가 코드는 두 번의 왕복을 잇습니다. 첫 왕복은 사용자의 브라우저를 거칩니다. 둘째 왕복에서는 앱의 서버가 인가 서버를 직접 부릅니다.
첫 왕복은 리다이렉트로 움직입니다. 리다이렉트는 서버가 브라우저에게 다른 주소로 가라고 응답해 브라우저를 옮겨 보내는 일입니다. 앱은 사용자를 인가 서버로 보냅니다. 인가 서버는 허락이 끝나면 사용자를 앱으로 되돌려 보냅니다.
sequenceDiagram
participant 브라우저
participant 앱 as 앱의 서버
participant 인가서버 as 인가 서버
앱-->>브라우저: 인가 서버로 가라고 돌려보낸다
브라우저->>인가서버: 앱이 청한 허락을 들고 간다
인가서버->>브라우저: 로그인과 허락을 묻는다
브라우저->>인가서버: 사용자가 허락을 누른다
인가서버-->>브라우저: 앱으로 가라 · 인가 코드를 붙여서
브라우저->>앱: 인가 코드를 전한다
앱->>인가서버: 인가 코드와 앱의 비밀값을 낸다
인가서버-->>앱: 액세스 토큰
인가 코드는 브라우저를 지나 앱에 닿습니다. 앱은 그 코드를 들고 인가 서버를 직접 부릅니다. 이 마지막 왕복에는 브라우저가 끼지 않습니다. 그래서 액세스 토큰은 브라우저를 한 번도 지나지 않습니다.
두 길에는 이름이 따로 있습니다. 브라우저를 거치는 길은 프런트 채널이라고 부릅니다. 서버끼리 직접 오가는 길은 백 채널이라고 부릅니다.
토큰 대신 코드를 보내는 까닭
브라우저를 지나는 주소는 여러 곳에 남습니다. 주소창과 방문 기록, 서버의 접속 로그가 그렇습니다. 다음 페이지로 넘어갈 때 이전 주소를 알려 주는 Referer 헤더에 실려 나가기도 합니다.
이 주소에 액세스 토큰을 실으면 주소가 남는 곳마다 토큰도 남습니다. 토큰은 가진 사람이면 누구든 쓸 수 있습니다. 로그 파일 하나가 새면 남의 데이터가 열립니다.
인가 코드는 새어도 피해가 작도록 만들어졌습니다. 코드만으로는 토큰을 못 받습니다. 앱의 비밀값을 함께 내야 합니다. 게다가 한 번 쓰면 버려집니다. 기한도 짧습니다.
그래서 두 길의 몫이 갈립니다. 브라우저를 지나는 길로는 새어도 쓸모가 적은 코드만 보냅니다. 오래 쓰는 토큰은 서버끼리 오가는 길로만 다닙니다.
대가로 왕복이 하나 늘어납니다. 토큰을 바로 받았다면 없었을, 코드를 토큰으로 바꾸는 왕복입니다. 앱은 그 왕복을 부를 서버 쪽 코드도 따로 갖춰야 합니다.
인가 코드에 매이는 조건
인가 서버는 코드를 내줄 때 몇 가지를 함께 기억해 둡니다. 코드를 바꾸러 온 요청이 그 기억과 맞아야 토큰을 내줍니다.
| 조건 | 막는 것 |
|---|---|
| 한 번만 쓴다 | 가로챈 코드를 나중에 다시 내미는 공격 |
| 기한이 짧다. 보통 몇 분 안쪽이다 | 로그에 남은 코드를 뒤늦게 주워 쓰는 공격 |
| 발급받은 앱만 바꿀 수 있다 | 다른 앱이 남의 코드를 가져다 쓰는 공격 |
| 처음 요청에 적은 돌아올 주소와 같아야 한다 | 코드를 엉뚱한 주소로 빼돌리는 공격 |
| 사용자가 허락한 범위가 코드에 매인다 | 바꾸는 단계에서 권한을 넓히는 시도 |
표의 넷째 줄에서 돌아올 주소는 앱이 첫 요청에 적어 보내는 주소입니다. 이 주소를 리다이렉션 URI(URI 는 Uniform Resource Identifier, 자원 식별자)라고 부릅니다.
인가 서버는 앱을 등록할 때 이 주소를 미리 받아 둡니다. 첫 요청에 적힌 주소가 등록된 주소가 아니면 코드를 보내지 않습니다. 그래서 공격자가 요청의 주소를 자기 서버로 바꿔도 코드는 그리로 가지 않습니다. 코드를 바꾸러 온 요청에 적힌 주소가 처음과 다를 때도 인가 서버는 거절합니다.
다섯째 줄의 허락한 범위는 스코프라고 부릅니다. 사진 읽기만 허락했다면 그 코드로 받은 토큰도 사진 읽기만 할 수 있습니다.
한 번 쓴 코드가 다시 오면 누군가 코드를 가로챈 것일 수 있습니다. 인가 서버는 그 요청을 거절합니다. 그 코드로 이미 내준 토큰까지 폐기하기도 합니다.
코드의 생김새는 따로 정해져 있지 않습니다. 인가 서버가 만듭니다. 앱은 속을 들여다보지 않은 채 되돌려 보냅니다. 대개 추측하기 어려운 무작위 문자열입니다.
오가는 값
두 왕복에서 코드가 어떻게 실리는지 봅니다. 먼저 인가 서버가 브라우저를 앱으로 돌려보낼 때의 주소입니다. 코드는 주소 뒤의 쿼리 문자열에 실립니다.
GET /callback // 앱의 돌아올 주소
?code=SplxlOBeZQQYbYS6 // 인가 코드
&state=xyz // 아래 state 절
브라우저가 이 주소를 부르면 앱의 서버가 코드를 받습니다. 앱의 서버는 곧바로 인가 서버의 토큰 엔드포인트를 부릅니다. 토큰 엔드포인트는 코드를 토큰으로 바꿔 주는 인가 서버의 창구입니다.
POST /token
Authorization: Basic … // 앱 신분
grant_type=authorization_code // 코드 교환
&code=SplxlOBeZQQYbYS6 // 받은 코드
&redirect_uri=… // 처음과 같게
요청 헤더의 Authorization 줄이 앱의 아이디와 비밀값을 싣습니다. 본문은 코드를 바꾸러 왔다는 표시와
코드, 돌아올 주소를 싣습니다. 응답에는 액세스 토큰이 실려 옵니다. 이때부터 그 코드는 쓸모가 없습니다.
비밀값이 없는 앱과 PKCE
모든 앱이 비밀값을 숨길 수 있는 것은 아닙니다. 브라우저 안에서만 도는 화면과 휴대폰에 설치되는 앱은 프로그램 전체가 사용자 기기에 깔립니다. 그 안에 적어 둔 비밀값은 누구든 꺼내 볼 수 있습니다. 이런 앱을 공개 클라이언트라고 부릅니다.
비밀값이 없으면 코드만 쥔 사람이 토큰을 받아 갑니다. 휴대폰에서는 같은 기기의 다른 앱이 돌아올 주소를 가로채 코드를 빼앗을 수 있습니다. 이 공격을 인가 코드 가로채기 공격이라고 부릅니다.
PKCE(Proof Key for Code Exchange, 코드 교환 증명 키)는 비밀값 없이 이 틈을 막는 장치입니다. 앱은 사용자를 인가 서버로 보내기 전에 무작위 문자열을 하나 만들어 혼자 간직합니다. 이 간직한 문자열은 요청마다 새로 만듭니다.
PKCE 는 해시를 씁니다. 해시는 어떤 값을 원래 값으로 되돌릴 수 없게 뒤섞은 값입니다. 같은 값을 넣으면 늘 같은 해시가 나옵니다.
인가 서버로 사용자를 보낼 때 앱은 간직한 문자열의 해시만 실어 보냅니다. 해시가 브라우저를 지나며 새어도 간직한 문자열은 드러나지 않습니다.
코드를 토큰으로 바꿀 때 앱은 간직한 문자열을 냅니다. 인가 서버는 그 문자열을 해시해 처음 받은 해시와 맞춰 봅니다. 코드를 가로챈 쪽은 간직한 문자열을 모르므로 토큰을 못 받습니다.
비밀값을 숨길 수 있는 앱도 PKCE 를 함께 쓸 수 있습니다. 둘은 확인하는 대상이 다릅니다. 비밀값은 어느 앱이 바꾸러 왔는지를 확인합니다. PKCE 는 이 요청을 시작한 쪽이 바꾸러 왔는지를 확인합니다.
돌아온 코드를 확인하는 state
앱의 돌아올 주소는 누구든 부를 수 있습니다. 공격자는 자기 계정으로 받은 인가 코드를 붙인 주소를 피해자에게 열게 할 수 있습니다. 그러면 피해자가 쓰는 앱이 공격자의 계정에 묶입니다.
피해자는 그걸 모른 채 앱에서 사진을 저장합니다. 그 사진은 공격자의 사진첩에 쌓이고, 공격자는 자기 계정으로 들어가 그 사진을 봅니다. 이런 공격을 크로스 사이트 요청 위조라고 부릅니다.
이 공격을 막는 값이 state 파라미터입니다. 앱은 첫 요청을 보낼 때 추측하기 어려운 값을 만들어 사용자의 세션에 적어 둡니다. 돌아온 주소의 state 가 적어 둔 값과 다르면 코드를 버립니다.
PKCE 를 쓰는 앱에서는 이 공격이 한 번 더 막힙니다. 공격자의 코드는 공격자가 간직한 문자열에 묶여 있습니다. 피해자의 앱이 내는 문자열과는 맞지 않습니다.
쓰는 경우와 안 쓰는 경우
사람이 브라우저 앞에서 앱에 허락을 내리는 경우에 씁니다. 다른 서비스 계정으로 로그인시키는 OpenID Connect도 이 코드를 거칩니다.
그랜트는 앱이 토큰을 받아 내는 방식 하나를 가리킵니다. 방식마다 이름이 따로 있습니다. 인가 코드를 거쳐 토큰을 받는 방식은 인가 코드 그랜트입니다.
사람이 끼지 않는 서버끼리의 통신에는 쓰지 않습니다. 부르는 쪽이 자기 아이디와 비밀값만으로 토큰을 받습니다. 이 방식은 클라이언트 자격증명 그랜트라고 부릅니다.
브라우저를 띄우기 어려운 기기도 있습니다. TV나 명령줄 도구가 그렇습니다. 이런 기기는 화면에 짧은 확인 번호를 띄우고 사용자가 휴대폰으로 허락하게 합니다. 이 방식은 기기 인가 그랜트입니다. 확인 번호는 인가 코드와 다른 값입니다.
코드를 거치지 않고 토큰을 주소에 바로 실어 보내는 암묵적 흐름도 그랜트 가운데 하나입니다. 서버가 없는 앱을 위해 만든 방식입니다. 토큰이 주소에 남기 때문에 암묵적 흐름은 이제 쓰지 않기를 권합니다. 서버가 없는 앱도 인가 코드에 PKCE 를 얹어 씁니다.
관련 항목
인가 코드를 내주고 받는 참여자
인가 서버 · 클라이언트 · 자원 서버 · 자원 소유자 · 사용자 에이전트 · 기밀 클라이언트 · 공개 클라이언트
인가 코드를 바꿔 받는 토큰
액세스 토큰 · 리프레시 토큰 · ID 토큰 · Bearer 토큰 · 불투명 토큰 · JWT · 토큰
인가 코드가 거치는 창구와 경로
인가 엔드포인트 · 토큰 엔드포인트 · 리다이렉션 URI · 리다이렉트 · 프런트 채널 · 백 채널 · 동의 화면
인가 코드를 쓰는 표준·프로토콜
OAuth 2.0 · OAuth 2.1 · OpenID Connect · 인가 코드 그랜트 · 인가 코드 흐름 · 인가 그랜트
인가 코드에 덧대는 보호 장치
PKCE · state 파라미터 · nonce · 클라이언트 인증 · 클라이언트 시크릿 · 스코프 · 해시
인가 코드를 노리는 공격
인가 코드 가로채기 공격 · 인가 코드 주입 · 크로스 사이트 요청 위조 · 오픈 리다이렉터 · 토큰 탈취 · 재전송 공격
인가 코드가 새어 나가는 경로
Referer 헤더 · 쿼리 문자열 · 방문 기록 · 접속 로그 · 브라우저
인가 코드 대신 토큰을 받는 다른 그랜트
암묵적 흐름 · 클라이언트 자격증명 그랜트 · 기기 인가 그랜트 · 리소스 소유자 비밀번호 자격증명 그랜트 · 리프레시 토큰 그랜트
인가 코드가 속하는 상위 분류
다른 이름: authorization code · auth code · 권한 부여 코드