개발
OAuth 2.0과 OIDC — 인증과 인가의 차이부터 소셜 로그인 원리까지
"로그인은 OAuth로 구현했어요"라는 말을 자주 듣습니다. 그런데 OAuth는 원래 로그인(인증) 프로토콜이 아닙니다.
OAuth와 OIDC를 혼동하는 건 흔한 일입니다. 이 두 개념을 제대로 구분하고 소셜 로그인의 내부 동작을 따라가다 보면 JWT까지 자연스럽게 이어집니다.
인증 vs 인가#
가장 먼저 짚어야 할 개념입니다.
| 구분 | 영어 | 질문 | 예시 |
|---|---|---|---|
| 인가 | Authorization | 너 뭘 할 수 있어? | 파일 접근 권한 |
| 인증 | Authentication | 너 누구야? | 로그인 |
OAuth 2.0은 인가(Authorization) 프로토콜입니다. "이 앱이 내 Google 캘린더에 접근해도 돼"를 처리합니다.
OIDC는 OAuth 2.0 위에 인증(Authentication) 레이어를 추가한 프로토콜입니다. "이 사람이 Google 계정 주인이 맞아"를 처리합니다.
OAuth 2.0 — 위임된 인가#
OAuth 2.0의 핵심은 사용자가 직접 비밀번호를 앱에 주지 않아도 된다는 점입니다.
예를 들어 "Google 캘린더 일정을 읽는 앱"을 만든다고 가정합니다. 예전에는 앱이 구글 아이디/비밀번호를 직접 받아 처리했습니다. OAuth는 이를 개선합니다.
- 사용자가 앱에서 "Google로 연결" 클릭
- Google 로그인 화면으로 리다이렉트
- 사용자가 Google에 직접 로그인 + 권한 허용
- Google이 앱에게 Access Token 발급
- 앱은 이 토큰으로 캘린더 API 호출
앱은 구글 비밀번호를 절대 알 수 없고, 허용된 범위(scope)만 접근할 수 있습니다.
Authorization Code Flow#
아래는 가장 흔하게 쓰이는 OAuth 흐름입니다.
사용자 → 앱: 로그인 요청
앱 → 인가 서버: 리다이렉트 (client_id, scope, redirect_uri, state)
사용자 → 인가 서버: 로그인 + 동의
인가 서버 → 앱: Authorization Code 전달 (redirect_uri로)
앱 → 인가 서버: Authorization Code + client_secret으로 토큰 요청
인가 서버 → 앱: Access Token + Refresh Token 발급
앱 → 리소스 서버: Access Token으로 API 호출
Authorization Code를 한 번 거치는 이유는 Access Token이 브라우저 URL에 노출되지 않도록 하기 위해서입니다.
이때 함께 넘기는 state 값은 CSRF 공격을 막기 위한 장치입니다. 앱이 요청 시 만든 랜덤 값을 인가 서버가 그대로 되돌려주고, 앱은 이 값이 일치하는지 확인해 위조된 콜백을 걸러냅니다.
아래는 Oracle에서 제공하는 Authorization Code Flow 다이어그램입니다.
Access Token과 Refresh Token#
| 토큰 | 역할 | 수명 |
|---|---|---|
| Access Token | API 호출에 사용 | 짧음 (보통 1시간) |
| Refresh Token | Access Token 재발급 | 김 (며칠~수개월) |
Access Token이 만료되면 Refresh Token으로 새 Access Token을 발급받습니다. Refresh Token까지 만료되면 재로그인이 필요합니다.
OIDC — OAuth 위에 인증 레이어 추가#
OAuth 2.0만으로는 "이 토큰의 주인이 누구인지" 알 방법이 없습니다. OIDC는 이 문제를 ID Token으로 해결합니다.
OIDC는 OAuth 2.0 흐름에 ID Token을 추가로 발급합니다. ID Token은 JWT(JSON Web Token) 형식입니다.
Authorization Code Flow + ID Token 발급
JWT — ID Token의 구조#
ID Token(JWT)은 점(.)으로 구분된 세 부분으로 구성됩니다.
header.payload.signature
payload 부분에 사용자 정보(claims)가 담깁니다.
{
"sub": "1234567890",
"name": "Jaejun",
"email": "jaejun@example.com",
"iss": "https://accounts.google.com",
"aud": "my-app-client-id",
"exp": 1714000000,
"iat": 1713996400
}
| claim | 의미 |
|---|---|
sub | 사용자 고유 ID |
iss | 토큰 발급자 |
aud | 토큰 수신 대상 (내 앱) |
exp | 만료 시각 |
여기서 가장 흔히 오해하는 부분이 있습니다. JWT는 암호화(encryption)가 아니라 인코딩(encoding) 입니다. payload는 base64url로 인코딩될 뿐이라 jwt.io 같은 도구에 붙여넣으면 누구나 내용을 그대로 읽을 수 있습니다.
세 번째 조각인 signature가 하는 일은 내용을 숨기는 게 아니라 위변조를 막는 것입니다. 서버는 서명을 검증해 토큰이 발급 후 조작되지 않았는지만 확인합니다.
실무에서는? JWT에 비밀번호·주민번호 같은 민감정보를 넣으면 안 됩니다.
인코딩일 뿐 암호화가 아니므로, 토큰을 가로챈 사람이 곧바로 내용을 읽을 수 있습니다.
PKCE — Authorization Code 탈취 방어#
일반 Authorization Code Flow는 client_secret을 서버에서 안전하게 보관하는 게 전제입니다.
그런데 SPA(React 등)나 모바일 앱은 client_secret을 숨길 방법이 없습니다. 이때 PKCE(Proof Key for Code Exchange) 를 사용합니다.
앱: code_verifier(랜덤 문자열) 생성
앱: code_verifier를 SHA-256 해시한 code_challenge 생성 (method=S256)
앱 → 인가 서버: code_challenge 포함해서 인가 요청
인가 서버 → 앱: Authorization Code 발급
앱 → 인가 서버: Authorization Code + code_verifier로 토큰 요청
인가 서버: code_verifier를 해시해서 code_challenge와 대조 → 일치 시 토큰 발급
client_secret 없이도 Authorization Code 탈취 공격을 막을 수 있습니다.
실무에서는? PKCE는 더 이상 SPA·모바일 전용이 아닙니다.
최신 OAuth 2.1 가이드는client_secret을 안전하게 보관할 수 있는 서버 사이드 앱을 포함해 모든 클라이언트에 PKCE 적용을 권장합니다.
OAuth vs OIDC — 언제 뭘 쓰나#
| 목적 | 프로토콜 |
|---|---|
| 외부 서비스 API 접근 권한 위임 | OAuth 2.0 |
| 소셜 로그인 (사용자 신원 확인) | OIDC |
| 둘 다 | OIDC (OAuth 위에 구현됨) |
"Google로 로그인" 버튼이 있는 서비스는 모두 OIDC를 사용합니다.
실무 — Next.js에서 소셜 로그인 구현#
Next.js에서는 Auth.js(구 NextAuth.js) 를 사용하면 OAuth/OIDC 흐름을 직접 구현하지 않아도 됩니다. 아래는 현재 표준인 Auth.js v5 기준 설정입니다.
// auth.ts
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Google],
})
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers
clientId·clientSecret은 각각 AUTH_GOOGLE_ID·AUTH_GOOGLE_SECRET 환경 변수로 자동 인식되며, 내부적으로 Authorization Code Flow + PKCE를 처리해 줍니다.
정리#
| 개념 | 설명 |
|---|---|
| OAuth 2.0 | 인가 프로토콜 — 권한 위임 |
| OIDC | 인증 프로토콜 — OAuth 2.0 위에 ID Token 추가 |
| Authorization Code Flow | 가장 안전한 OAuth 흐름 — 서버 사이드 앱에 적합 |
| Access Token | API 호출용, 수명 짧음 |
| Refresh Token | Access Token 재발급용, 수명 김 |
| ID Token (JWT) | 사용자 신원 정보 담긴 토큰 (암호화 아닌 인코딩) |
| PKCE | Authorization Code 탈취 방어 — 모든 클라이언트에 권장 |
| Auth.js (NextAuth) | Next.js에서 OAuth/OIDC 구현을 추상화한 라이브러리 |