Authentication
회원가입, 로그인, 토큰 흐름과 보안 규칙을 설명합니다.
인증은 짧게 사는 액세스 토큰과 쿠키에 담긴 리프레시 토큰 두 가지로 이루어집니다.
Token strategy
| 토큰 | 저장 위치 | 수명 | 용도 |
|---|---|---|---|
| Access token | 메모리 (Zustand, persist 안 함) | 짧음 | API 요청의 Authorization 헤더 |
| Refresh token | httpOnly 쿠키 | 김 | 액세스 토큰 재발급 |
- 액세스 토큰은 localStorage에 저장하지 않습니다. XSS로 읽히는 것을 막기 위해 메모리에만 둡니다.
- 리프레시 토큰은 httpOnly 쿠키라 자바스크립트에서 읽을 수 없습니다.
/api프록시로 프론트와 백엔드가 같은 도메인처럼 동작하므로 쿠키 방식이 잘 맞습니다.
Sign-up flow
사용자가 회원가입 화면에 정보 입력
↓
프론트에서 입력값 검증 (필수, 이메일 형식, 비밀번호 8자 이상)
↓
POST /auth/signup
↓
서버: 아이디·이메일 중복 확인
↓
서버: 비밀번호 해시 (Argon2) 후 users 저장, 기본 역할 부여
↓
가입 완료 → 프론트가 이어서 자동 로그인가입 화면에는 역할 선택이 없습니다. 일반 사용자가 스스로
MASTER를 지정할 수 없도록, 기본 역할은 항상 서버가 부여합니다.Login & refresh
POST /auth/login이 액세스 토큰을 응답하고 리프레시 쿠키를 설정합니다.- 프론트가 토큰을 스토어에 저장하고,
GET /users/me로 내 정보를 가져옵니다. - 이후 모든 요청에
Authorization: Bearer ...가 자동으로 붙습니다. - 요청이 401을 받으면
POST /auth/refresh로 토큰을 갱신하고 원래 요청을 한 번 재시도합니다. - 새로고침하면
MeerkitProvider가 리프레시 쿠키로 세션을 복원합니다.
단일 갱신
여러 요청이 동시에 401을 받아도 갱신 요청은 한 번만 보냅니다. 나머지는 그 결과를 기다렸다가 새 토큰으로 재시도합니다.
src/meerkit/api/client.ts
let refreshing: Promise<string | null> | null = null;
api.interceptors.response.use(undefined, async (error) => {
const original = error.config;
if (error.response?.status !== 401 || original._retried) {
return Promise.reject(error);
}
original._retried = true;
refreshing ??= refreshAccessToken().finally(() => (refreshing = null));
const token = await refreshing;
if (!token) return Promise.reject(error);
original.headers.Authorization = `Bearer ${token}`;
return api(original);
});Logout
- 사용자가 로그아웃을 누르면
POST /auth/logout으로 서버의 리프레시 토큰을 폐기합니다. - 서버 응답과 관계없이 프론트는 메모리의 액세스 토큰을 지우고 내 정보 캐시를 비웁니다.
- 화면은 로그인 전 상태로 돌아가고, 보호된 화면은 로그인 안내를 보여 줍니다.
Session restore
액세스 토큰은 메모리에만 있어서 새로고침하면 사라집니다. 대신 앱이 시작될 때 리프레시 쿠키로 POST /auth/refresh를 한 번 호출해 로그인 상태를 되살립니다.
| 상황 | 결과 |
|---|---|
| 리프레시 쿠키 유효 | 새 액세스 토큰을 받아 로그인 상태 복원 |
| 쿠키 없음·만료 | 로그인 전 상태로 시작 (오류 표시 없음) |
| 다른 탭에서 로그아웃 | 다음 요청에서 갱신이 실패하며 로그아웃 상태가 됨 |
What the screen shows
| 상황 | 화면 동작 |
|---|---|
| 가입 성공 | 자동 로그인 후 모달이 닫힘 |
| 아이디·이메일 중복 | 폼 아래에 서버 메시지 표시, 모달 유지 |
| 로그인 실패 | 폼 아래에 오류 메시지 표시 |
| 보호된 화면에 비로그인으로 접근 | 로그인 안내와 로그인 버튼 표시 |
| 권한이 없는 화면 | 접근 권한이 없습니다 표시 |
| 비활성화·삭제된 계정 | 로그인 차단 (서버가 거절) |
| 연속 로그인 실패 | 남은 시도 안내 후 한도에 닿으면 잠금과 카운트다운 |
| 약관 미동의 | 필수 약관에 모두 동의해 주세요 오류, 요청은 나가지 않음 |
Password policy
- 프론트는 8자 이상만 검사합니다. 복잡도(대소문자, 숫자, 특수문자) 규칙은 서버 정책으로 정하세요.
- 비밀번호는 입력 후 화면 어디에도 다시 표시하거나 저장하지 않습니다.
- 비밀번호 찾기·재설정과 비밀번호 변경은 제공됩니다 (Password Reset, Profile & Password 참고).
Related features
| 기능 | 설명 | 페이지 |
|---|---|---|
| 로그인 잠금 | n번 연속 실패하면 m분 잠금. 새로고침해도 유지 | Login Lockout |
| 이메일 인증 (선택) | 가입 시 인증번호로 이메일 확인 | Email Verification |
| 비밀번호 재설정 | 이메일 링크로 새 비밀번호 설정 | Password Reset |
| 내 정보·비밀번호 변경 | 로그인한 사용자가 직접 수정 | Profile & Password |
| 약관 동의 | 가입 시 필수·선택 약관 동의 | Terms Agreement |
Not included yet
- 소셜 로그인, 2단계 인증
- 모든 기기에서 로그아웃
- 감사 로그
Security rules
- 비밀번호는 원문을 저장하지 않고 Argon2 같은 안전한 해시로만 저장합니다.
- 응답에
password_hash를 포함하지 않습니다. - 사용자 정보 수정은 본인 또는 허용된 관리자만 가능합니다. 서버에서 검증합니다.
- 다른 사용자의 데이터를 조회하거나 삭제하지 못하도록 서버에서 권한을 확인합니다.
- 계정 탈퇴는 바로 지우지 않고
deactivated_at을 기록해 막습니다. 보관 기간(기본 1년)이 지나면 서버가 개인정보를 지우고deleted_at을 기록합니다. - 시드 관리자 계정은 로컬 테스트 전용이며
.env는 커밋하지 않습니다.
