별도리 Dev Docs
연동 가이드

인증 플로우

회원가입부터 토큰 관리, 소셜 로그인, 회원탈퇴까지 전체 인증 흐름

엔드포인트 요약

메서드경로설명인증 필요
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/googleGoogle 소셜 로그인
POST/auth/kakaoKakao 소셜 로그인
POST/auth/naverNaver 소셜 로그인
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

재발급 시 동작:

  1. 클라이언트가 보낸 refreshToken을 SHA-256 해싱
  2. DB의 tokenHash와 비교 — 불일치 시 401
  3. 만료/폐기 여부 확인
  4. 새 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/login10회1분
/auth/signup5회1분
/auth/password/reset-request3회1시간
/auth/google10회1분
/auth/kakao10회1분
/auth/naver10회1분

초과 시 429 Too Many Requests 반환.


API 직접 테스트

회원가입

POST
/auth/signup

Authorization

bearerAuth
AuthorizationBearer <token>

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": {}
}

로그인

POST
/auth/login

Authorization

bearerAuth
AuthorizationBearer <token>

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"
  }
}

토큰 재발급

POST
/auth/token

Authorization

bearerAuth
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

refreshToken*string

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"
  }
}

아이디(이메일) 찾기

POST
/auth/find-email

Authorization

bearerAuth
AuthorizationBearer <token>

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"
    ]
  }
}

비밀번호 재설정 요청(메일 발송)

POST
/auth/password/reset-request

Authorization

bearerAuth
AuthorizationBearer <token>

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": {}
}

비밀번호 재설정 확인

POST
/auth/password/reset-confirm

Authorization

bearerAuth
AuthorizationBearer <token>

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 소셜 로그인

POST
/auth/google

Authorization

bearerAuth
AuthorizationBearer <token>

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 소셜 로그인

POST
/auth/kakao

Authorization

bearerAuth
AuthorizationBearer <token>

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"
  }
}
POST
/auth/naver

Authorization

bearerAuth
AuthorizationBearer <token>

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"
  }
}

이메일 인증

GET
/auth/verify-email

Authorization

bearerAuth
AuthorizationBearer <token>

In: header

Query Parameters

token*string

Response Body

*/*

curl -X GET "https://byeoldori-server-hbxnfn4woa-du.a.run.app/auth/verify-email?token=string"
"string"

로그아웃 (리프레시 토큰 제거)

POST
/users/logout

Authorization

bearerAuth
AuthorizationBearer <token>

In: header

Response Body

*/*

curl -X POST "https://byeoldori-server-hbxnfn4woa-du.a.run.app/users/logout"
{
  "success": true,
  "message": "string",
  "data": {}
}

회원 탈퇴

DELETE
/users/me

Authorization

bearerAuth
AuthorizationBearer <token>

In: header

Response Body

*/*

curl -X DELETE "https://byeoldori-server-hbxnfn4woa-du.a.run.app/users/me"
{
  "success": true,
  "message": "string",
  "data": {}
}

On this page