이 글의 요점은 단순하다.
로그인 UX의 경쟁력은 이제 "입력을 잘 받는 폼"보다 "입력을 덜 하게 만드는 인증"에서 나온다.
Chrome DevBlog와 구현 가이드를 바탕으로, 번역 재작성 대신 "제품에 붙일 때 실패하는 지점"만 정리한다.
비밀번호 인증은 줄어드는 방향이 맞다. Microsoft도 개인 계정에서 passwordless 전환을 공식 가이드로 안내한다(공식 문서).
업계 메시지는 꽤 분명하다. "아이디/비밀번호를 더 잘 입력하게 하는 UX"보다 "비밀번호 자체를 덜 쓰는 인증 UX"로 이동 중이라는 것이다.
TL;DR
- 대상 브라우저는 Chrome 149+를 기준으로 잡는다.
mediation: 'immediate'는 OT 방식이고, stable은uiMode: 'immediate'다.- 이 기능이 동작하려면 사용자의 브라우저/기기에 패스키 또는 저장 비밀번호가 사전 연결돼 있어야 한다.
- WebView는 제약이 크다. 앱 내 WebView 로그인은 별도 검증 없이 바로 확대하지 않는다.
- 적용 포인트는 API 한 줄이 아니라 사전 등록(credential 생성) + 로그인 호출 + fallback UX다.
인증의 방향이 왜 바뀌는가
사용자는 이미 매일 기기 잠금(지문/얼굴/PIN)으로 신원을 확인한다. 로그인도 같은 방향으로 수렴하는 것이 자연스럽다.
여기서 중요한 기술적 구분이 있다.
- 생체정보 원본을 서비스 서버로 보내 인증하는 모델이 아니다.
- 기기 내부 인증을 통과하면, 기기/플랫폼에 저장된 자격증명(패스키) 사용 권한이 열리는 모델이다.
- 즉 "생체정보 수집"보다 "로컬 보안 경계에서 키 사용 승인"에 가깝다.
이 구분을 이해하면 보안팀, 기획, 고객 커뮤니케이션 문구가 훨씬 명확해진다.
왜 이게 편한가 (그리고 왜 오해가 생기나)
사용자 입장에서 편한 이유는 간단하다. 긴 비밀번호를 기억하고 입력하는 대신, 이미 매일 쓰는 기기 잠금(지문/얼굴/PIN)으로 인증할 수 있기 때문이다.
다만 표현은 정확히 구분해야 한다.
- 맞는 말: 모바일에서 지문/얼굴로 인증하는 흐름은 이미 보편적이다.
- 보완할 점: 서버에 지문 원본이 전달되는 게 아니라, 기기 내부의 인증수단으로 패스키(개인키 사용 권한)를 해제하는 방식이다.
- 즉 "제3에 생체정보를 저장해서 인증"이라기보다, "기기(또는 플랫폼 계정 동기화 저장소)에 있는 자격증명을 기기 생체인증으로 사용 승인"에 가깝다.
먼저 제약사항부터
1) 브라우저/버전 제약
- 본문 기준 레퍼런스는 Chrome 문서의 Chrome 149+ 흐름이다.
- 지원 여부는 런타임에서
PublicKeyCredential.getClientCapabilities()로 확인한다. - 미지원 환경(Safari/Firefox/구버전 Chrome)은 기존 로그인 흐름으로 즉시 fallback 한다.
2) 사전 연결(등록) 전제
- immediate UI는 "없는 인증수단을 만들어 주는 기능"이 아니다.
- 사용자가 이전에 패스키를 만들었거나, 브라우저 비밀번호 매니저에 자격증명이 있어야 의미가 있다.
- 즉, 롤아웃은 보통
등록률(credential 보유율)을 먼저 보고 시작한다.
3) WebView 제약
- WebView는 브라우저와 동일한 동작을 보장하지 않는다.
- 인증 저장소 접근, 사용자 제스처, 계정 선택 UI 노출에서 차이가 날 수 있다.
- 모바일 앱 WebView는 "지원 가정" 대신 실측 매트릭스로 판단해야 한다.
API 변경 핵심 (OT → stable)
Origin Trial 시절(레거시):
// ❌ Chrome 149+에서 immediate 트리거 아님
navigator.credentials.get({
mediation: 'immediate',
// ...
});
Chrome 149+:
navigator.credentials.get({
publicKey: { /* ... */ },
uiMode: 'immediate',
});
OT 코드가 남아 있으면 프로덕션에서 조용히 실패할 수 있으니 diff를 먼저 제거한다.
구현 순서 (실무 버전)
1) 인증수단 연결(등록) API 먼저 준비
로그인 API보다 먼저 "연결" 경로를 만든다.
// 예시: 등록(create) 흐름
const credential = await navigator.credentials.create({
publicKey: publicKeyCreationOptionsFromServer,
});
// 서버에 attestation/credential 저장
- 등록이 끝나야
navigator.credentials.get()에서 고를 credential이 생긴다. - 제품 UX에서는 "기기 인증수단 연결하기" 단계를 명시해 둔다.
2) 로그인 시 capability 확인 후 immediate 호출
const caps = await PublicKeyCredential.getClientCapabilities?.();
const supportsImmediate = caps?.immediateGet === true;
if (!supportsImmediate) {
return openDefaultLoginFlow();
}
try {
const cred = await navigator.credentials.get({
uiMode: 'immediate',
password: true,
publicKey: publicKeyRequestOptionsFromServer,
});
return verifyCredentialOnServer(cred);
} catch (error) {
return openDefaultLoginFlow();
}
3) 오류/예외는 즉시 fallback
NotAllowedError는 흔한 정상 케이스(선택 안 함/없음)로 취급하고 기본 로그인 UI를 연다.- 네트워크/서버 검증 실패는 분리 로그를 남긴다.
- 에러 문구보다 "다른 로그인 수단으로 계속하기" CTA가 더 중요하다.
테스트 매트릭스 (최소)
- Chrome 149+ / credential 있음 / 없음
- Chrome 149+ incognito
- Safari / Firefox fallback
- Android/iOS 인앱 WebView (지원 여부 확인)
관측 포인트
- immediate 시도 → 성공/취소/실패 이벤트 로깅 (PII 없이)
- 등록 완료율(credential 생성 성공률)
- WebView vs 브라우저 성공률 분리
- A/B: 로그인 완료율, 이탈률, fallback 진입률
도입 전략: 어디부터 붙일까
- 전면 치환보다 Chrome 149+ + 웹 브라우저 진입 로그인부터 시작한다.
- 성공률이 나오는 구간(credential 보유 사용자)에서 먼저 체감을 만든다.
- WebView는 별도 트랙으로 측정하고, 결과가 쌓일 때까지 기본 로그인을 유지한다.
- KPI는 "신기능 노출 수"보다 "완료율/이탈률/fallback 진입률"로 본다.
마치며
Immediate UI는 멋진 데모 기능이 아니라, 로그인 비용을 줄이는 실무 기능이다.
다만 전제(버전, 사전 등록, WebView 현실)를 모르면 기능을 붙여도 성과가 잘 안 나온다.
한 줄 결: 지금은 비밀번호를 더 잘 받는 시대가 아니라, 비밀번호를 덜 쓰게 만드는 인증을 설계하는 시대다.