-
Ory Kratos 로그인 플로우 Deep Dive실제 경험과 인사이트를 AI와 함께 정리한 글 2025. 12. 18. 12:28
작성일: 2025년 12월 18일
카테고리: Authentication, Security, Backend
키워드: Ory Kratos, Self-Service Flow, Session, Cookie, CSRF, Login요약
Ory Kratos의 Self-Service Login Flow는 CSRF 방어, 세션 관리, SPA 지원을 위해 설계된 상태 기반 인증 플로우다. Flow 생성, 로그인 제출, 세션 확인의 3단계로 구성되며, 각 단계에서 보안 검증이 이루어진다. 이 글에서는 로그인 버튼을 누르면 발생하는 모든 요청과 응답을 추적하고, 세션 쿠키의 보안 속성과 에러 처리 방법을 다룬다.
개요
로그인 버튼을 눌렀을 때 뒤에서 무슨 일이 일어나는지 살펴보자.
놀이공원 입장권 발급에 비유하면:
- 번호표 받기: 창구에서 대기 번호표를 받는다 (Flow 생성)
- 신분 확인: 신분증을 보여주고 정보를 입력한다 (로그인 정보 제출)
- 입장권 수령: 확인이 끝나면 팔찌를 받는다 (세션 쿠키 발급)
번호표가 있어야 창구에서 처리가 가능하듯, Kratos도 Flow ID가 있어야 로그인 요청을 처리한다.
왜 Self-Service Flow인가?
방식 설명 장점 단점 전통적 방식 서버가 로그인 폼 렌더링 단순함 CSRF 취약, 유연성 낮음 Self-Service Flow 클라이언트가 Flow 시작, 서버가 상태 관리 보안 강화, SPA 지원 복잡함 Kratos는 Self-Service Flow를 통해:
- CSRF 토큰 자동 생성/검증
- Flow 상태 서버에서 관리 (만료, 재시도 제한)
- SPA/모바일 앱 지원
로그인 플로우 전체 흐름
한눈에 보는 시퀀스
sequenceDiagram participant U as 사용자 participant B as 브라우저 participant F as 프론트엔드 participant K as Kratos U->>B: 로그인 페이지 접속 B->>K: 1. Flow 생성 요청 K-->>B: flow_id + csrf_token B->>U: 로그인 폼 표시 U->>B: 이메일/비밀번호 입력 B->>K: 2. 로그인 요청 K-->>B: Set-Cookie + Redirect B->>F: 3. 대시보드 접속3단계 요약
단계 액션 Kratos 엔드포인트 1 Flow 생성 GET /self-service/login/browser2 로그인 제출 POST /self-service/login3 세션 확인 GET /sessions/whoami단계별 상세 분석
1단계: Flow 생성
사용자가
/login페이지에 접속하면 Flow가 생성된다.요청
GET /self-service/login/browser HTTP/1.1 Host: auth.example.com Accept: application/json응답
{ "id": "f8e2f3a1-...", "type": "browser", "expires_at": "2024-01-15T10:30:00Z", "issued_at": "2024-01-15T10:00:00Z", "request_url": "https://app.example.com/login", "ui": { "action": "https://auth.example.com/self-service/login", "method": "POST", "nodes": [ { "type": "input", "group": "default", "attributes": { "name": "csrf_token", "type": "hidden", "value": "abc123..." } }, { "type": "input", "group": "password", "attributes": { "name": "identifier", "type": "text" } }, { "type": "input", "group": "password", "attributes": { "name": "password", "type": "password" } } ] } }Flow 구조 이해
필드 설명 id고유 식별자 ( "f8e2f3a1-...")expires_at30분 후 만료 ui.actionPOST 대상 URL ui.nodes폼 필드 목록 csrf_tokenhidden 필드 (CSRF 방어) identifier이메일 입력 필드 password비밀번호 입력 필드 Flow는 30분 후 만료된다. 만료된 Flow로 로그인 시도하면 새 Flow를 생성해야 한다.
2단계: 로그인 제출
사용자가 이메일/비밀번호를 입력하고 제출한다.
요청
POST /self-service/login?flow=f8e2f3a1-... HTTP/1.1 Host: auth.example.com Content-Type: application/json { "method": "password", "csrf_token": "abc123...", "identifier": "user@example.com", "password": "secretPassword123" }성공 응답
HTTP/1.1 200 OK Set-Cookie: ory_kratos_session=MTcwNTMxMjAwMHx...; Path=/; HttpOnly; Secure; SameSite=Lax { "session": { "id": "session-uuid-...", "active": true, "expires_at": "2024-01-16T10:00:00Z", "identity": { "id": "user-uuid-...", "traits": { "email": "user@example.com", "name": "홍길동" } } } }세션 쿠키 분석
속성 값 설명 쿠키명 ory_kratos_sessionKratos 세션 식별자 Path/모든 경로에서 전송 HttpOnly설정됨 JS에서 접근 불가 (XSS 방어) Secure설정됨 HTTPS에서만 전송 SameSiteLax크로스사이트 제한 (CSRF 방어) 3단계: 세션 확인 (whoami)
로그인 후 API 요청 시 세션을 확인한다.
sequenceDiagram participant B as 브라우저 participant API as API 서버 participant K as Kratos B->>API: GET /api/resources<br/>Cookie: ory_kratos_session API->>K: GET /sessions/whoami<br/>Cookie 전달 K-->>API: Session + Identity API->>API: 권한 체크 API-->>B: 리소스 목록whoami 요청
GET /sessions/whoami HTTP/1.1 Host: auth.example.com Cookie: ory_kratos_session=MTcwNTMxMjAwMHx...whoami 응답
{ "id": "session-uuid-...", "active": true, "expires_at": "2024-01-16T10:00:00Z", "authenticated_at": "2024-01-15T10:00:00Z", "identity": { "id": "user-uuid-...", "schema_id": "default", "traits": { "email": "user@example.com", "name": "홍길동" }, "created_at": "2024-01-01T00:00:00Z" } }세션과 쿠키
왜 세션과 쿠키가 필요한가?
HTTP는 무상태(stateless) 프로토콜이다. 매 요청마다 서버는 "이 사람 누구지?"를 다시 확인해야 한다.
호텔 객실 카드키에 비유하면:
- 체크인할 때 신분증 확인 후 카드키(세션 쿠키) 발급
- 이후에는 카드키만 대면 객실 출입 가능
- 체크아웃하면 카드키가 비활성화됨
매번 신분증을 보여주지 않아도 되는 것처럼, 세션 쿠키가 있으면 매 요청마다 비밀번호를 입력하지 않아도 된다.
세션 수명 주기
stateDiagram-v2 [*] --> Active: 로그인 성공 Active --> Active: API 요청 (갱신) Active --> Expired: 24시간 경과 Active --> Revoked: 로그아웃 Expired --> [*] Revoked --> [*]세션 설정
항목 값 설명 쿠키 이름 ory_kratos_sessionKratos 기본값 유효 기간 24시간 설정 가능 HttpOnly 설정됨 XSS 방어 Secure 설정됨 HTTPS 필수 SameSite Lax CSRF 방어 세션 vs 토큰 비교
항목 세션 (Kratos) JWT 저장 위치 서버 (DB) 클라이언트 취소 가능 즉시 만료까지 대기 크기 작음 (쿠키 ID만) 큼 (payload 포함) 용도 웹 브라우저 API/모바일 일반적으로 웹 대시보드는 세션, API/모바일은 JWT를 사용한다.
API 요청 시 인증
인증 미들웨어 구현 (Go)
func (m *AuthMiddleware) Authenticate() gin.HandlerFunc { return func(c *gin.Context) { // 1. 쿠키에서 세션 추출 cookie, err := c.Cookie("ory_kratos_session") if err != nil { c.AbortWithStatusJSON(401, gin.H{ "error": "unauthorized", }) return } // 2. Kratos whoami 호출 session, err := m.kratosClient.ToSession( c.Request.Context(), cookie, ) if err != nil { c.AbortWithStatusJSON(401, gin.H{ "error": "session_invalid", }) return } // 3. 컨텍스트에 사용자 정보 저장 c.Set("user_id", session.Identity.Id) c.Set("email", session.Identity.Traits["email"]) c.Next() } }인증 흐름 다이어그램
flowchart TB A[API 요청] --> B{쿠키 있음?} B -->|No| C[401 Unauthorized] B -->|Yes| D[Kratos whoami] D --> E{세션 유효?} E -->|No| C E -->|Yes| F[컨텍스트에 저장] F --> G[핸들러 실행] style C stroke:#dc2626,stroke-width:2px style G stroke:#16a34a,stroke-width:2px로그아웃 플로우
로그아웃도 Self-Service Flow를 따른다.
로그아웃 시퀀스
sequenceDiagram participant B as 브라우저 participant K as Kratos B->>K: 1. GET /self-service/logout/browser K-->>B: logout_token B->>K: 2. GET /self-service/logout?token=... K-->>B: Set-Cookie (삭제) + Redirect단계별 요청
1단계: 로그아웃 URL 요청
GET /self-service/logout/browser HTTP/1.1 Cookie: ory_kratos_session=...{ "logout_url": "https://auth.example.com/self-service/logout?token=xyz123", "logout_token": "xyz123" }2단계: 로그아웃 실행
GET /self-service/logout?token=xyz123 HTTP/1.1HTTP/1.1 303 See Other Set-Cookie: ory_kratos_session=; Max-Age=0; Path=/ Location: https://app.example.com/loginMax-Age=0으로 쿠키가 즉시 삭제된다.에러 처리
일반적인 에러 케이스
에러 원인 해결 session_inactive세션 만료 재로그인 csrf_violationCSRF 토큰 불일치 Flow 재생성 flow_expiredFlow 30분 초과 Flow 재생성 credentials_invalid잘못된 비밀번호 재입력 에러 응답 예시
{ "error": { "code": 400, "status": "Bad Request", "reason": "The provided credentials are invalid.", "message": "The provided credentials are invalid, check for spelling mistakes in your password or username." }, "ui": { "messages": [ { "id": 4000006, "type": "error", "text": "The provided credentials are invalid." } ] } }프론트엔드 에러 처리 (TypeScript)
async function handleLogin(email: string, password: string) { try { const response = await kratos.submitSelfServiceLoginFlow({ flow: flowId, updateLoginFlowBody: { method: 'password', identifier: email, password: password, csrf_token: csrfToken, }, }); // 성공 → 대시보드로 이동 router.push('/dashboard'); } catch (error) { if (error.response?.status === 400) { // UI 메시지 표시 const messages = error.response.data.ui?.messages; setError(messages?.[0]?.text || '로그인 실패'); } else if (error.response?.status === 410) { // Flow 만료 → 새 Flow 생성 await initializeLoginFlow(); } } }결론
로그인 플로우 체크리스트
순서 항목 1 Flow 생성 ( GET /self-service/login/browser)2 폼 렌더링 ( ui.nodes기반)3 CSRF 토큰 포함하여 제출 4 세션 쿠키 수신 확인 5 whoami로 세션 유효성 확인핵심 요약
항목 내용 Flow 시작 GET /self-service/login/browser로그인 제출 POST /self-service/login?flow={id}세션 확인 GET /sessions/whoami세션 유효기간 24시간 (설정 가능) 쿠키 속성 HttpOnly, Secure, SameSite=Lax 보안 체크포인트
위협 방어 메커니즘 XSS HttpOnly 쿠키 CSRF csrf_token + SameSite 세션 탈취 Secure (HTTPS) 브루트포스 Rate Limiting (Kratos 내장) 참고 자료
공식 문서
관련 자료
'실제 경험과 인사이트를 AI와 함께 정리한 글' 카테고리의 다른 글
GORM 소개: Go 개발자를 위한 ORM 완벽 가이드 (0) 2025.12.22 Claude Code로 세련된 UI 만들기 (0) 2025.12.18 Ory 스택 완전 가이드: 오픈소스 인증/인가 시스템 (0) 2025.12.18 Kubernetes Gateway API 설계 철학: Ingress를 넘어서 (0) 2025.12.18 Kubernetes Operator 패턴과 Reconciliation: 선언적 인프라의 핵심 (0) 2025.12.18