[BE] 인증 인가 설계 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki
배경 (Background)
프로젝트 목표
Devths의 인증/인가 아키텍처 및 플로우를 구축, 사용자가 서비스를 정상적으로 사용할 수 있는 기반을 제공한다.
로그인/회원가입/토큰 재발급/로그아웃이 안정적으로 동작한다.
인증이 필요한 API는 인증되지 않은 요청을 차단하고(401), 권한이 없는 요청을 차단한다(403).
AT 만료 시에도 RT 기반으로 사용자 경험이 끊기지 않는다.
XSS/CSRF 등 웹 보안 위협을 고려한 토큰 저장/전송 전략을 적용한다.
RT Rotation으로 RT 탈취 재사용을 탐지/차단한다.
문제 정의
Devths는 Google OAuth2를 통해 외부 사용자 신원을 확인하지만, 서비스 고유의 사용자 정보와 JWT 기반 AT/RT 체계를 별도로 운용한다. 따라서 외부 식별자(provider_user_id=sub)와 내부 사용자(userId)의 매핑 정합성, 신규 유저 온보딩(tempToken), 토큰 저장/전송 전략(CORS, CSRF), 그리고 Google 리소스 접근을 위한 Google AT/RT 안전 저장 및 갱신 정책을 함께 설계해야 한다.
목표가 아닌 것 (Non-goals) (Optional)
해당 문서에서는 Devths의 인증 및 인가 아키텍처에 대한 내용만 다룬다.
회원 관련 ERD, API 명세에 관한 내용은 본 서비스의 ‘유저’ 도메인 테크 스펙에 포함한다.
설계 및 기술 자료 (Architecture and Technical Documentation)
인증/인가 설계
사용자 식별 방식
외/내부 식별자
외부(OAuth Provider) : Google
식별 키 : provider_user_id (id_token의 subject)
서명/iss/aud/exp/iat 검증 후 sub 사용
내부(Devths)
식별 키 : userId (users 테이블의 PK)
토큰 기반 식별(JWT)
JWT 채택 이유
서버가 사용자의 상태를 기억하지 않아도 되므로 RESTful API 기반의 Stateless 아키텍처에 적합
추후 다중 인스턴스 환경으로 확장 시 Scale-Out이 용이(세션 동기화 불필요)
Access Token(AT): 요청 당 사용자 식별용(TTL 30분)
클레임
sub: 내부 식별자(userId)
roles: [ROLE_USER, ...]
iat, exp
Refresh Token(RT): AT 재발급용(TTL 14일), Refresh Token Rotation 정책 적용
tempToken: 회원가입 전 신규 사용자를 식별하기 위한 초단기 임시 토큰(TTL 10분)
서비스 계정 생성 전 단계이므로 권한 범위를 POST /api/users (회원가입)으로 최소화
회원가입 성공 시 tempToken 폐기(재사용 불가)
사용자 역할 및 권한
역할(Role)
ROLE_USER: 기본 사용자
ROLE_ADMIN: 운영자 및 시스템 관리자
알림 발송 요청 등 시스템 관련 기능에 접근할 수 있음
권한(Authorization) 정책
인증 필요 엔드 포인트 : 기본적으로 /api/**는 모두 인증이 필요하나, 아래는 예외
POST /api/auth/google: 소셜 로그인
POST /api/auth/tokens: 액세스 토큰 재발급(단, 유효한 RT 필요)
POST /api/users: 회원가입(단, 유효한 tempToken 필요)
보안성 확보를 위한 기타 설정
Refresh Token 저장 위치 및 상태 관리 방식
서버 응답(회원가입, 로그인, 토큰 재발급) 시 Set-Cookie로 내려주고, 필요 시 Response Cookie에 담아 전송
서버 DB에 사용자 별 RT를 저장하여 상태 관리
초기 RDB 테이블, 아키텍처 고도화 시 Redis
Cookie 설정
HttpOnly: XSS 공격 방지
Secure: HTTPS에서만 전송
SameSite=Lax: CSRF 완화(단, 크로스사이트 사용 시 조정 필요)
Path=/api/auth: RT가 필요한 엔드포인트 범위로만 전송(노출 최소화)
Max-Age/Expires: RT의 TTL과 일치
다중 디바이스 미허용
로그아웃 및 탈퇴 시 처리
서버 저장소의 RT 폐기 및 현재 쿠키 만료(Set-Cookie Max-Age=0)
Access Token 저장 위치 및 전달 방식
클라이언트 저장 방식 명시
프론트는 AT를 메모리(in-memory)에 저장하고, 토큰 만료 시 또는 새로고침 시 /api/auth/tokens로 재발급
헤더 표준
요청: Authorization: Bearer {accessToken}
응답: Authorization: Bearer {accessToken}
브라우저에서 읽기 위해 Access-Control-Expose-Headers: Authorization
서버에는 별도로 저장하지 않음
TTL 정책
만료 시간을 30분으로 두고 RTR 기반 재발급
RTR(Refresh Token Rotation) 정책
/api/auth/tokens 성공 시: 새 RT 발급 + 기존 RT 폐기
새 RT는 DB 저장소에도 원자적으로 업데이트
RT 재사용 탐지 시 대응 정책
이미 폐기된 RT가 다시 오면 탈취 가능성 존재로 판단
해당 사용자(또는 해당 디바이스)의 모든 RT 폐기 + 강제 재로그인으로 대응
동시성 제어
재발급 API가 동시에 두 번 호출되면 레이스 발생 가능
user_id 단위 락
Google AT/RT 저장 여부
사용자 구글 리소스(Google Calendar API, Google Tasks API)에 접근해야 하므로 Google AT/RT를 서버 DB에 저장
저장 목적/범위
서버가 사용자 대신 Google Calendar/Tasks API 호출
캘린더 CRUD 및 To-do CRUD 기능에 사용
AT 저장 정책 재검토
AT는 초기 RDB에 저장, 추후 Redis로 이관
토큰 갱신 동시성
Google 토큰 갱신도 동시 요청 경쟁 가능 → provider_user_id 단위 락
권한 철회(Revoked) 대응
Google에서 권한 철회/연동 해제 시 invalid_grant 발생
이 경우 재시도 멈추고 status=REVOKED, 사용자에게 “재연동 필요” 안내
HTTPS
전 구간 HTTPS 강제
RT가 Secure 쿠키로 전달됨
CSRF 방어
위협 모델
AT는 헤더로 보내므로 CSRF 영향 적음(공격자가 헤더를 임의로 못 붙임)
그러나 RT는 쿠키이므로 브라우저가 자동 전송하므로 /api/auth/tokens, /api/auth/logout이 CSRF 대상이 될 수 있음
1차 방어: SameSite
SameSite=Lax로 대부분의 Cross-Site POST를 줄임
단, 프론트엔드/백엔드가 서로 다른 Site라면 조정 필요
2차 방어 : CSRF 토큰
재발급/로그아웃 같은 RT 쿠키 기반 엔드포인트에만 적용 고려
인증 프로세스
참여자 및 책임
User: Google 로그인 버튼을 통해 소셜 로그인을 시작
Frontend: Google 인증 UI 처리 및 authCode를 수신 후 Devths 로그인 API 호출