기존 비밀번호 사용자를 수용하면서 패스키를 활용하는 로그인 환경을 만듭니다.
이 가이드에서는 양식 자동 완성을 사용하여 사용자가 비밀번호와 함께 패스키로 로그인할 수 있도록 허용하는 방법을 설명합니다. 양식 자동 완성을 사용하면 통합된 로그인 환경이 만들어져 비밀번호에서 더 안전하고 사용자 친화적인 패스키 인증 방법으로의 전환이 간소화됩니다.
기존 로그인 양식에서 최소한의 불편을 끼치면서 패스키 사용자와 비밀번호 사용자를 모두 지원하도록 WebAuthn의 조건부 UI를 구현하는 방법을 알아보세요.
양식 자동 완성을 통해 패스키로 로그인해야 하는 이유는 무엇인가요?
패스키를 사용하면 사용자가 지문, 얼굴, 기기 PIN을 사용하여 웹사이트에 로그인할 수 있습니다.
모든 사용자에게 패스키가 있는 경우 인증 흐름은 단일 로그인 버튼이 될 수 있습니다. 이 버튼을 탭하면 사용자가 화면 잠금으로 계정을 직접 인증하고 로그인할 수 있습니다.
하지만 비밀번호에서 패스키로 전환하는 데는 어려움이 있습니다. 이 기간 동안 웹사이트는 비밀번호 사용자와 패스키 사용자를 모두 지원해야 합니다. 사용자가 패스키를 사용하는 사이트를 기억하고 사전에 로그인 방법을 선택하도록 요청하면 사용자 경험이 저하됩니다.
패스키도 새로운 기술이므로 명확하게 설명하기가 쉽지 않을 수 있습니다. 익숙한 자동 완성 인터페이스를 사용하면 전환 문제와 사용자 친숙성 요구사항을 모두 해결할 수 있습니다.
조건부 UI 사용
패스키 사용자와 비밀번호 사용자를 모두 효과적으로 지원하려면 양식의 자동 완성 추천에 패스키를 포함하세요. 이 접근 방식은 WebAuthn 표준의 기능인 조건부 UI를 사용합니다.
사용자가 사용자 이름 입력란에 포커스를 맞추면 저장된 비밀번호와 함께 저장된 패스키를 제안하는 자동 완성 대화상자가 표시됩니다. 사용자는 패스키 또는 비밀번호 중 하나를 선택하고 패스키를 선택한 경우 기기 화면 잠금을 사용하여 로그인할 수 있습니다.
이렇게 하면 사용자가 기존 로그인 양식으로 웹사이트에 로그인할 수 있지만 패스키가 있는 경우 패스키의 보안 이점이 추가됩니다.
패스키 인증 작동 방식
패스키로 인증하려면 WebAuthn API를 사용합니다.
패스키 인증 흐름의 네 가지 구성요소는 다음과 같습니다.
- 백엔드: 공개 키를 비롯한 사용자 계정 세부정보를 저장합니다.
- 프런트엔드: 브라우저와 통신하고 백엔드에서 필요한 데이터를 가져옵니다.
- 브라우저: JavaScript를 실행하고 WebAuthn API와 상호작용합니다.
- 패스키 제공업체: 패스키를 생성하고 저장합니다. 일반적으로 Google 비밀번호 관리자와 같은 비밀번호 관리자 또는 보안 키입니다.

패스키 인증 프로세스는 다음 흐름을 따릅니다.
- 사용자가 로그인 페이지를 방문하면 프런트엔드가 백엔드에 인증 챌린지를 요청합니다.
- 백엔드는 사용자의 계정과 연결된 WebAuthn 챌린지를 생성하고 반환합니다.
- 프런트엔드는 브라우저를 사용하여 인증을 시작하기 위해 챌린지를 사용하여
navigator.credentials.get()
를 호출합니다. - 브라우저는 패스키 제공업체와 상호작용하여 사용자에게 패스키를 선택하고 (자주 로그인 입력란에 포커스를 맞춰 트리거된 자동 완성 대화상자를 사용함) 기기 화면 잠금 또는 생체 인식을 사용하여 신원을 확인하라는 메시지를 표시합니다.
- 사용자 인증에 성공하면 패스키 제공업체가 챌린지에 서명하고 브라우저는 결과 공개 키 사용자 인증 정보(서명 포함)를 프런트엔드로 반환합니다.
- 프런트엔드는 이 사용자 인증 정보를 백엔드로 전송합니다.
- 백엔드는 사용자의 저장된 공개 키를 기준으로 사용자 인증 정보의 서명을 확인합니다. 인증에 성공하면 백엔드에서 사용자를 로그인합니다.
양식 자동 완성을 통해 패스키로 인증
양식 자동 완성을 사용하여 패스키 인증을 시작하려면 로그인 페이지가 로드될 때 조건부 WebAuthn get
호출을 실행합니다. 이 navigator.credentials.get()
호출에는 mediation: 'conditional'
옵션이 포함됩니다.
WebAuthn의 navigator.credentials.get()
API에 대한 조건부 요청은 즉시 UI를 표시하지 않습니다. 대신 사용자가 사용자 이름 필드의 자동 완성 메시지와 상호작용할 때까지 대기 중 상태로 유지됩니다. 사용자가 패스키를 선택하면 브라우저는 기존 양식 제출을 우회하여 사용자를 로그인할 사용자 인증 정보로 대기 중인 약속을 확인합니다. 사용자가 대신 비밀번호를 선택하면 약속이 확인되지 않고 표준 비밀번호 로그인 흐름이 계속됩니다. 그러면 페이지에서 사용자를 로그인해야 합니다.
양식 입력란에 주석 추가
패스키 자동 완성을 사용 설정하려면 양식의 사용자 이름 input
필드에 autocomplete
속성을 추가합니다. username
및 webauthn
를 공백으로 구분된 값으로 모두 포함합니다.
<input type="text" name="username" autocomplete="username webauthn" autofocus>
이 필드에 autofocus
를 추가하면 페이지가 로드될 때 자동 완성 메시지가 트리거되어 사용 가능한 비밀번호와 패스키가 즉시 표시됩니다.
기능 감지
조건부 WebAuthn API 호출을 호출하기 전에 다음을 확인합니다.
- 브라우저가
PublicKeyCredential
를 사용하여 WebAuthn을 지원합니다.
- 브라우저가
PublicKeyCredential.isConditionalMediationAvailable()
를 사용하여 WebAuthn 조건부 UI를 지원합니다.
다음 스니펫은 브라우저에서 이러한 기능을 지원하는지 확인하는 방법을 보여줍니다.
// Availability of window.PublicKeyCredential means WebAuthn is usable.
if (window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable) {
// Check if conditional mediation is available.
const isCMA = await PublicKeyCredential.isConditionalMediationAvailable();
if (isCMA) {
// Call WebAuthn authentication
}
}
백엔드에서 정보 가져오기
백엔드는 navigator.credentials.get()
호출을 시작하기 위해 프런트엔드에 여러 옵션을 제공해야 합니다. 이러한 옵션은 일반적으로 서버의 엔드포인트에서 JSON 객체로 가져옵니다.
options 객체의 주요 속성은 다음과 같습니다.
challenge
: ArrayBuffer의 서버 생성 챌린지입니다 (일반적으로 JSON 전송을 위해 Base64URL로 인코딩됨). 이는 재전송 공격을 방지하는 데 필수적입니다. 서버는 로그인 시도마다 새 챌린지를 생성해야 하며, 잠시 후 또는 시도가 실패하면 챌린지를 무효화해야 합니다.allowCredentials
: 사용자 인증 정보 설명자의 배열입니다. 빈 배열을 전달합니다. 그러면 브라우저가 지정된rpId
의 모든 사용자 인증 정보를 나열합니다.userVerification
: 기기 화면 잠금 요구와 같은 사용자 인증 환경설정을 지정합니다. 기본값 및 권장값은"preferred"
입니다. 가능한 값은 다음과 같습니다."required"
: 인증자가 사용자 인증을 실행해야 합니다(예: PIN 또는 생체 인식). 확인을 실행할 수 없는 경우 작업이 실패합니다."preferred"
: 인증자가 사용자 인증을 시도하지만 인증 없이도 작업이 성공할 수 있습니다."discouraged"
: 인증자는 가능하면 사용자 확인을 피해야 합니다.
rpId
: 신뢰 당사자 ID입니다(일반적으로 웹사이트의 도메인(예:example.com
)). 이 값은 패스키 사용자 인증 정보가 생성될 때 사용된rp.id
와 정확히 일치해야 합니다.
서버에서 이 옵션 객체를 생성해야 합니다. ArrayBuffer
값 (예: challenge
)은 JSON 전송을 위해 Base64URL로 인코딩되어야 합니다. 프런트엔드에서 JSON을 파싱한 후 PublicKeyCredential.parseRequestOptionsFromJSON()
를 사용하여 객체 (Base64URL 문자열 디코딩 포함)를 navigator.credentials.get()
가 예상하는 형식으로 변환합니다.
다음 코드 스니펫은 패스키로 인증하는 데 필요한 정보를 가져와 디코딩하는 방법을 보여줍니다.
// Fetch an encoded PubicKeyCredentialRequestOptions from the server.
const _options = await fetch('/webauthn/signinRequest');
// Deserialize and decode the PublicKeyCredentialRequestOptions.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseRequestOptionsFromJSON(decoded_options);
...
conditional
플래그를 사용하여 WebAuthn API를 호출하여 사용자 인증
publicKeyCredentialRequestOptions
객체 (아래 예시 코드에서는 options
라고 함)가 준비되면 navigator.credentials.get()
를 호출하여 조건부 패스키 인증을 시작합니다.
// To abort a WebAuthn call, instantiate an AbortController.
const abortController = new AbortController();
// Invoke WebAuthn to authenticate with a passkey.
const credential = await navigator.credentials.get({
publicKey: options,
signal: abortController.signal,
// Specify 'conditional' to activate conditional UI
mediation: 'conditional'
});
이 호출의 주요 매개변수는 다음과 같습니다.
publicKey
: 이전 단계에서 서버에서 가져와 처리한publicKeyCredentialRequestOptions
객체(예:options
)여야 합니다.signal
:AbortController
의 신호 (예:abortController.signal
)를 전달하면 프로그래매틱 방식으로get()
요청을 취소할 수 있습니다. 이는 다른 WebAuthn 호출을 호출하려는 경우에 유용합니다.mediation: 'conditional'
: WebAuthn 호출을 조건부로 만드는 중요한 플래그입니다. 모달 대화상자를 즉시 표시하는 대신 자동 완성 메시지와의 사용자 상호작용을 기다리도록 브라우저에 지시합니다.
반환된 공개 키 사용자 인증 정보를 RP 서버로 전송
사용자가 패스키를 선택하고 본인 인증을 완료하면 (예: 기기 화면 잠금 사용) navigator.credentials.get()
약속이 해결됩니다. 그러면 프런트엔드에 PublicKeyCredential
객체가 반환됩니다.
약속은 여러 가지 이유로 거부될 수 있습니다. 코드에서 Error
객체의 name
속성을 확인하여 이러한 오류를 처리해야 합니다.
NotAllowedError
: 사용자가 작업을 취소했거나 패스키가 선택되지 않았습니다.AbortError
:AbortController
를 사용하는 코드에 의해 작업이 중단되었습니다.- 기타 예외: 예기치 않은 오류가 발생했습니다. 브라우저는 일반적으로 사용자에게 오류 대화상자를 표시합니다.
PublicKeyCredential
객체에는 여러 속성이 포함되어 있습니다. 인증과 관련된 주요 속성은 다음과 같습니다.
id
: 인증된 패스키 사용자 인증 정보의 base64url로 인코딩된 ID입니다.rawId
: 사용자 인증 정보 ID의 ArrayBuffer 버전입니다.response.clientDataJSON
: 클라이언트 데이터의 ArrayBuffer입니다. 이 입력란에는 챌린지 및 서버에서 확인해야 하는 출처 등의 정보가 포함됩니다.response.authenticatorData
: 인증자 데이터의 ArrayBuffer입니다. 이 필드에는 RP ID와 같은 정보가 포함됩니다.response.signature
: 서명이 포함된 ArrayBuffer입니다. 이 값은 사용자 인증 정보의 핵심이며 서버는 저장된 사용자 인증 정보의 공개 키를 사용하여 이 서명을 확인해야 합니다 .response.userHandle
: 패스키 등록 중에 제공된 사용자 ID가 포함된 ArrayBuffer입니다.authenticatorAttachment
: 인증자가 클라이언트 기기의 일부인지 (platform
) 아니면 외부인지 (cross-platform
) 나타냅니다.cross-platform
첨부파일은 사용자가 휴대전화로 로그인한 경우 발생할 수 있습니다. 이 경우 향후 편의를 위해 현재 기기에서 패스키를 생성하라는 메시지를 표시하는 것이 좋습니다.type
: 이 필드는 항상"public-key"
로 설정됩니다.
이 PublicKeyCredential
객체를 백엔드로 전송하려면 먼저 .toJSON()
메서드를 호출합니다. 이 메서드는 JSON 직렬화 가능한 버전의 사용자 인증 정보를 생성하며, 이 버전은 ArrayBuffer
속성(예: rawId
, clientDataJSON
, authenticatorData
, signature
, userHandle
)을 Base64URL 인코딩된 문자열로의 변환을 올바르게 처리합니다. 그런 다음 JSON.stringify()
를 사용하여 이 객체를 문자열로 변환하고 요청 본문에서 서버로 전송합니다.
...
// Encode and serialize the PublicKeyCredential.
const _result = credential.toJSON();
const result = JSON.stringify(_result);
// Encode and send the credential to the server for verification.
const response = await fetch('/webauthn/signinResponse', {
method: 'post',
credentials: 'same-origin',
body: result
});
서명 확인
백엔드 서버는 공개 키 사용자 인증 정보를 수신하면 인증 정보를 확인해야 합니다. 여기에는 다음 항목이 포함됩니다.
- 사용자 인증 정보 데이터 파싱
- 사용자 인증 정보의
id
와 연결된 저장된 공개 키를 조회합니다. - 저장된 공개 키와 수신된
signature
를 확인합니다. - 챌린지 및 출처와 같은 기타 데이터 검증
이러한 암호화 작업을 안전하게 처리하려면 서버 측 FIDO/WebAuthn 라이브러리를 사용하는 것이 좋습니다. awesome-webauthn GitHub 저장소에서 오픈소스 라이브러리를 찾을 수 있습니다.
서명과 다른 모든 어설션이 유효하면 서버에서 사용자를 로그인할 수 있습니다. 자세한 서버 측 유효성 검사 단계는 서버 측 패스키 인증을 참고하세요.
백엔드에서 일치하는 사용자 인증 정보가 없으면 신호를 보냅니다.
로그인 중에 백엔드 서버에서 일치하는 ID가 있는 사용자 인증 정보를 찾을 수 없는 경우, 사용자가 이전에 서버에서 이 패스키를 삭제했지만 패스키 제공업체에서는 삭제하지 않았을 수 있습니다. 패스키 제공업체가 더 이상 사이트에서 작동하지 않는 패스키를 계속 제안하는 경우 이러한 불일치로 인해 혼란스러운 사용자 환경이 발생할 수 있습니다. 이를 개선하려면 패스키 제공업체에 고아 패스키를 삭제하도록 신호를 보내야 합니다.
Webauthn Signal API의 일부인 PublicKeyCredential.signalUnknownCredential()
메서드를 사용하여 패스키 제공업체에 지정된 사용자 인증 정보가 삭제되었거나 존재하지 않음을 알릴 수 있습니다. 서버에서 제공된 사용자 인증 정보 ID를 알 수 없다고 나타내면 (예: 404와 같은 특정 HTTP 상태 코드 사용) 클라이언트 측에서 이 정적 메서드를 호출합니다. 이 메서드에 RP ID와 알 수 없는 사용자 인증 정보 ID를 제공합니다. 패스키 제공업체(신호를 지원하는 경우)는 패스키를 삭제해야 합니다.
// Detect authentication failure due to lack of the credential
if (response.status === 404) {
// Feature detection
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId: "example.com",
credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
});
} else {
// Encourage the user to delete the passkey from the password manager nevertheless.
...
}
}
인증 후
사용자가 로그인한 방식에 따라 따라야 할 흐름이 다릅니다.
사용자가 패스키 없이 로그인한 경우
사용자가 패스키 없이 웹사이트에 로그인한 경우 해당 계정이나 현재 기기에 패스키가 등록되어 있지 않을 수 있습니다. 패스키 생성을 장려하기에 좋은 기회입니다. 다음 접근 방식을 고려해 보세요.
- 비밀번호를 패스키로 업그레이드: 비밀번호 로그인이 완료된 후 브라우저에서 사용자의 패스키를 자동으로 생성할 수 있는 WebAuthn 기능인 조건부 생성을 사용합니다. 이렇게 하면 생성 프로세스가 간소화되어 패스키 채택이 크게 개선될 수 있습니다. 사용자가 패스키를 더 원활하게 채택하도록 지원에서 작동 방식과 구현 방법을 알아보세요.
- 패스키 생성을 수동으로 요청: 사용자에게 패스키를 생성하도록 권장합니다. 이는 사용자가 다중 인증 (MFA)과 같이 더 복잡한 로그인 프로세스를 완료한 후에 효과적일 수 있습니다. 하지만 사용자 환경에 방해가 될 수 있는 과도한 메시지는 피하세요."
사용자가 패스키를 만들도록 유도하고 다른 권장사항을 알아보는 방법은 사용자에게 패스키 전달하기의 예시를 참고하세요.
사용자가 패스키로 로그인한 경우
사용자가 패스키로 로그인한 후에는 환경을 더욱 개선하고 계정 일관성을 유지할 수 있는 여러 기회가 있습니다.
교차 기기 인증 후 새 패스키 만들기 권장
사용자가 교차 기기 메커니즘 (예: 휴대전화로 QR 코드 스캔)을 사용하여 패스키로 로그인하는 경우, 사용자가 사용한 패스키가 로그인하는 기기에 로컬로 저장되지 않을 수 있습니다. 다음과 같은 상황에서 상품이 반환될 수 있습니다.
- 패스키가 있지만 로그인 운영체제 또는 브라우저를 지원하지 않는 패스키 제공업체에 있습니다.
- 로그인하는 기기에서 패스키 제공업체에 액세스할 수 없게 되었지만 다른 기기에서는 패스키를 계속 사용할 수 있습니다.
이 경우 사용자에게 현재 기기에서 새 패스키를 만들라는 메시지를 표시하는 것이 좋습니다. 이렇게 하면 향후 교차 기기 로그인 프로세스를 반복하지 않아도 됩니다. 사용자가 교차 기기 패스키를 사용하여 로그인했는지 확인하려면 사용자 인증 정보의 authenticatorAttachment
속성을 확인합니다. 값이 "cross-platform"
이면 교차 기기 인증을 나타냅니다. 이 경우 새 패스키를 만드는 편리함을 설명하고 생성 과정을 안내합니다.
신호를 사용하여 패스키 세부정보를 제공업체와 동기화
일관성과 더 나은 사용자 환경을 보장하기 위해 신뢰 당사자 (RP)는 WebAuthn Signals API를 사용하여 사용자 인증 정보 및 사용자 정보에 관한 업데이트를 패스키 제공업체에 전달할 수 있습니다.
예를 들어 패스키 제공업체의 사용자 패스키 목록을 정확하게 유지하려면 백엔드의 사용자 인증 정보를 동기화 상태로 유지합니다. 패스키 제공업체가 불필요한 패스키를 삭제할 수 있도록 패스키가 더 이상 존재하지 않음을 알릴 수 있습니다.
마찬가지로 사용자가 서비스에서 사용자 이름 또는 표시 이름을 업데이트하면 신호를 보내 패스키 제공업체에서 표시하는 사용자 정보 (예: 계정 선택 대화상자)를 최신 상태로 유지할 수 있습니다.
패스키를 일관되게 유지하기 위한 권장사항에 관한 자세한 내용은 Signal API를 사용하여 패스키를 서버의 사용자 인증 정보와 일치시키기를 참고하세요.
두 번째 인증 요소를 요청하지 않음
패스키는 피싱과 같은 일반적인 위협으로부터 강력한 내장 보호 기능을 제공합니다. 따라서 두 번째 인증 요소는 상당한 보안 가치를 추가하지 않습니다. 대신 로그인 중에 사용자에게 불필요한 단계가 생성됩니다.
체크리스트
- 사용자가 양식 자동 완성을 통해 패스키로 로그인할 수 있도록 허용합니다.
- 백엔드에서 패스키의 일치하는 사용자 인증 정보를 찾을 수 없는 경우 신호를 보냅니다.
- 사용자가 로그인한 후 아직 패스키를 만들지 않은 경우 패스키를 수동으로 만들라는 메시지를 표시합니다.
- 사용자가 비밀번호 (및 보조 인증 수단)로 로그인한 후 패스키를 자동으로 생성합니다 (조건부 생성).
- 사용자가 교차 기기 패스키로 로그인한 경우 로컬 패스키 생성을 요청합니다.
- 로그인 후 또는 변경사항이 발생할 때 제공업체에 사용 가능한 패스키 목록과 업데이트된 사용자 세부정보 (사용자 이름, 표시 이름)를 전달합니다.