패스키를 사용하면 사용자 계정을 더 안전하고 간편하고 쉽게 사용할 수 있습니다.
게시일: 2022년 10월 12일, 최종 업데이트: 2026년 4월 9일
패스키를 사용하면 보안이 강화되고, 로그인이 간소화되며, 비밀번호가 대체됩니다. 사용자가 기억하고 수동으로 입력해야 하는 일반 비밀번호와 달리 패스키는 생체 인식이나 PIN과 같은 기기의 화면 잠금 메커니즘을 사용하여 피싱 위험과 사용자 인증 정보 도용을 줄입니다.
패스키는 Google 비밀번호 관리자 및 iCloud 키체인과 같은 패스키 제공업체를 사용하여 여러 기기에서 동기화됩니다.
패스키는 비공개 키를 패스키 제공업체에 안전하게 저장하고 인증을 위해 필요한 메타데이터와 공개 키를 서버에 저장하여 생성해야 합니다. 비공개 키는 유효한 도메인에서 사용자 확인 후 서명을 발급하여 패스키가 피싱에 강하도록 합니다. 공개 키는 민감한 사용자 인증 정보를 저장하지 않고 서명을 확인하므로 패스키는 사용자 인증 정보 도난에 강합니다.
패스키 생성 방식
사용자가 패스키로 로그인할 수 있도록 하려면 먼저 패스키를 만들고 이를 사용자 계정과 연결한 후 패스키의 공개 키를 서버에 저장해야 합니다.
다음과 같은 상황에서 사용자에게 패스키를 만들도록 요청할 수 있습니다.
- 가입 중 또는 가입 후
- 로그인한 후
- 다른 기기의 패스키를 사용하여 로그인한 후 (즉,
authenticatorAttachment이cross-platform인 경우) - 사용자가 패스키를 관리할 수 있는 전용 페이지
패스키를 만들려면 WebAuthn API를 사용합니다.
패스키 등록 흐름의 네 가지 구성요소는 다음과 같습니다.
- 백엔드: 공개 키를 비롯한 사용자 계정 세부정보를 저장합니다.
- 프런트엔드: 브라우저와 통신하고 백엔드에서 필요한 데이터를 가져옵니다.
- 브라우저: JavaScript를 실행하고 WebAuthn API와 상호작용합니다.
- 패스키 제공업체: 패스키를 생성하고 저장합니다. 일반적으로 Google 비밀번호 관리자와 같은 비밀번호 관리자 또는 보안 키입니다.
패스키를 만들기 전에 시스템이 다음 기본 요건을 충족하는지 확인하세요.
사용자 계정은 의미 있게 짧은 기간 내에 안전한 방법 (예: 이메일, 전화 인증 또는 ID 제휴)을 통해 인증됩니다.
프런트엔드와 백엔드는 안전하게 통신하여 사용자 인증 정보 데이터를 교환할 수 있습니다.
브라우저에서 WebAuthn 및 패스키 생성을 지원합니다.
다음 섹션에서는 대부분의 항목을 확인하는 방법을 보여줍니다.
시스템이 이 조건을 충족하면 다음 프로세스가 실행되어 패스키가 생성됩니다.
- 사용자가 작업을 시작하면 (예: 패스키 관리 페이지에서 '패스키 만들기' 버튼을 클릭하거나 등록을 완료한 후) 시스템에서 패스키 생성 프로세스를 트리거합니다.
- 프런트엔드는 중복을 방지하기 위해 사용자 정보, 챌린지, 인증 정보 ID를 비롯한 필요한 인증 정보 데이터를 백엔드에 요청합니다.
- 프런트엔드는
navigator.credentials.create()를 호출하여 기기의 패스키 제공업체에 백엔드의 정보를 사용하여 패스키를 생성하라는 메시지를 표시합니다. 이 호출은 프로미스를 반환합니다. - 사용자의 기기에서 생체 인식 방법, PIN 또는 패턴을 사용하여 사용자를 인증하여 패스키를 생성합니다.
- 패스키 제공업체는 패스키를 생성하고 프런트엔드에 공개 키 사용자 인증 정보를 반환하여 약속을 해결합니다.
- 프런트엔드는 생성된 공개 키 사용자 인증 정보를 백엔드로 전송합니다.
- 백엔드는 향후 인증을 위해 공개 키와 기타 중요한 데이터를 저장합니다.
- 백엔드는 사용자에게 이메일 등을 사용하여 패스키 생성을 확인하고 잠재적인 무단 액세스를 감지하도록 알립니다.
이 프로세스를 통해 사용자는 안전하고 원활하게 패스키를 등록할 수 있습니다.
호환성
대부분의 브라우저가 WebAuthn을 지원하지만 약간의 차이가 있습니다. 브라우저 및 OS 호환성 세부정보는 passkeys.dev를 참고하세요.
새 패스키 만들기
새 패스키를 만들려면 프런트엔드에서 다음 프로세스를 따라야 합니다.
다음 섹션에서는 이 작업을 수행하는 방법을 보여줍니다.
호환성 확인
'새 패스키 만들기' 버튼을 표시하기 전에 프런트엔드는 다음을 확인해야 합니다.
- 브라우저가
PublicKeyCredential로 WebAuthn을 지원합니다.
- 브라우저가
PublicKeyCredential.getClientCapabilities()를 사용한 기능 감지를 지원합니다.
브라우저가
conditionalGet를 사용하여 WebAuthn 조건부 UI를 지원합니다.기기가
passkeyPlatformAuthenticator를 사용하여 플랫폼 인증자 (기기에서 패스키를 생성하고 인증할 수 있음)를 지원합니다.
다음 코드 스니펫은 패스키 관련 옵션을 표시하기 전에 호환성을 확인하는 방법을 보여줍니다.
if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true &&
capabilities.passkeyPlatformAuthenticator === true) {
// The browser supports passkeys and the conditional UI.
}
}
이 예에서는 모든 조건이 충족된 경우에만 새 패스키 만들기 버튼이 표시되어야 합니다.
백엔드에서 정보 가져오기
사용자가 버튼을 클릭하면 백엔드에서 필요한 정보를 가져와 navigator.credentials.create()을 호출합니다.
다음 코드 스니펫은 navigator.credentials.create()를 호출하는 데 필요한 정보가 포함된 JSON 객체를 보여줍니다.
// Example `PublicKeyCredentialCreationOptions` contents
{
challenge: *****,
rp: {
name: "Example",
id: "example.com",
},
user: {
id: *****,
name: "john78",
displayName: "John",
},
pubKeyCredParams: [{
alg: -7, type: "public-key"
},{
alg: -257, type: "public-key"
}],
excludeCredentials: [{
id: *****,
type: 'public-key',
transports: ['internal'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
}
객체의 키-값 쌍에는 다음 정보가 포함됩니다.
challenge: 이 등록을 위해 서버에서 생성된 ArrayBuffer의 챌린지입니다.rp.id: RP ID (신뢰 당사자 ID), 도메인, 웹사이트는 도메인 또는 등록 가능한 접미사를 지정할 수 있습니다. 예를 들어 RP의 출처가https://login.example.com:1337인 경우 RP ID는login.example.com또는example.com일 수 있습니다. RP ID가example.com으로 지정된 경우 사용자는login.example.com또는example.com의 하위 도메인에서 인증할 수 있습니다. 자세한 내용은 관련 출처 요청을 사용하여 여러 사이트에서 패스키 재사용 허용을 참고하세요.rp.name: RP (신뢰 당사자)의 이름입니다. WebAuthn L3에서는 지원 중단되었지만 호환성을 위해 포함되었습니다.user.id: 계정 생성 시 생성되는 ArrayBuffer의 고유 사용자 ID입니다. 수정 가능한 사용자 이름과 달리 영구적이어야 합니다. 사용자 ID는 계정을 식별하지만 개인 식별 정보 (PII)를 포함해서는 안 됩니다. 시스템에 사용자 ID가 이미 있을 수 있지만 필요한 경우 패스키 전용으로 만들어 개인 식별 정보가 포함되지 않도록 합니다.user.name: 사용자가 인식할 수 있는 계정의 고유 식별자입니다(예: 이메일 주소 또는 사용자 이름). 이것은 계정 선택기에 표시됩니다.user.displayName: 계정의 필수 사용자 친화적인 이름입니다. 고유하지 않아도 되며 사용자가 선택한 이름일 수 있습니다. 사이트에 포함할 수 있는 적합한 값이 없는 경우 빈 문자열을 전달합니다. 브라우저에 따라 계정 선택기에 표시될 수 있습니다.pubKeyCredParams: RP (신뢰 당사자)에서 지원되는 공개 키 알고리즘을 지정합니다.[{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]로 설정하는 것이 좋습니다. P-256 및 RSA PKCS#1을 사용하는 ECDSA 지원을 지정하며, 지원을 통해 완전한 적용 범위를 제공합니다.excludeCredentials: 이미 등록된 사용자 인증 정보 ID 목록입니다. 이미 등록된 사용자 인증 정보 ID 목록을 제공하여 동일한 기기가 두 번 등록되는 것을 방지합니다.transports멤버는 제공되는 경우 각 사용자 인증 정보를 등록하는 동안getTransports()를 호출한 결과를 포함해야 합니다.authenticatorSelection.authenticatorAttachment: 이 패스키 생성이 로그인 후 프로모션에서와 같이 비밀번호에서 업그레이드된 경우hint: ['client-device']와 함께"platform"로 설정합니다."platform"은 RP가 USB 보안 키를 삽입하라는 메시지가 표시되지 않는 플랫폼 인증자 (플랫폼 기기에 삽입된 인증자)를 원함을 나타냅니다. 사용자에게 패스키를 생성하는 더 간단한 옵션이 있습니다.authenticatorSelection.requireResidentKey: 불리언true로 설정합니다. 검색 가능한 사용자 인증 정보 (상주 키)는 사용자 정보를 패스키에 저장하고 사용자가 인증 시 계정을 선택할 수 있도록 합니다.authenticatorSelection.userVerification: 기기 화면 잠금을 사용하는 사용자 확인이"required"인지,"preferred"인지 또는"discouraged"인지를 나타냅니다. 기본값은"preferred"이며, 이는 인증자가 사용자 확인을 건너뛸 수 있음을 의미합니다."preferred"로 설정하거나 속성을 생략합니다.
서버에서 객체를 구성하고 Base64URL로 ArrayBuffer
를 인코딩하고 프런트엔드에서 가져오는 것이 좋습니다. 이렇게 하면 PublicKeyCredential.parseCreationOptionsFromJSON()를 사용하여 페이로드를 디코딩하고 navigator.credentials.create()에 직접 전달할 수 있습니다.
다음 코드 스니펫은 패스키를 만드는 데 필요한 정보를 가져오고 디코딩하는 방법을 보여줍니다.
// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');
// Deserialize and decode the `PublicKeyCredentialCreationOptions`.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseCreationOptionsFromJSON(decoded_options);
...
WebAuthn API를 호출하여 패스키 생성
navigator.credentials.create()를 호출하여 새 패스키를 만듭니다. API는 모달 대화상자를 표시하는 사용자 상호작용을 기다리는 프로미스를 반환합니다.
// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
publicKey: options
});
반환된 공개 키 사용자 인증 정보를 백엔드로 전송
기기의 화면 잠금을 사용하여 사용자가 인증되면 패스키가 생성되고 프런트엔드에 PublicKeyCredential 객체를 반환하는 프로미스가 해결됩니다.
약속은 다양한 이유로 거부될 수 있습니다. Error 객체의 name 속성을 확인하여 이러한 오류를 처리할 수 있습니다.
InvalidStateError: 기기에 패스키가 이미 있습니다. 사용자에게 오류 대화상자가 표시되지 않습니다. 사이트에서 이를 오류로 취급해서는 안 됩니다. 사용자가 로컬 기기를 등록하려고 했고 등록되었습니다.NotAllowedError: 사용자가 작업을 취소했습니다.AbortError: 작업이 중단되었습니다.- 기타 예외: 예상치 못한 문제가 발생했습니다. 브라우저에 사용자에게 오류 대화상자가 표시됩니다.
공개 키 사용자 인증 정보 객체에는 다음 속성이 포함되어 있습니다.
id: 생성된 패스키의 Base64URL로 인코딩된 ID입니다. 이 ID는 인증 시 브라우저에서 기기에 일치하는 패스키가 있는지 확인하는 데 도움이 됩니다. 이 값은 백엔드의 데이터베이스에 저장해야 합니다.rawId: 사용자 인증 정보 ID의 ArrayBuffer 버전입니다.response.clientDataJSON: 인코딩된 ArrayBuffer 클라이언트 데이터입니다.response.attestationObject: ArrayBuffer로 인코딩된 증명 객체입니다. 여기에는 RP ID, 플래그, 공개 키와 같은 중요한 정보가 포함되어 있습니다.authenticatorAttachment: 이 사용자 인증 정보가 패스키 지원 기기에서 생성되면"platform"을 반환합니다.type: 이 필드는 항상"public-key"로 설정됩니다.
.toJSON() 메서드로 객체를 인코딩하고 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/registerResponse', {
method: 'post',
credentials: 'same-origin',
body: result
});
...
사용자 인증 정보 저장
백엔드에서 공개 키 사용자 인증 정보를 수신한 후 공개 키 사용자 인증 정보를 처리하는 자체 코드를 작성하는 대신 서버 측 라이브러리 또는 솔루션을 사용하는 것이 좋습니다.
그런 다음 인증 정보에서 가져온 정보를 나중에 사용할 수 있도록 데이터베이스에 저장할 수 있습니다.
다음 목록에는 저장하는 것이 권장되는 속성이 포함되어 있습니다.
- 사용자 인증 정보 ID: 공개 키 사용자 인증 정보와 함께 반환된 사용자 인증 정보 ID입니다.
- 사용자 인증 정보 이름: 사용자 인증 정보의 이름입니다. AAGUID에 따라 식별할 수 있는 패스키 제공업체 이름으로 지정합니다.
- 사용자 ID: 패스키를 만드는 데 사용된 사용자 ID입니다.
- 공개 키: 공개 키 사용자 인증 정보와 함께 반환된 공개 키입니다. 이는 패스키 어설션을 확인하는 데 필요합니다.
- 생성 날짜 및 시간: 패스키 생성 날짜와 시간을 기록합니다. 이는 패스키를 식별하는 데 유용합니다.
- 마지막 사용 날짜 및 시간: 사용자가 패스키를 사용하여 로그인한 마지막 날짜와 시간을 기록합니다. 이는 사용자가 어떤 패스키를 사용했는지 (또는 사용하지 않았는지) 확인하는 데 유용합니다.
- AAGUID: 패스키 제공업체의 고유 식별자입니다.
- 백업 자격 요건 플래그: 기기가 패스키 동기화에 적합한 경우 true입니다. 이 정보는 사용자가 패스키 관리 페이지에서 동기화 가능한 패스키와 동기화 불가능한 기기 바인딩 패스키를 식별하는 데 도움이 됩니다.
서버 측 패스키 등록에서 자세한 안내를 따르세요.
등록에 실패하면 신호
패스키 등록이 실패하면 사용자에게 혼란을 줄 수 있습니다. 패스키 제공업체에 패스키가 있고 사용자가 사용할 수 있지만 연결된 공개 키가 서버 측에 저장되지 않은 경우 패스키를 사용한 로그인 시도는 절대 성공하지 않으며 문제를 해결하기 어렵습니다. 이 경우 사용자에게 알려야 합니다.
이러한 상황을 방지하려면 Signal API를 사용하여 패스키 제공업체에 알 수 없는 패스키를 알리세요.
RP는 RP ID와 사용자 인증 정보 ID를 사용하여 PublicKeyCredential.signalUnknownCredential()를 호출하여 지정된 사용자 인증 정보가 삭제되었거나 존재하지 않음을 패스키 제공업체에 알릴 수 있습니다. 이 신호를 처리하는 방법은 패스키 제공업체에 달려 있지만, 지원되는 경우 연결된 패스키가 삭제될 것으로 예상됩니다.
// 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.
...
}
}
Signal API에 대해 자세히 알아보려면 Signal API를 사용하여 패스키를 서버의 사용자 인증 정보와 일치시키기를 참고하세요.
사용자에게 알림 전송
패스키가 등록될 때 알림 (예: 이메일)을 보내면 사용자가 승인되지 않은 계정 액세스를 감지하는 데 도움이 됩니다. 공격자가 사용자의 알림 없이 패스키를 생성하면 비밀번호가 변경된 후에도 패스키를 계속 악용할 수 있습니다. 알림은 사용자에게 경고하고 이를 방지하는 데 도움이 됩니다.
체크리스트
- 사용자가 패스키를 만들 수 있도록 허용하기 전에 이메일이나 보안 방법을 사용하여 사용자를 인증합니다.
excludeCredentials를 사용하여 동일한 패스키 제공업체에 중복 패스키가 생성되지 않도록 합니다.- 패스키 제공업체를 식별하고 사용자의 사용자 인증 정보를 명명하기 위해 AAGUID를 저장합니다.
- 패스키 등록 시도가
PublicKeyCredential.signalUnknownCredential()로 실패하는지 신호로 알립니다. - 계정의 패스키를 생성하고 등록한 후 사용자에게 알림을 보냅니다.
리소스
다음 단계: 양식 자동 완성을 통한 패스키 로그인