인증 플로우
회원가입부터 토큰 관리, 소셜 로그인, 회원탈퇴까지 전체 인증 흐름
엔드포인트 요약
| 메서드 | 경로 | 설명 | 인증 필요 |
|---|---|---|---|
| POST | /auth/signup | 회원가입 | ❌ |
| GET | /auth/verify-email?token= | 이메일 인증 | ❌ |
| POST | /auth/login | 로그인 | ❌ |
| POST | /auth/token | 액세스 토큰 재발급 | ❌ |
| POST | /auth/find-email | 이메일 찾기 | ❌ |
| POST | /auth/password/reset-request | 비밀번호 재설정 요청 | ❌ |
| POST | /auth/password/reset-confirm | 비밀번호 재설정 확인 | ❌ |
| POST | /auth/google | Google 소셜 로그인 | ❌ |
| POST | /auth/kakao | Kakao 소셜 로그인 | ❌ |
| POST | /auth/naver | Naver 소셜 로그인 | ❌ |
| POST | /users/logout | 로그아웃 | ✅ |
| GET | /users/me | 내 정보 조회 | ✅ |
| DELETE | /users/me | 회원탈퇴 | ✅ |
회원가입 → 로그인 플로우
1. POST /auth/signup
2. 이메일 수신 → GET /auth/verify-email?token=...
3. POST /auth/login → accessToken, refreshToken 저장
4. GET /users/me → 내 정보 확인회원가입 요청
POST /auth/signup
{
"email": "user@example.com",
"password": "Abcd1234!",
"passwordConfirm": "Abcd1234!",
"name": "홍길동",
"phone": "01012345678"
}⚠️ 이메일 인증 필수 — 인증 전 로그인 시도하면
403 Forbidden반환합니다./auth/verify-email응답은 JSON이 아닌 HTML 문자열"verification-success"를 반환합니다.
로그인 응답
{
"data": {
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"accessTokenExpiresAt": "2024-01-01T01:00:00",
"refreshTokenExpiresAt": "2024-01-15T00:00:00",
"onboardingRequired": false
}
}
onboardingRequired: true이면 닉네임 미설정 신규 사용자입니다. 소셜 로그인 최초 가입 시 닉네임이 없으므로 프로필 설정 화면으로 유도해야 합니다.
JWT 구현 상세
서버는 HMAC-SHA256 서명 방식으로 JWT를 발급합니다.
accessToken 만료: 1시간 (3,600,000ms)
refreshToken 만료: 14일 (1,209,600,000ms)토큰 페이로드:
sub— 사용자 이메일typ—"access"또는"refresh"(타입 구분용 커스텀 클레임)iat/exp— 발급/만료 시각
리프레시 토큰 보안
서버는 리프레시 토큰 자체를 DB에 저장하지 않고 SHA-256 해시만 저장합니다.
DB: refresh_tokens 테이블
- user_id (unique) — 사용자당 토큰 1개만 유지
- token_hash — SHA-256(refreshToken) 저장
- expires_at
- revoked_at / rotated_at재발급 시 동작:
- 클라이언트가 보낸
refreshToken을 SHA-256 해싱 - DB의
tokenHash와 비교 — 불일치 시401 - 만료/폐기 여부 확인
- 새 accessToken + refreshToken 발급, DB 해시 업데이트 (토큰 로테이션)
POST /auth/token
{ "refreshToken": "eyJ..." }재발급 실패(401) 시 → 로그아웃 처리 후 /login 이동.
소셜 로그인 (Authorization Code Flow)
Google·Kakao·Naver 모두 동일한 방식으로 구현되어 있습니다.
전체 플로우
1. 프론트: Provider 인증 페이지로 리다이렉트
- Google: https://accounts.google.com/o/oauth2/v2/auth
- Kakao: https://kauth.kakao.com/oauth/authorize
- Naver: https://nid.naver.com/oauth2.0/authorize
2. Provider: /auth/{provider}/callback?code=xxx 로 리다이렉트
3. 프론트: 받은 code를 백엔드로 전송
POST /auth/google (또는 /kakao, /naver)
{ "code": "인가코드", "redirectUri": "사용한 콜백 URI" }
4. 백엔드: JWT 발급 후 응답요청 형식
POST /auth/google
{
"code": "4/0AfJohXk...",
"redirectUri": "https://byeoldori-web.vercel.app/auth/google/callback"
}
redirectUri는 Provider에 등록된 값과 정확히 일치해야 합니다. 로컬 개발 시http://localhost:3000/auth/google/callback사용.
응답 형식 (성공)
{
"data": {
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"accessTokenExpiresAt": "2024-01-01T01:00:00Z",
"refreshTokenExpiresAt": "2024-01-15T00:00:00Z",
"onboardingRequired": true
}
}소셜 최초 가입 시
onboardingRequired: true— 닉네임 설정 페이지로 이동해야 합니다.
계정 처리 정책
| 상황 | 처리 |
|---|---|
| 최초 소셜 로그인 | 자동 회원가입 후 JWT 발급, onboardingRequired: true |
| 동일 Provider로 재로그인 | 기존 계정으로 JWT 발급 |
| 같은 이메일로 다른 Provider 로그인 | 409 Conflict 에러 반환 |
| 이메일 미제공 소셜 계정 (Kakao 선택동의 거부 등) | 합성 이메일(kakao.{id}@noemail.byeoldori) 사용, 자동 가입 |
// 다른 Provider 충돌 시
{
"message": "이미 google 계정으로 가입된 이메일입니다."
}비밀번호 재설정
일반 계정(이메일+비밀번호로 가입) 전용입니다. 소셜 로그인 계정은 Provider에서 비밀번호를 관리합니다.
1. POST /auth/password/reset-request { "email": "..." }
→ 이메일로 재설정 링크 발송
2. POST /auth/password/reset-confirm { "token": "...", "newPassword": "..." }
→ 비밀번호 변경 완료이메일 찾기
POST /auth/find-email
{
"name": "홍길동",
"phone": "01012345678"
}응답:
{ "data": { "email": "u***@example.com" } }이름과 전화번호가 일치하는 계정의 이메일을 마스킹하여 반환합니다.
로그아웃
POST /users/logout (Authorization 헤더 필요)서버에서 DB의 refresh_tokens 레코드를 삭제합니다.
클라이언트에서는 localStorage의 토큰도 함께 제거해야 합니다.
회원탈퇴
DELETE /users/me (Authorization 헤더 필요)처리 방식 (소프트 딜리트)
탈퇴 계정은 즉시 삭제되지 않고 익명화됩니다. 커뮤니티 게시글·댓글은 "탈퇴한 사용자" 이름으로 유지됩니다.
| 항목 | 처리 |
|---|---|
| 이메일 | deleted_{id}@byeoldori.deleted 형태로 익명화 |
| 비밀번호 해시 | DELETED 로 덮어쓰기 |
| 전화번호 / 닉네임 / 생년월일 | 삭제 (null 처리) |
| 프로필 이미지 | 삭제 (null 처리) |
| 소셜 연동 정보 | 삭제 (null 처리) |
| 리프레시 토큰 | 즉시 폐기 |
| 게시글 / 댓글 | 유지 (작성자명 "탈퇴한 사용자"로 표시) |
deleted_at | 탈퇴 시각 기록 |
탈퇴 후 동작
- 기존 액세스 토큰으로 API 호출 시
401 Unauthorized반환 - 탈퇴한 이메일로 재가입 불가 (이메일이 익명화되므로 동일 이메일로 재가입 가능)
소셜 로그인 계정도 동일하게 탈퇴 가능합니다.
DELETE /users/me에 액세스 토큰을 담아 호출하면 됩니다.
Rate Limiting
Redis 기반 IP별 요청 제한이 적용되어 있습니다.
| 경로 | 제한 | 시간 |
|---|---|---|
/auth/login | 10회 | 1분 |
/auth/signup | 5회 | 1분 |
/auth/password/reset-request | 3회 | 1시간 |
/auth/google | 10회 | 1분 |
/auth/kakao | 10회 | 1분 |
/auth/naver | 10회 | 1분 |
초과 시 429 Too Many Requests 반환.
API 직접 테스트
회원가입
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/signup" \ -H "Content-Type: application/json" \ -d '{ "email": "string", "password": "stringst", "passwordConfirm": "stringst", "name": "string", "phone": "string" }'{
"success": true,
"message": "string",
"data": {}
}로그인
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/login" \ -H "Content-Type: application/json" \ -d '{ "email": "string", "password": "string" }'{
"success": true,
"message": "string",
"data": {
"accessToken": "string",
"refreshToken": "string",
"accessTokenExpiresAt": "string",
"refreshTokenExpiresAt": "string"
}
}토큰 재발급
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/token" \ -H "Content-Type: application/json" \ -d '{ "refreshToken": "string" }'{
"success": true,
"message": "string",
"data": {
"accessToken": "string",
"refreshToken": "string",
"accessTokenExpiresAt": "string",
"refreshTokenExpiresAt": "string"
}
}아이디(이메일) 찾기
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/find-email" \ -H "Content-Type: application/json" \ -d '{ "name": "string", "phone": "string" }'{
"success": true,
"message": "string",
"data": {
"ids": [
"string"
]
}
}비밀번호 재설정 요청(메일 발송)
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/password/reset-request" \ -H "Content-Type: application/json" \ -d '{ "email": "string", "name": "string", "phone": "string" }'{
"success": true,
"message": "string",
"data": {}
}비밀번호 재설정 확인
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/password/reset-confirm" \ -H "Content-Type: application/json" \ -d '{ "token": "string", "newPassword": "string", "confirmNewPassword": "string" }'{
"success": true,
"message": "string",
"data": {}
}Google 소셜 로그인
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/google" \ -H "Content-Type: application/json" \ -d '{ "code": "string", "redirectUri": "string" }'{
"success": true,
"message": "string",
"data": {
"accessToken": "string",
"refreshToken": "string",
"accessTokenExpiresAt": "string",
"refreshTokenExpiresAt": "string"
}
}Kakao 소셜 로그인
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/kakao" \ -H "Content-Type: application/json" \ -d '{ "code": "string", "redirectUri": "string" }'{
"success": true,
"message": "string",
"data": {
"accessToken": "string",
"refreshToken": "string",
"accessTokenExpiresAt": "string",
"refreshTokenExpiresAt": "string"
}
}Naver 소셜 로그인
Authorization
bearerAuth In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
*/*
curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/naver" \ -H "Content-Type: application/json" \ -d '{ "code": "string", "redirectUri": "string" }'{
"success": true,
"message": "string",
"data": {
"accessToken": "string",
"refreshToken": "string",
"accessTokenExpiresAt": "string",
"refreshTokenExpiresAt": "string"
}
}이메일 인증
Authorization
bearerAuth In: header
Query Parameters
Response Body
*/*
curl -X GET "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/verify-email?token=string""string"curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/users/logout"{
"success": true,
"message": "string",
"data": {}
}curl -X DELETE "https://byeoldori-server-hbxnfn4woa-du.a.run.app/users/me"{
"success": true,
"message": "string",
"data": {}
}