이 가이드에서는 비밀번호 기반 로그인에 성공한 직후 사용자의 패스키를 자동으로 등록하여 마찰을 최소화하고 패스키 채택을 늘리는 방법을 자세히 설명합니다.
적절한 트리거 순간
자동 패스키 생성(조건부 생성 또는 자동 로그인 후 프로모션이라고도 함)은 비밀번호를 포함한 전체 로그인이 성공한 직후에만 트리거되어야 합니다(MUST).
- 비밀번호 없는 흐름 (예: 매직 링크, SMS OTP, ID 제휴)에 조건부 생성을 시도하지 마세요.
- 다중 인증이 필요한 경우 조건부 생성을 시작하기 전에 모든 인증이 완료될 때까지 기다려야 합니다(MUST).
- 생성 엔드포인트에 요청하기 전에 유효하고 인증된 사용자 세션이 활성 상태인지 확인합니다.
구현 단계
1. 이전 자동 완성 작업 중단
로그인 페이지에서 양식 자동 완성 (조건부 UI/Get)을 사용하는 경우 브라우저 충돌을 방지하기 위해 활성 사용자 인증 정보 가져오기 호출을 중단해야 합니다.
navigator.credentials.create()를 호출하기 전에 대기 중인navigator.credentials.get()자동 완성 요청에 연결된AbortController에서abortController.abort()를 호출합니다.
2. 기능 감지
PublicKeyCredential.getClientCapabilities()로 conditionalCreate를 확인하여 조건부 생성을 사용할 수 있는지 확인합니다.
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalCreate) {
// Conditional create is available
}
3. 조건부 생성으로 패스키 만들기
navigator.credentials.create()옵션 내에서mediation: 'conditional'를 전달합니다. 이렇게 하면 브라우저가 방해가 되는 모달 대화상자를 표시하지 않고 백그라운드에서 또는 상황에 맞게 패스키 생성 흐름을 자동으로 처리합니다.- 중복 키가 등록되지 않도록 사용자의 기존 패스키 사용자 인증 정보 ID로
excludeCredentials를 채웁니다.
4. 자동 오류 처리
- 패스키 생성 프롬프트 (
navigator.credentials.create)를 try/catch 블록으로 래핑합니다. 사용자에게 오류 UI를 렌더링하지 않고 일반적인 사용자 대상 예외(InvalidStateError,NotAllowedError,AbortError)를 포착하고 자동으로 무시해야 합니다(MUST).
5. 서버 측 사용자 현재 상태 확인
- 서버 측 확인 엔드포인트는 조건부 생성 트리거에 의해 생성된 사용자 인증 정보를 확인할 때만 사용자 참여(UP) 요구사항(
requireUserPresence: false)을 완화해야 합니다(MUST). 표준 명시적 생성의 경우 엄격한 현재 상태 확인이 활성 상태로 유지되어야 합니다.
6. 서버 인증 실패를 원활하게 처리
navigator.credentials.create()가 성공했지만 서버 인증 가져오기에서 잘못된 응답을 반환하는 경우 (예: 서명 확인 실패)PublicKeyCredential.signalUnknownCredential()를 호출하여 분리된 사용자 인증 정보가 패스키 제공업체에 남아 있지 않도록 합니다.
코드 예시
// optionsFetch and registerVerifyFetch are app-defined server endpoint requests
import { optionsFetch, registerVerifyFetch } from './api.js';
async function triggerConditionalCreate(loginAbortController) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalCreate !== true) {
return; // Platform does not support conditional creation
}
// 1. Abort any active autofill conditional-get controllers to clear the WebAuthn pipeline
loginAbortController.abort();
// 2. Fetch creation options signaling the backend that this is a conditional request
const creationOptionsJSON = await optionsFetch({ conditional: true });
const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);
let credential;
try {
// 3. Invoke silent credentials creation prompt
credential = await navigator.credentials.create({
publicKey,
mediation: 'conditional' // Silent background creation mediation
});
} catch (e) {
// 4. Silently swallow common WebAuthn browser exceptions
if (['InvalidStateError', 'NotAllowedError', 'AbortError'].includes(e.name)) {
return;
}
console.error('Unexpected conditional create error:', e);
return;
}
// 5. Server verification step using dedicated Try/Catch block
let encodedResponse = credential.toJSON();
try {
const response = await registerVerifyFetch(encodedResponse);
if (!response.ok) {
// If the server verification fails, clean up using Signal API
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedResponse.id
});
}
}
} catch (serverErr) {
console.error('Verification network failure:', serverErr);
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedResponse.id
});
}
}
}
대체 전략
패스키 기능 감지 대체
항상 'webauthn-polyfills'를 설치하고 컨텍스트에서 가져옵니다.
PublicKeyCredential가 지원되는 한 PublicKeyCredential.getClientCapabilities도 지원됩니다.
import 'webauthn-polyfills';
신호 API 동기화 대체
WebAuthn Signal API (webauthn-signals)는 비밀번호 관리자를 서버 사용자 인증 정보 상태와 동기화하는 데 사용되는 점진적 최적화입니다.
- 대체 환경:
if (PublicKeyCredential.signalUnknownCredential)를 통해 제한됩니다. 지원되지 않는 경우 백그라운드 인증 동기화는 브라우저 예외를 발생시키지 않고 정상적으로 우회됩니다.
간편한 JSON 직렬화 대체
항상 'webauthn-polyfills'를 설치하고 컨텍스트에서 가져옵니다.
PublicKeyCredential가 지원되는 한 PublicKeyCredential.parseCreationOptionsFromJSON 및 PublicKeyCredential.prototype.toJSON도 지원됩니다.
import 'webauthn-polyfills';