Login
패턴 정본 1호 — WDS 컴포넌트를 조합한 슬롯형 인증 표면. 이메일·비밀번호를 기본으로 패스키·SSO·소셜 로그인·MFA·복구·패스키 등록 권유를 정책에 따라 선택합니다.
마지막 업데이트 2026-07-02
이 패턴은 **화면(UI)**을 정의합니다. 버튼들을 실제 인증에 연결하는 법(패스키 등록→로그인 생애주기·웹 vs 네이티브 OS 호출·SSO·배선 레시피)은 인증 연동을 참고하세요.
AI / Consumer Implementation Contract#
AI 작업자와 소비 프로젝트는 아래 계약을 먼저 적용합니다. 세부 설명보다 이 섹션과
patterns/login.contract.json을 우선합니다.
React 테스트에서는 @wds/ui-web/testing의 assertLoginPatternCompliance(preset, screen)으로 같은 계약을 검증합니다.
MUST#
- 단일 패널 — 로그인은
LoginSurface하나로 구성합니다. 전체 화면·모달·사이드 패널 어디에 넣어도 인증 표면은 하나입니다. - 최대 폭
390px—LoginSurface size="sm"을 사용합니다. 인증 표면 자체는 데스크탑에서도390px를 넘지 않습니다. - 정렬 고정 — 브랜드·제목·설명은 중앙 정렬, 입력 라벨·필드·오류는 좌측 정렬입니다.
- 입력 배선 —
FormField와Input을 조합해 보이는 라벨,htmlFor/id,aria-describedby,aria-invalid를 유지합니다. - 타이포 위계 — 제목, primary CTA, 방식 버튼은
medium; 설명·footer는regular기준입니다. - Primary CTA는 하나 — 한 로그인 표면 안의 채움 primary action은 1개만 둡니다.
- 방식 버튼 위계 — passkey/SSO는
AuthMethodButton, Apple/Google/Microsoft는ProviderButton으로 풀폭 동등 노출합니다. - 기업 로그인 구분 — 기업 로그인은 조직 SSO와 관리자 문의가 중심입니다. 공개 가입 권한은 제품/조직 관리자가 소유합니다.
- 2FA/복구 분리 — 인증 앱 코드와 복구 코드는 첫 로그인 화면에 섞지 않고 별도 단계 표면으로 전환합니다.
- 접근성 —
LoginSurface는 이름 있는 region, 입력은 보이는 라벨, 오류는 live region, OTP는OneTimeCodeInput, 식별자는 가능한 경우autocomplete="username webauthn"을 사용합니다.
SHOULD#
- 범용 로그인은 이메일·비밀번호를 기본값으로 두고 passkey와 provider 버튼을 같은
LoginMethods그룹에 배치합니다. - 패스키 준비 프리셋은 이메일 자동완성(
username webauthn)과AuthMethodButton method="passkey"를 함께 제공합니다. - 인증 실패는 어느 필드가 틀렸는지 말하지 않는 제너릭
FormMessage variant="error"로 CTA 바로 위에 둡니다. - 계정 잠금은
FormMessage variant="warning"또는 동등한 live status를 쓰고, 모든 CTA를 비활성화합니다. - 소비 프로젝트 테스트에는 아래 한 줄을 추가합니다.
assertLoginPatternCompliance('enterprise', screen);
NEVER#
- 마케팅 split 패널, 제품 소개 카드, 캠페인 이미지 패널을 로그인 패턴 내부에 넣지 않습니다.
- 한 화면에 primary CTA를 2개 이상 두지 않습니다.
- passkey/SSO/provider 버튼을 작은 링크, tertiary action, 접힌 메뉴로 낮추지 않습니다.
- 기업 로그인에 Apple/Google/Microsoft provider 버튼 또는
계정 만들기CTA를 넣지 않습니다. - 기업 로그인 footer를 공개 가입으로 보내지 않습니다. footer는
관리자에게 문의입니다. - 2FA/복구 코드 입력을 이메일·비밀번호·provider 버튼과 같은 첫 로그인 화면에 섞지 않습니다.
- placeholder를 라벨 대체로 쓰거나, 비밀번호 붙여넣기/비밀번호 매니저를 막지 않습니다.
프리셋별 구현 계약#
| 프리셋 | 반드시 있어야 하는 요소 | 절대 없어야 하는 요소 |
|---|---|---|
| 범용 로그인 | LoginSurface size="sm", 이메일/아이디 FormField, 비밀번호 FormField, primary 로그인 CTA 1개, 계정 만들기 footer | 마케팅 split 패널, 첫 화면의 OTP/복구 코드 입력, primary CTA 2개 이상 |
| 기업 로그인 | LoginSurface size="sm", AuthMethodButton method="sso", primary CTA 1개, 관리자에게 문의 footer | Apple/Google/Microsoft ProviderButton, 계정 만들기, 첫 화면의 OTP/복구 코드 입력 |
| 패스키 준비 | 이메일/아이디 Input autoComplete="username webauthn", 비밀번호 필드, AuthMethodButton method="passkey", primary CTA 1개 | 패스키를 보조 링크로 숨김, 첫 화면의 OTP/복구 코드 입력, primary CTA 2개 이상 |
| 2FA | OneTimeCodeInput length={6}, 확인 CTA 1개, 복구 코드 사용 링크 | 이메일 필드, 비밀번호 필드, passkey/SSO/provider 버튼, 계정 만들기 |
| 인증 실패 | 이메일/비밀번호 필드, CTA 위 FormMessage variant="error", 제너릭 실패 문구, primary CTA 1개 | ”이 이메일은 없음” 같은 계정 열거 문구, 첫 화면의 OTP/복구 코드 입력, 마케팅 split 패널 |
| 계정 잠금 | FormMessage variant="warning" 또는 live status, 비활성 CTA, 잠금/레이트리밋 안내 | 활성 primary CTA, 활성 provider 버튼, 첫 화면의 OTP/복구 코드 입력 |
React 소비 레시피#
import {
AuthMethodButton,
Button,
FormField,
Input,
LoginMethods,
LoginSurface,
TextLink,
} from '@wds/ui-web';
export function EnterpriseLoginRecipe() {
return (
<LoginSurface
brand={<ProductLogo />}
title="기업 로그인"
subtitle="회사 계정으로 계속하세요."
footer={<TextLink href="/help/access">관리자에게 문의</TextLink>}
size="sm"
>
<LoginSurface.Body>
<FormField label="회사 이메일">
<Input type="email" autoComplete="username webauthn" />
</FormField>
<FormField label="비밀번호">
<Input type="password" autoComplete="current-password" />
</FormField>
<LoginMethods>
<AuthMethodButton method="sso" />
<AuthMethodButton method="passkey" />
</LoginMethods>
</LoginSurface.Body>
<LoginSurface.Actions>
<Button type="submit" fullWidth>
로그인
</Button>
</LoginSurface.Actions>
</LoginSurface>
);
}
import {
AuthMethodButton,
Button,
FormField,
Input,
LoginMethods,
LoginSurface,
ProviderButton,
TextLink,
} from '@wds/ui-web';
export function GenericLoginRecipe() {
return (
<LoginSurface
brand={<ProductLogo />}
title="로그인"
subtitle="계정으로 계속하세요."
footer={<TextLink href="/signup">계정 만들기</TextLink>}
size="sm"
>
<LoginSurface.Body>
<FormField label="이메일">
<Input type="email" autoComplete="username webauthn" />
</FormField>
<FormField label="비밀번호">
<Input type="password" autoComplete="current-password" />
</FormField>
<LoginMethods>
<AuthMethodButton method="passkey" />
<ProviderButton provider="apple" fullWidth />
<ProviderButton provider="google" fullWidth />
<ProviderButton provider="microsoft" fullWidth />
</LoginMethods>
</LoginSurface.Body>
<LoginSurface.Actions>
<Button type="submit" fullWidth>
로그인
</Button>
</LoginSurface.Actions>
</LoginSurface>
);
}
import {
Button,
LoginSurface,
OneTimeCodeInput,
TextLink,
} from '@wds/ui-web';
export function TwoFactorRecipe() {
return (
<LoginSurface
brand={<ProductLogo />}
title="인증 앱 확인"
subtitle="인증 앱의 6자리 코드를 입력하세요."
footer={<TextLink href="/recovery-code">복구 코드 사용</TextLink>}
size="sm"
>
<LoginSurface.Body>
<OneTimeCodeInput length={6} name="otp" aria-label="인증 코드" />
</LoginSurface.Body>
<LoginSurface.Actions>
<Button type="submit" fullWidth>
확인
</Button>
</LoginSurface.Actions>
</LoginSurface>
);
}
import { render, screen } from '@testing-library/react';
import { assertLoginPatternCompliance } from '@wds/ui-web/testing';
render(<EnterpriseLoginRecipe />);
assertLoginPatternCompliance('enterprise', screen);
데모#
상단 컨트롤로 세 축을 살펴봅니다 — 프리셋(범용 · 패스키 준비 · 기업 로그인 · 소셜 로그인 · 고보안 · 재인증),
상태(라이브 · 로딩 · 인증 실패 · 계정 잠금 · 인증 앱 · 복구 코드 · 성공 · 패스키 등록),
옵션(Apple·Google·Microsoft 계정 연동 표시/숨김).
실제 인증은 없습니다(UI Reference). 라이브에서 빈 값 제출은 필드 오류를 보여주고, 유효 제출·패스키·SSO·Provider 버튼은 성공을 시뮬레이션합니다.
프리셋·상태처럼 선택지가 길거나 모바일 한 줄에서 넘칠 수 있는 데모 선택기는 SegmentedButton이 아니라
ChipGroup을 사용합니다. 짧고 고정된 2~4개 선택지만 한 줄에 안정적으로 들어갈 때 SegmentedButton을 씁니다.
언제 로그인을 요구하나#
로그인 화면을 어떻게 만드느냐보다 언제 요구하느냐가 먼저입니다.
- 게스트 우선 — 로그인을 가능한 한 늦춥니다. 둘러보기·열람으로 가치를 먼저 경험하게 하고, 계정이 꼭 필요한 동작(저장·공유·결제)에서만 요구합니다.
- 가치 교환 — “가치를 주는 대가로만” 로그인을 요청합니다. 진입 즉시 벽을 세우지 않습니다.
- 계정 최소화 — 핵심 기능이 계정을 요구하지 않으면 계정 생성을 강제하지 않습니다.
- 삭제 진입점 의무 — 계정 생성을 돕는다면 삭제(비활성화 아님) 경로도 앱 안에서 제공해야 합니다.
- provider 동등성 — 서드파티 로그인(Google·Microsoft 등)만으로 계정을 만드는 진입은 동급의 대안(Sign in with Apple·자체 계정)을 함께 둡니다.
이 패턴은 “로그인 표면”의 정본일 뿐, 로그인을 띄울지 말지는 제품이 위 정책으로 결정합니다.
구성#
로그인 화면의 정본은 단일 인증 표면입니다. 제품 브랜드·캠페인 메시지·마케팅 이미지는 페이지 배경이나 상위 레이아웃에서 처리하고, 패턴 자체는 인증에 필요한 입력과 선택지만 담습니다.
| 영역 | 역할 | 구성 요소 |
|---|---|---|
| 표면 헤더 | 최소 브랜드 확인 | LoginSurface의 서비스 로고 슬롯 · 서비스명 |
| 인증 표면 | 입력·방식 선택·2차 인증·복구 | LoginSurface · 이메일 · 비밀번호 · 대체 방식 · MFA · 복구 코드 · 패스키 등록 권유 |
데모의 로고는 실제 WDS·제품 로고가 아니라 서비스 로고 자리표시자입니다. 제품은 이 슬롯에 자사 심볼, 앱 아이콘, 조직 브랜드를 넣고, 패턴은 로고의 모양을 강제하지 않습니다.
아트디렉션 계약#
로그인 표면은 기능 옵션보다 먼저 읽는 순서와 시각 리듬이 고정되어야 합니다. 아래 계약을 깨면 구글·애플·마이크로소프트 계정 연동을 붙여도 화면이 불안정해집니다.
| 항목 | 계약 | 구현 기준 |
|---|---|---|
| 표면 | 데스크탑과 모바일이 같은 단일 패널을 재사용 | 패널 최대 폭 390px, 마케팅 split 패널 금지 |
| 정렬 | 브랜드·제목·설명은 중앙, 입력 라벨·필드·오류는 좌측 | FormField는 입력 박스 기준 좌측 정렬 |
| 타이포 | 로그인 표면 안의 텍스트는 과한 굵기를 피함 | 제목·CTA·방식 버튼 medium, 보조 문장 regular |
| 세로 리듬 | 각 묶음은 spacing scale로만 떨어뜨림 | 라벨→입력 space-2, 필드 간 space-4, 폼→구분선·Provider→footer space-5 |
| CTA 위계 | 강한 primary action은 한 화면에 하나 | 로그인만 채움 CTA, 패스키·SSO·Provider는 동등한 방식 버튼 |
| Provider 버튼 | Apple·Google·Microsoft는 브랜드 규칙을 보존하되 다른 방식과 같은 폭·높이 | ProviderButton은 full width, LoginMethods 안에서 같은 stack 간격 |
| 패스키 | 낯선 3차 옵션으로 숨기지 않음 | 이메일 필드 autocomplete="username webauthn" + 명시 버튼 |
| 2FA/복구 | 첫 화면 로그인 방식과 2차 인증 fallback을 섞지 않음 | 복구 코드는 MFA/복구 단계 안에서만 전환 |
| 터치 타깃 | 작은 링크·아이콘도 최소 타깃/간격을 확보 | 버튼은 WDS md 높이, 작은 보조 링크는 인접 타깃과 겹치지 않게 배치 |
이 계약은 레퍼런스 조사의 기본 산출물에도 포함됩니다. 새 로그인 변형을 제안할 때는 기능 흐름뿐 아니라 레이아웃·정렬·간격·여백·타이포·버튼 위계·반응형 검증을 함께 기록해야 합니다.
적응형 동작#
패턴은 같은 단일 패널을 재사용합니다. 상위 화면이 넓어져도 인증 표면 자체는 390px를 넘지 않고,
좁은 배치에서는 padding·OTP 칸·간격만 컨테이너 폭에 맞춰 축소합니다.
| 배치 폭 | 레이아웃 |
|---|---|
| 넓은 페이지·모달·사이드 패널 | 모바일과 같은 폭의 중앙 단일 인증 패널(최대 390px) |
| 좁은 모바일 폭 | 같은 단일 인증 패널, padding·OTP 칸·간격만 축소 |
입력·CTA의 터치 타깃은 유지하고, 동일 패턴을 모달·사이드 패널·전체 화면 어디에 배치해도 깨지지 않습니다. 인증 앱·복구 코드·패스키 등록 권유도 같은 단일 표면 안에서 단계 전환됩니다. 코드 입력은 모바일에서도 한 줄을 유지하되, 칸과 간격만 컨테이너 폭에 맞춰 축소합니다.
조합 모델#
로그인은 이제 “식별자 우선”과 “단일 화면” 중 하나를 고르는 문제가 아닙니다. 제품 정책에 따라 한 인증 표면의 슬롯을 켜고 끕니다. 기본값은 이메일·비밀번호를 바로 사용할 수 있는 표면이며, 패스키·SSO·소셜 로그인은 동등한 대체 방식으로 붙습니다. 인증 앱과 복구 코드는 1차 로그인 방식이 아니라 2차 인증/복구 단계입니다.
| 슬롯 | 역할 | 대표 컴포넌트 |
|---|---|---|
identity | 이메일·아이디 입력, 패스키 conditional UI | Input, FormField |
primaryCredentials | 비밀번호, 표시 토글, 로그인 유지, 비밀번호 찾기 | Input, Checkbox, TextLink |
alternateMethods | 패스키·SSO·소셜·매직 링크 | LoginMethods, AuthMethodButton, ProviderButton |
secondFactor | 인증 앱·보안 키·복구 코드 | OneTimeCodeInput, FormMessage |
postAuthEnrollment | 패스키 등록 권유 | AuthMethodButton, Button |
패스키 conditional UI — 패스키의 1차 발견 경로는 명시 버튼만이 아니라 이메일 필드 autofill입니다. 식별자 입력에
autocomplete="username webauthn"을 주면 브라우저가 사용 가능한 패스키를 자동완성 목록에 함께 띄웁니다.AuthMethodButton method="passkey"는 이 conditional UI를 보완·명시하는 경로로 둡니다.
프리셋#
프리셋은 별도 패턴이 아니라 옵션 묶음입니다. 제품팀은 하나를 시작점으로 삼고 필요한 슬롯만 조정합니다.
| 프리셋 | 우선 노출 | 보조/다음 단계 |
|---|---|---|
| 범용 | 이메일·비밀번호 | 패스키·Apple/Google/Microsoft 계정 연동(옵션)·계정 만들기 |
| 패스키 준비 | 이메일·비밀번호 + 패스키 동등 노출 | 로그인 후 패스키 등록 권유 |
| 기업 로그인 | 조직 SSO | 비밀번호·패스키 폴백·관리자 문의 |
| 소셜 로그인 | Apple·Google·Microsoft | 자체 계정 로그인 |
| 고보안 | 비밀번호 또는 SSO | 인증 앱·복구 코드(2FA 단계 안에서) |
| 재인증 | 비밀번호 또는 패스키 | 필요 시 MFA, 가입 CTA 없음 |
로그인 프로세스#
범용 로그인과 기업 로그인은 같은 인증 표면을 쓰지만 계정 생성 권한이 다릅니다.
| 프로세스 | 계정 생성 | 1차 진입 | 보조 경로 | 계정 없음/접근 없음 |
|---|---|---|---|---|
| 범용 | 사용자 셀프 가입 허용 | 이메일·비밀번호 또는 패스키 | Apple/Google/Microsoft 계정 연동·비밀번호 찾기 | 계정 만들기 |
| 기업 | 사용자 셀프 가입 금지 | 조직 SSO 또는 회사 이메일 | 허용된 경우 비밀번호·패스키 폴백 | 관리자에게 문의 |
기업 로그인은 공개 회원가입 플로우로 이어지면 안 됩니다. 계정은 조직 관리자가 초대·발급하거나 IdP/SCIM 같은 조직 프로비저닝으로 생성합니다. 사용자가 접근 권한이 없거나 조직 도메인이 매칭되지 않는 경우에는 가입 CTA 대신 관리자 문의, 헬프데스크, 접근 요청 흐름으로 보냅니다.
공통 로그인 진행#
| 단계 | 사용자가 보는 것 | 제품/백엔드 책임 | 다음 분기 |
|---|---|---|---|
| 1. 식별자 입력 | 이메일 또는 아이디 입력, 패스키 자동완성 제안 | 식별자 형식 검증, 도메인·조직 정책 조회 | 범용은 자체 계정/소셜, 기업은 SSO 라우팅 가능 |
| 2. 1차 인증 | 비밀번호·패스키·Provider·SSO 중 허용된 방식 | 비밀번호 검증, WebAuthn/OIDC/SAML/OAuth 검증, 레이트리밋 | 성공, 실패, 잠금, 2FA 필요 |
| 3. 2차 확인 | 인증 앱 6자리 코드 또는 복구 코드 | TOTP 검증, 이미 사용한 코드 재사용 차단, 실패 횟수 제한 | 세션 발급 또는 로그인 화면 복귀 |
| 4. 세션 발급 | 완료 상태, 필요한 경우 다음 화면 이동 | 세션/토큰 발급, 새 기기·위치 알림, 기존 위험 세션 처리 | 앱 진입, 패스키 등록 권유 |
| 5. 보안 후속 조치 | 패스키 추가, 로그인 알림, 활성 세션 관리 | 패스키 저장·대표 지정, 세션 회수, 비밀번호 변경/만료 정책 | 보안 설정 또는 일반 사용 흐름 |
보조 프로세스#
| 흐름 | 진입점 | 진행 | 완료/실패 처리 |
|---|---|---|---|
| 로그인 | 로그인 CTA, 패스키, Apple/Google/Microsoft, 조직 SSO | 1차 인증 → 정책 확인 → 필요 시 2FA → 세션 발급 | 실패 메시지는 제너릭, 반복 실패는 잠금·알림 |
| 아이디/이메일 찾기 | 로그인 표면의 보조 링크 또는 고객지원 | 사용자가 소유한 이메일·전화·조직 정보를 제출 | 계정 존재 여부를 직접 노출하지 않고 안내 메일·지원 티켓으로 응답 |
| 비밀번호 찾기 | 비밀번호 찾기 | 복구 이메일/문자 요청 → 1회용 링크 또는 코드 → 새 비밀번호 설정 | 링크는 짧은 만료와 1회 사용, 성공 후 기존 세션 회수 |
| 패스키 등록 | 로그인 성공 직후 권유 또는 보안 설정 | OS/브라우저 생체 인증 → 공개키 저장 → 기기 이름·대표 여부 관리 | 실패하면 비밀번호/SSO 폴백 유지, 등록된 패스키는 설정에서 삭제 가능 |
| 2FA 설정 | 보안 설정 또는 조직 정책 | QR 스캔 → 인증 앱 6자리 코드 검증 → 복구 코드 발급 권장 | 다음 비밀번호 로그인부터 2차 확인, 복구 코드는 인증 앱 분실 시 사용 |
| 계정 만들기 | 범용 프리셋 하단 링크 | 이메일/소셜로 신원 생성 → 약관·이메일 확인 → 비밀번호 또는 패스키 등록 | 제품은 계정 삭제 경로도 함께 제공 |
| 관리자에게 문의 | 기업 프리셋 하단 링크 | 조직 도메인·초대 상태·권한 요청을 관리자/헬프데스크로 전달 | 공개 가입으로 보내지 않고 초대, IdP, SCIM 프로비저닝으로 해결 |
어드민 콘솔 연결#
로그인 패턴은 어드민 콘솔의 보안 기능과 이어져야 합니다. 콘솔의 인증 & 접근 영역은 패스키·2FA·조직 SSO를 관리하고, 로그인 알림은 새 기기·비정상 위치·반복 실패를 알려줍니다. 비밀번호 영역은 변경, 만료 정책, 재설정 링크를 담당하며, 활성 세션은 현재 기기와 의심 세션 회수를 보여줍니다.
이 구조를 쓰면 로그인 화면은 얇게 유지하면서도, 계정 복구·패스키 등록·2FA 설정·세션 보안 같은 운영 프로세스는 어드민 콘솔 또는 계정 설정으로 자연스럽게 이어집니다.
변형#
같은 인증 표면을 맥락에 따라 배치합니다 — 컴포넌트·상태는 공유하고 주변 셸만 바꿉니다.
| 변형 | 형태 | 쓰는 곳 |
|---|---|---|
| centered auth panel | 인증 표면 단독 중앙 카드(기본 데모) | 전용 로그인 페이지·미니멀 진입·임베드 |
| step-up panel | 2차 인증·복구·등록을 위한 같은 표면의 단계 상태 | MFA·복구 코드·패스키 등록 권유 |
| modal / embedded | 오버레이·인라인 패널에 표면만 | 작업 중 재인증·세션 만료 복귀 |
| enterprise SSO landing | SSO를 우선 노출하고 비밀번호/패스키 폴백 제공 | 도메인 페더레이션 조직 진입 |
modal·SSO landing은 같은 표면을 다른 셸에 끼워 재사용합니다 — 새 컴포넌트를 만들지 않습니다.
상태#
데모의 상태 컨트롤로 로딩·인증 실패·계정 잠금·MFA·복구·패스키 등록 권유를 강제 표시해 검수할 수 있습니다.
| 상태 | 트리거 | 표현 |
|---|---|---|
| 기본 | 초기 | 이메일·비밀번호·CTA·대체 방식 노출, 오류 없음 |
| 로딩 | 제출 직후 | 1차 CTA loading(Spinner·aria-busy)·중복 제출 차단 |
| 인라인 오류 | 빈·형식 오류 필드 | 해당 FormField에 오류 텍스트 + aria-invalid 보더 |
| 인증 실패 | 자격 증명 불일치 | CTA 위 FormMessage(error) role="alert" — 제너릭 메시지(계정 열거 방지) |
| 계정 잠금 | 연속 실패(레이트리밋) | CTA 위 FormMessage(warning) + 모든 CTA 비활성 |
| 인증 앱 | 1차 인증 성공 후 MFA 필요 | 6자리 OneTimeCodeInput + 복구 코드 경로 |
| 복구 코드 | 인증 앱 사용 불가 | 8자리 영숫자 OneTimeCodeInput + 인증 앱 복귀 |
| 패스키 등록 권유 | 로그인 성공 후 패스키 미등록 | ”패스키 추가” CTA + 나중에 하기 |
| Caps Lock | 비밀번호 포커스 중 Caps Lock | 필드 하단 role="status" 라이브 경고 |
| 완료 | 유효 제출 · 패스키 · SSO · Provider | 성공 마크 + 환영 메시지 + 다른 계정 전환 |
실제 인증 로직은 포함하지 않습니다 — 패턴은 UI Reference만 제공합니다(MASTER_PLAN Portal Boundary). 제품은 이 표면에 자사 인증 흐름을 연결합니다.
모션#
전환은 흐름을 보조할 뿐 주의를 끌지 않습니다 — spring 토큰을 그대로 소비합니다.
| 대상 | 토큰 | 동작 |
|---|---|---|
| 2차 인증·복구·등록 상태 진입 | spring.spatial.fast (256ms · ζ0.8) | 패널 콘텐츠가 약한 오버슈트로 슬라이드+페이드 |
| 폼 메시지 등장(인증 실패·계정 잠금) | spring.effect.default (188ms · ζ1.0) | 오버슈트 없이 페이드 인 |
transform·opacity만 애니메이트해 레이아웃을 흔들지 않습니다(컴포지터 친화).
prefers-reduced-motion: reduce면 모든 전환을 끄고 즉시 정적으로 표시합니다(WCAG 2.3.3).
콘텐츠·보안#
| 규칙 | 근거 |
|---|---|
| 실패 메시지는 제너릭 — 어느 필드가 틀렸는지 노출 금지 | 계정 열거 방지(OWASP Authentication) |
메시지 배치는 범위(scope) 기준 — 필드 오류는 인풋 아래(Tier 1), 제출 실패·잠금은 CTA 위 FormMessage(Tier 2) | 메시지 위계 |
| 라벨은 지시 아닌 명사(이메일·비밀번호), placeholder는 라벨 대체 금지 | 라벨 상시 가시성 |
| 1차 동사는 행동형(로그인·계속), 방식 버튼은 “패스키로 로그인”·“조직 SSO로 계속하기” | 동사 일관성 |
| 비밀번호는 붙여넣기·비밀번호 매니저 허용(차단 금지) | WCAG 2.2 §3.3.8 |
| 인증 앱·복구 코드는 첫 화면의 로그인 방식 그룹에 섞지 않음 | 1차 인증과 2차/복구 단계 구분 |
| 가입·문의 링크는 도메인 중립 문구 사용 | 범용은 “계정 만들기”, 기업은 “관리자에게 문의”처럼 제품명이 들어가지 않는 링크 |
| 기업 로그인은 회원가입 링크 금지 | 계정 생성은 조직 관리자·IdP·프로비저닝 시스템이 소유 |
외부 기준#
| 기준 | 패턴 반영 |
|---|---|
| web.dev — passkey form autofill | 기존 비밀번호 사용자를 유지하면서 이메일 필드의 username webauthn 자동완성으로 패스키를 제안 |
| Google Identity — Passkeys | WebAuthn challenge·공개키 검증·등록/인증 생애주기를 제품 배선 책임으로 분리 |
| Google Identity — Branding guidelines | Google 버튼은 브랜드 외형을 보존하고 다른 로그인 방식과 같은 폭·위계로 배치 |
| Apple — Sign in with Apple for the web | Apple 로그인은 별도 Provider 버튼으로 노출하고 제품이 서비스 ID·리디렉션을 배선 |
| Microsoft identity platform — supported account types | Microsoft 버튼은 계정 공급자 진입이고, 조직 SSO는 tenant/authority 정책으로 제품이 분리 |
| Microsoft identity platform — OpenID Connect | Microsoft OAuth/OIDC redirect·token·session 처리는 DS가 아니라 제품 인증 코드가 배선 |
| Microsoft Fluent 2 — Button usage | 한 레이아웃의 primary action은 하나만 두고, 다수 보조 동작은 neutral/outline 위계로 정리 |
| Microsoft Entra — SSPR | 비밀번호 찾기는 1회용 링크/코드, 짧은 만료, 재설정 후 세션 회수 흐름으로 정리 |
| Microsoft Entra — Application provisioning | 기업 계정 생성은 공개 가입이 아니라 관리자 초대, IdP, SCIM 프로비저닝으로 처리 |
| W3C WAI — Labeling Controls | 입력 목적을 보이는 라벨로 제공하고 label–control 연결을 유지 |
| WCAG 2.2 — Target Size (Minimum) | 포인터 타깃은 최소 크기 또는 충분한 간격을 확보 |
| WCAG 2.2 — Accessible Authentication | 비밀번호 매니저·붙여넣기·대체 인증 경로로 인지 부담을 줄임 |
접근성#
WCAG 2.2 AA를 기준으로, 로그인 특화 신규 기준(§3.3.8 Accessible Authentication)을 명시적으로 만족시킵니다.
| 계약 | 구현 |
|---|---|
| 폼 영역 | LoginSurface 기본 role="region" + aria-label="로그인" 으로 랜드마크화 |
| 라벨 연결 | FormField 가 label–입력을 htmlFor/id로 배선, 오류 시 aria-describedby/aria-invalid 자동 주입 |
| 입력 목적 (1.3.5) | 식별자 autocomplete="username webauthn"·비밀번호 current-password·inputmode 지정 |
| 인증 접근성 (§3.3.8) | 붙여넣기·비밀번호 매니저 허용 + 비(非)인지 경로(패스키·SSO) 제공 |
| 중복 입력 (3.3.7) | 2차 인증·복구 단계에서 이메일·비밀번호 재입력 요구 없음 |
| 상태 메시지 (4.1.3) | 폼 오류 FormMessage(error)=role="alert", Caps Lock=role="status" 라이브 리전 |
| 비밀번호 토글 | aria-pressed + 상태별 aria-label(표시/숨기기), 기본 마스킹 |
| 코드 입력 | OneTimeCodeInput 그룹 라벨·자리별 라벨·autocomplete="one-time-code" |
| 대비 | 인증 표면은 semantic 토큰으로 AA 충족 |
사용 컴포넌트#
Input(leadingIcon·trailingIcon·invalid) · FormField(error) · Button(primary·secondary·ghost·fullWidth·loading) ·
Checkbox · TextLink · FormMessage(error·warning, Tier 2) · ChipGroup(긴 데모 선택 컨트롤) ·
LoginSurface ·
AuthMethodButton · LoginMethods ·
ProviderButton · OneTimeCodeInput. 전부
06 컴포넌트의 실물을 그대로 조합했습니다. LoginSurface는 패키지로 배포되는 표면 recipe이고,
프리셋·상태·실제 인증 프로세스는 제품이 조합합니다.
토큰#
패턴 전용 토큰은 두지 않고 semantic 토큰을 직접 소비합니다.
| 속성 | 토큰 |
|---|---|
| 폼 표면 | color.surface-raised |
| 표면 폭 | 최대 390px |
| 라운드/그림자 | radius.xl · shadow.lg |
| 구분선/보더 | color.border-subtle |
| 본문/보조 텍스트 | color.text · color.text-muted · color.text-subtle |
| 완료 마크 | color.success |
백엔드 연동 (P6)#
위 패턴 데모는 UI Reference라 실제 인증이 없습니다. P6 Backend Base의 core-api와
실제로 통신하는 데모는 아래에 있습니다 — 루트에서 make stack-up으로 로컬 풀스택을
먼저 띄워야 동작합니다(진입점 localhost:58080, Nginx 리버스 프록시).
아래 폼은 실제 core-api(http://localhost:58080)로 요청합니다. 시드 계정 비밀번호는 infra/docker/.env의 WDS_DEMO_ADMIN_PASSWORD 값입니다.
| 흐름 | 엔드포인트 |
|---|---|
| 로그인 | POST /api/v1/auth/login → access(15분) + refresh(14일, 회전) |
| 내 정보 | GET /api/v1/auth/me (Bearer) |
| 실패 | 401 ProblemDetail · 연속 실패 시 429(레이트리밋) |
시드 계정은 demo 프로파일이 만듭니다 — 이메일 admin@wiz-factory.com,
비밀번호는 infra/docker/.env의 WDS_DEMO_ADMIN_PASSWORD. 자세한 API 계약은
BACKEND_GUIDE(P6 문서)를 참고하세요.