[DIP] Session, JWT, OAuth

auth

사내 서비스에서 사용자 인증 방식을 우회하는 방법이 커뮤니티에 확산되며 문제가 된적이 있었다. 이 사태에 대한 인증 방식 부분을 살펴보며 지금껏 회사 내 개발을 진행해 오며 로그인 및 인증에 대한 처리를 직접 바닥부터 구현하거나 고민을 한 적은 없었다는 것이 찝찝함으로 자리 잡았다. 대부분 사내 제공되는 로그인 시스템 기반으로 서비스 개발을 진행했기 때문이다. 위 문제를 확인하며 사내 로그인 인증 방식에 대한 새로운 인사이트를 얻을 수 있지 않을까 하는 생각에 기본적으로 많이 사용되는 로그인 인증 처리 방식들을 정리해 보고자 한다.


여러 로그인 인증 처리가 있겠지만 가장 많이 사용된다고 생각되는 Session, JWT, OAuth 세 가지 방식에 대해서 각각 기본적인 정보들을 파헤쳐 보겠다.

Web-Auth-1



Session

세션을 통한 로그인 인증 처리는 사용자의 ID/PW를 통해 인증 성공 시 서버에서 세션을 생성하고 인증 상태를 서버 메모리에 저장한다. 그리고 생성된 세션 ID를 클라이언트에 쿠키로 발급하여 인증을 유지하는 방식이다.

쿠키로 설정된 세션 ID를 통해 클라이언트에서 이후 요청은 브라우저를 통해 자동으로 쿠키에 포함되어 서버에서 유효한 인증 여부를 판단하는 역할을 하게 된다.



구성요소

구성 요소설명
세션 ID (Session ID)서버에서 발급하는 고유한 문자열, 클라이언트가 쿠키에 저장
세션 객체사용자 ID, 권한, 로그인 시간, 만료 시간 등의 정보를 담음
세션 저장소메모리(local), Redis, DB 등에 저장됨. 서버 확장 시 공유 필요
Set-Cookie 헤더로그인 성공 시 서버가 세션 ID를 클라이언트에게 전달하는 수단
쿠키 설정값HttpOnly, Secure, SameSite 등으로 보안 제어


인증 플로우

docker
[사용자] │ ▼ [로그인 요청 (ID/PW)] │ ▼ [서버: 인증 성공 → 세션 객체 생성] │ ▼ [서버: 세션 ID 생성 → Set-Cookie 응답] │ ▼ [브라우저: 쿠키 저장 (세션 ID)] │ ▼ [API 요청 시 쿠키 자동 포함] │ ▼ [서버: 세션 ID로 사용자 인증 확인]


고려사항

구현 고려 사항으로 인증 상태가 필요한 API 요청인 경우 도메인이 다른 서버로 요청 시 세션 ID가 포함된 쿠키를 함께 보내기 위해 withCredentials: true 옵션이 필요하다. 또한 쿠키가 API 요청마다 자동으로 전송되는 상황으로 CSRF 공격에 대한 대응이 필요하다.

CSRF (Cross-Site Request Forgery) 사이트간 위조 요청으로 의도하지 않은 요청을 인증된 상태로 수행하게 하는 공격


기본적으로 다른 도메인으로 전송을 방지하기 위한 SameSite 옵션을 Strict, Lax로 설정하여 방지한다. 만약 도메인이 다른 서버와의 통신이 필요한 경우 CSRF 방어를 위한 별도의 Token을 서버에서 생성하고 추가로 2차 검증을 하는 과정을 거쳐 CSRF 방어에 사용한다.

SameSite: Strict 설정시 cross-site 요청시 쿠키 전송 되지 않음
SameSite: Lax 설정시 일부 요청 GET, HEAD, OPTIONS에 대해서 쿠키 전송 허용


추가로 쿠키 탈취 방지를 위해 스크립트로 쿠키에 접근할 수 없도록 HttpOnly 설정과 HTTPS 통신에서만 쿠키가 전송되도록 Secure 설정을 해야 한다. 마지막으로 쿠키가 유효한 도메인과 경로 범위를 지정하여 제한된 범위에서 쿠키가 전송되도록 처리 할 수 있다.



JWT

JWT는 JSON Web Token을 사용한 사용자 인증 방식으로 사용자가 ID/PW를 통해 로그인 성공 시 서버에서 JWT 토큰을 발급하고 클라이언트로 응답해 준다.

클라이언트에서는 전달받은 토큰을 저장하고 이후 API 요청 시 Authorization 헤더에 포함하고 서버에서는 포함된 토큰으로 인증 여부를 검증한다.



구성요소

구성 요소설명
Access Token인증 및 권한 검증용 토큰, 만료 시간 짧음
Refresh TokenAccess Token 갱신 용도, 만료 시간 긺
Payload사용자 ID, 권한, 로그인 시간, 만료 시간 등의 정보를 포함
Signature서버의 비밀키로 HMAC 또는 RSA 서명
저장 위치localStorage, sessionStorage, 메모리, HttpOnly 쿠키 등 다양


인증 플로우

docker
[사용자] │ ▼ [로그인 요청 (ID/PW)] │ ▼ [서버: 인증 성공 → JWT Access + Refresh Token 발급] │ ▼ [클라이언트: 토큰 저장 (localStorage, memory, 쿠키 등)] │ ▼ [API 요청 시 Authorization: Bearer <AccessToken>] │ ▼ [서버: 토큰 서명 및 만료 시간 검증 → 인증] │ └───→ [Access Token 만료됨?] │ ├─ 아니오 → 인증 성공 │ └─ 예 → Refresh Token으로 토큰 재발급 요청 │ ▼ [새로운 Access Token 반환]


고려사항

JWT 구현 시 고려 사항으로는 토큰 저장 시 XSS 취약한 localStorage보다는 메모리 또는 HttpOnly 쿠키로 관리되는 방식이 권장된다. JWT 사용자 인증 방식은 서버에서 상태가 관리 되지 않기 때문에 토큰이 탈취되는 경우 서버에서는 탈취 여부 판단이 어려울 수 있다. 해서 추가적 보안을 위해 토큰을 서버 서명에 포함하여 위조를 방지한다.

서버 서명(Signature): JWT 내용이 위조 되지 않았다는것을 보장하기 위한 디지털 서명
서버에 저장된 비밀키를 이용해 서명을 생성 후 JWT Signature에 사용
JWT 구조: [Base64(Header)].[Base64(Payload)].[Signature]


추가로 Access Token에 대한 만료 시간은 짧게 설정하고 세션에서와 동일하게 HttpOnly, Secure 옵션 등을 통한 보안 강화가 필수적이다.



OAuth

OAuth(Open Authorization)는 인증에 대한 처리를 외부에 위임하는 방식이다. 사용자가 본인을 인증할 수 있는 인가 서버(Google, Kakao 등)를 선택하여 인증 후 인증 코드를 받아 서버를 통해 Access Token으로 교환한다.

서버로부터 토큰을 전달받은 클라이언트는 해당 토큰으로 인증 여부를 검증받아 통신하고 인가 서버로부터 정보를 전달받아 사용한다.



구성요소

구성 요소설명
Client ID / SecretOAuth 앱 등록 시 발급되는 식별자
Authorization Code인증 후 클라이언트에 전달되는 임시 코드
Access Token / Refresh Token인증 완료 후 교환하여 사용
Redirect URI인증 성공 후 리디렉션될 주소
State요청의 정당성을 검증하는 임의 문자열 (CSRF 방지용)
PKCEpublic client(SPA, 모바일 앱)용 보안 강화 메커니즘 (code_verifier / code_challenge)

PKCE(Proof Key for Code Exchange): OAuth 2.0 보안 확장 기능으로 인증 코드 보안 취약점 보안
Authorization Code 교환시 무작위 키를 랜덤하게 생성하여 서버와 추가적인 검증키로 사용
code_verifier: 클라이언트가 임의로 생성한 랜덤 문자열 (보통 43~128자)
code_challenge: 서버에 저장할 code_verifier를 변환한 값 (SHA256 해시 후 Base64 URL 인코딩)


인증 플로우

docker
[클라이언트] │ ▼ [OAuth 인증 요청 → Provider로 리디렉션] │ ▼ [사용자: 로그인 + 권한 동의] │ ▼ [OAuth 서버 → Redirect URI로 code 전달] │ ▼ [클라이언트 → 서버에 Authorization Code 전달] │ ▼ [서버: code + verifier로 Access Token 요청] │ ▼ [OAuth 서버: Access Token (및 Refresh Token) 응답] │ ▼ [클라이언트: Access Token 저장] │ ▼ [API 호출 시 Authorization: Bearer <AccessToken>]


고려사항

OAuth를 사용한 방식은 다른 인증 방식에 비해 다소 복잡한 플로우를 고려하여 보안에 신경 써야 한다. 처음 OAuth 인증 서버를 통한 인증 페이지 처리를 하게 된다. 로그인 이후 리다이렉션 URL에서 전달받은 code를 백엔드에 전달하는 부분에서 클라이언트에서 랜덤하게 생성한 state를 활용하여 redirect가 위조되었는지 확인하는 과정이 필요하다. 그리고 서버로 Authorization Code를 전달 시 PKCE 처리를 통하여 인증 코드 탈취를 방지한다.

그리고 인증 코드로 생성된 토큰은 다른 인증 방식에서 설명했던 것과 동일하게 HttpOnly, Secure 등과 같은 옵션을 통해 보다 안전한 방식으로 토큰이 사용되도록 처리하는 것이 좋다.



샘플 구현

위 정리된 내용을 기반으로 Session, JWT, OAuth를 실제로 간단한 데모로 구현하며 정리된 내용을 한번 더 돌아보려고한다. 데모 구현으로 Node 서버내에서 인증 로그인 처리를 수행하고 세션 및 토큰을 관리하도록 하였고, OAuth의 경우 설정이 간단하고 무료로 사용 가능한 Github OAuth로 구현해 보았다.


Session

🔐 Session 인증 데모 (실제 서버 통신)

💡 실제 동작 원리:
1. 로그인 시 Next.js 서버에서 세션 ID를 생성하고 메모리에 저장
2. HttpOnly, Secure, SameSite=Strict 옵션으로 쿠키 설정
3. 세션 ID는 JSON 응답에 포함되지 않고 오직 HttpOnly 쿠키로만 전달 (XSS 방어)
4. 이후 모든 API 요청에 쿠키가 자동으로 포함 (credentials: 'include')
5. 서버에서 쿠키의 세션 ID로 사용자 인증 상태 확인
6. 로그아웃 시 서버의 세션 삭제 및 쿠키 제거
✅ (실제 Next.js 서버 API Routes를 사용한 보안 구현입니다)



JWT

🔐 JWT 인증 데모 (실제 서버 통신)

💡 실제 동작 원리:
1. 로그인 시 서버에서 Access Token (15분)과 Refresh Token (7일) 발급
2. Access Token은 메모리에 저장, Refresh Token은 HttpOnly 쿠키에 저장
3. API 요청 시 Authorization: Bearer 헤더에 Access Token 포함
4. 서버에서 토큰 서명을 검증하여 인증 (HMAC SHA256)
5. Access Token 만료 시 Refresh Token으로 새로운 Access Token 발급
6. 로그아웃 시 Access Token을 블랙리스트에 추가하고 Refresh Token 쿠키 삭제
✅ (실제 Next.js 서버 API Routes를 사용한 JWT 구현입니다)



OAuth

🔐 OAuth 인증 데모 (GitHub + PKCE)

ℹ️ OAuth 로그인 방식

- GitHub 계정으로 로그인합니다
- State를 통한 CSRF 방어
- PKCE를 통한 Authorization Code 보안
- 인증 후 자동으로 이 페이지로 돌아옵니다

💡 실제 동작 원리:
1. 로그인 버튼 클릭 → 서버에서 State와 PKCE Code Verifier/Challenge 생성
2. GitHub 인증 페이지로 리디렉션 (State와 Code Challenge 포함)
3. 사용자가 GitHub에서 로그인 및 권한 승인
4. GitHub가 Authorization Code와 State를 Callback URL로 전달
5. 서버에서 State 검증 (CSRF 방어) 및 Code Verifier로 Token 교환 (PKCE)
6. Access Token으로 GitHub API에서 사용자 정보 조회
7. 서버 세션 생성 및 HttpOnly 쿠키로 저장
✅ (실제 GitHub OAuth API를 사용한 구현입니다)



인증이란?

서비스에서의 인증 처리는 사용자에게 신뢰를 줄 수 있는 중요한 지표 중 하나다. 다양한 방식의 인증 방법이 존재하지만, 각 서비스에 적합한 인증 방식을 명확히 이해하고 도입하는 것이야말로 신뢰를 쌓는 출발점이 아닐까 생각한다.

이미 검증된 인증 방식을 활용하는 것도 좋은 접근이지만, 우리 서비스 내에 혹시 놓치고 있는 보안 취약점은 없는지 항상 점검해야 한다. 그래야만 사용자에게 더욱 신뢰받는 서비스를 만들어갈 수 있을 것이다.

이번 내용을 정리하며, 우리가 놓친 부분은 없는지 다시 돌아보고, 프론트엔드에서 필요한 보안 처리뿐 아니라 서버 측의 보안 이슈도 함께 살펴보는 것이 중요하다고 느꼈다. 프론트 개발자로서의 역할에만 머무르지 않고, 서비스 전반의 보안을 함께 고민하고 챙기는 태도가 앞으로 더 중요해질 것이라고 생각한다.

Web-Auth-2

"Trust is built in very small moments." - Brené Brown -