PatternsP4 본문

인증 연동

AuthMethodButton·LoginMethods로 만든 로그인 UI를 실제 인증에 연결하는 방법 — 패스키 생애주기(등록→인증→복구), 웹 vs 네이티브 OS 호출 맵, SSO·서드파티 흐름, 배선 레시피. DS는 UI를, 제품은 인증 의식을 담당합니다.

마지막 업데이트 2026-06-22

로그인 패턴이 **화면(UI)**을 정의한다면, 이 문서는 그 버튼들을 실제 인증에 연결하는 방법을 정리합니다 — 패스키 등록부터 로그인·복구까지의 프로세스와, OS·디바이스별 호출 차이입니다.

경계 — 무엇을 DS가 주고, 무엇을 제품이 배선하나#

인증 의식(WebAuthn 호출·자격증명 저장·세션)은 보안 민감 + 백엔드 결합이라 디자인 시스템이 코드로 담지 않습니다. DS는 UI·UX·접근성을, 제품(또는 인증 SDK)이 인증 로직을 책임집니다.

WIZ DS가 제공제품이 배선
AuthMethodButton·LoginMethods·ProviderButton (UI)WebAuthn 등록/인증 의식(navigator.credentials)
상태(로딩·실패·잠금)·동등 노출(parity)·a11yRP 서버(challenge 발급·공개키 저장·서명 검증)
로그인 화면 패턴·conditional UI 가이드세션·토큰·계정 복구·다중기기 정책

즉 버튼은 “트리거”이고, 그 onClick에 제품의 인증 호출을 연결합니다. DS는 어떤 인증 백엔드(SimpleWebAuthn·Auth0·Hanko·자체 OIDC)도 강제하지 않습니다.

패스키 생애주기 (프로세스)#

패스키는 등록(Registration) 없이는 인증(Authentication) 할 수 없습니다(닭-달걀). 그래서 최초 진입은 항상 다른 1차 방식을 거칩니다.

  1. 1차 로그인 — 비밀번호·조직 SSO·이메일 매직링크 중 하나로 최초 인증.
  2. 패스키 등록 — 1차 로그인 직후 권유하거나 계정 설정에서. 기기의 생체(Face ID·지문·Windows Hello·PIN)로 키쌍 생성, 공개키만 서버에 저장.
  3. 이후 인증 — 다음 로그인부터 패스키로. 가능하면 **conditional UI(자동완성)**로 이메일 필드에서 바로 제안.
  4. 크로스디바이스 — 등록 안 된 기기에선 QR/hybrid로 휴대폰 패스키 사용.
  5. 폴백 — 패스키 불가 환경에선 비밀번호·SSO·매직링크로 회귀(항상 제공).
  6. 복구 — 모든 패스키 분실 시 이메일 복구·신뢰 기기·관리자 재설정.

등록 진입점을 설정 + 1차 로그인 직후 권유 두 곳에 두는 것이 채택률을 높이는 표준 패턴입니다(Passkey UX).

등록 의식 (Registration)#

// 1) 서버: 등록 옵션 + 일회성 challenge 발급 (RP ID = 도메인)
const options = await fetch('/auth/passkey/register/options').then((r) => r.json());

// 2) 클라이언트: 브라우저+OS가 생체 프롬프트를 띄우고 키쌍 생성
//    (직접 navigator.credentials.create 대신 라이브러리 권장 — 아래 '배선' 참고)
import { startRegistration } from '@simplewebauthn/browser';
const credential = await startRegistration(options);

// 3) 서버: 공개키·credential_id·signature_count·AAGUID 저장 (개인키는 저장하지 않음)
await fetch('/auth/passkey/register/verify', {
  method: 'POST',
  body: JSON.stringify(credential),
});

2025 신규 — Conditional Create: 비밀번호로 로그인할 때 백그라운드로 패스키를 자동 생성(자동 업그레이드)할 수 있습니다. 별도 버튼 없이 패스키 보급을 늘립니다.

인증 의식 (Authentication)#

// 명시 버튼(AuthMethodButton method="passkey")의 onClick에 연결
import { startAuthentication } from '@simplewebauthn/browser';

async function onPasskey() {
  const options = await fetch('/auth/passkey/login/options').then((r) => r.json());
  const assertion = await startAuthentication(options);
  await fetch('/auth/passkey/login/verify', { method: 'POST', body: JSON.stringify(assertion) });
}
<AuthMethodButton method="passkey" onClick={onPasskey} />

conditional UI (자동완성) — 권장 1차 경로#

명시 버튼보다 이메일 필드의 자동완성에 패스키를 띄우는 것이 모던 권장입니다(비밀번호 아래 3차 버튼은 안티패턴 — 사용자가 비번을 먼저 채움).

// 이메일 인풋에 webauthn 토큰 추가 (autocomplete 의 마지막 토큰이어야 함)
<Input id="login-email" autoComplete="username webauthn" />
// 폼 마운트 시 conditional mediation 으로 자동완성 제안 활성화
import { startAuthentication } from '@simplewebauthn/browser';
const supportsAutofill = await window.PublicKeyCredential?.isConditionalMediationAvailable?.();
if (supportsAutofill) {
  const options = await fetch('/auth/passkey/login/options').then((r) => r.json());
  startAuthentication(options, /* useBrowserAutofill */ true); // 백그라운드 대기
}

→ 이때 AuthMethodButton method="passkey"보완/명시 경로(자동완성 미지원·식별자 우선 흐름)로 둡니다.

플랫폼 호출 맵 — 웹 vs 네이티브#

핵심: 웹은 OS가 달라도 호출이 동일합니다(브라우저+OS가 차이를 흡수). 네이티브 앱만 OS별 API가 갈립니다.

타겟호출 APIOS별 분기동기화 제공자
(@wds/ui-web)navigator.credentials (WebAuthn)불필요 — Win Hello·Touch ID·Android 생체를 OS가 처리iCloud Keychain · Google PM · Windows Hello (OS 자동)
iOS 네이티브 (wiz_ui)ASAuthorization / AuthenticationServices (iOS 16+)iOS 전용iCloud Keychain
Android 네이티브 (wiz_ui)Credential Manager (Android 14+)Android 전용Google Password Manager

그래서 웹 제품은 OS별 코드를 거의 쓰지 않습니다 — 하나의 WebAuthn 연동이 Windows·macOS·iOS·Android 브라우저에서 동작합니다. per-OS 작업은 Flutter 네이티브 앱에서만 필요하며, 이는 후속 wiz_ui 패리티 트랙에서 다룹니다. 출처: W3C WebAuthn L2 · passkeys.dev · Android Credential Manager · web.dev conditional UI

네이티브 패스키 호출 레시피 (Flutter)#

네이티브 앱에서 WizAuthMethodButtonUI 트리거일 뿐입니다 — 버튼의 onPressed 안에서 플랫폼 채널이나 패스키 패키지(passkeys 등)로 OS 인증 의식을 호출합니다. 등록/인증 의식·서버 검증은 제품(또는 인증 SDK)이 배선합니다. DS는 화면을, 제품은 인증을 책임지는 경계는 네이티브에서도 동일합니다.

Android — Credential Manager (androidx.credentials):

// 등록: 새 패스키 생성 (서버가 내려준 옵션 JSON 사용)
val request = CreatePublicKeyCredentialRequest(requestJson = optionsJson)
val response = credentialManager.createCredential(activity, request)

// 인증: 기존 패스키로 로그인
val getRequest = GetCredentialRequest(listOf(GetPublicKeyCredentialOption(requestJson = challengeJson)))
val credential = credentialManager.getCredential(activity, getRequest)

iOS — AuthenticationServices (ASAuthorization):

// 플랫폼 패스키 프로바이더 (relyingPartyIdentifier = 도메인)
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: "example.com")
let request = provider.createCredentialRegistrationRequest(challenge: challenge, name: userName, userID: userID)
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.performRequests()
// Flutter UI: 버튼은 트리거, onPressed 가 플랫폼 채널/패키지로 위 의식을 호출
WizAuthMethodButton(
  method: WizAuthMethod.passkey,
  onPressed: () => authService.signInWithPasskey(),
)

직접 구현 대신 passkeys 같은 패키지가 Android Credential Manager·iOS ASAuthorization 을 한 API로 감쌉니다. 서버 의식(옵션 생성·검증)은 웹과 동일하게 재사용합니다(RP ID = 도메인). 출처: Android — Credential Manager · Apple — AuthenticationServices

조직 SSO (OIDC / SAML)#

// 식별자 우선: 이메일 도메인으로 조직 IdP 를 디스커버리한 뒤 리다이렉트
<AuthMethodButton method="sso" onClick={() => location.assign('/auth/sso/start?email=' + email)} />

서버는 이메일 도메인 → 조직 IdP 매핑으로 OIDC/SAML 리다이렉트를 시작합니다. 식별자 우선 흐름과 자연스럽게 맞물립니다(이메일 입력 → 방식 라우팅).

서드파티 (Apple · Google)#

브랜드 고정 버튼은 ProviderButton을 쓰고(색·로고·라벨이 IdP 가이드라인에 고정), 각 IdP의 OAuth/OIDC SDK로 연결합니다. WDS 테마로 바꾸지 않습니다.

배선 레시피 · 라이브러리#

직접 WebAuthn 구현은 까다롭고 보안 함정이 많습니다 — 검증된 라이브러리/매니지드 서비스를 권합니다:

  • 셀프호스팅: @simplewebauthn/server + @simplewebauthn/browser (RP 의식·검증).
  • 매니지드: Auth0 · Hanko · Corbado · Stytch (패스키 + 폴백 + 복구 포함).
  • 서버 설정 핵심: RP ID = 도메인(서브도메인 주의), origin 검증, signature_count로 복제 탐지.

폴백 · 복구 · 다중기기#

  • 폴백 필수 — 패스키 불가(구형 브라우저·기기) 시 비밀번호/매직링크/SSO로 회귀.
  • 복구 — 모든 패스키 분실 대비: 신뢰 기기·이메일 복구 링크·관리자 재설정 중 최소 하나.
  • 다중기기 — 동기화 제공자(iCloud/Google)가 같은 계정 기기에 자동 전파 + 크로스디바이스(QR)로 비동기 기기 커버.

적용 체크리스트#

  • RP ID·origin 서버 설정
  • 등록 진입점 2곳(설정 + 1차 로그인 직후 권유)
  • conditional UI(이메일 필드 autocomplete="username webauthn")
  • 명시 패스키 버튼(AuthMethodButton method="passkey")을 보완으로
  • 폴백 방식(비번·매직링크·SSO) 항상 제공
  • 복구 경로 1개 이상
  • LoginMethods로 방식 동등 노출(prominence parity) — 한 방식을 3차로 묻지 않기
  • 접근성: 44px 타깃·라벨·포커스 순서(접근성)

참고#

W3C WebAuthn Level 2 · passkeys.dev · FIDO Alliance · web.dev — 패스키 자동완성 · Apple — AuthenticationServices · Android — Credential Manager · SimpleWebAuthn