이 Codelab 정보
1. 시작하기 전에
Web Authentication API(WebAuthn이라고도 함)를 사용하면 원본 범위의 공개 키 사용자 인증 정보를 만들어 사용자 인증을 실행할 수 있습니다.
API는 보안 키라고도 하는 BLE, NFC, USB 로밍 U2F 또는 FIDO2 인증자뿐 아니라 사용자가 지문 또는 화면 잠금을 사용하여 인증할 수 있는 플랫폼 인증자 사용을 지원합니다.
이 Codelab에서는 지문 센서를 사용하는 간단한 재인증 기능이 지원되는 웹사이트를 빌드합니다. 재인증을 사용하면 웹사이트에 이미 로그인한 사용자가 웹사이트의 중요한 섹션에 들어가려고 하거나 일정 시간이 지난 후 웹사이트를 다시 방문하려고 할 때 다시 인증을 해야 하므로 계정 데이터가 보호됩니다.
기본 요건
- WebAuthn의 작동 원리에 관한 기본적인 이해
- 자바스크립트를 활용한 기본적인 프로그래밍 기술
실행할 작업
- 지문 센서를 사용하는 간단한 재인증 기능이 지원되는 웹사이트 빌드하기
준비물
- 다음 기기 중 하나가 필요합니다.
- Android 기기, 생체 인식 센서가 있는 기기 권장
- Touch ID 또는 Face ID가 지원되는 iPhone 또는 iPad(iOS 14 이상)
- Touch ID가 지원되는 MacBook Pro 또는 Air(macOS Big Sur 이상)
- Windows Hello가 설정된 Windows 10 19H1 이상
- 다음 브라우저 중 하나가 필요합니다.
- Chrome 67 이상
- Microsoft Edge 85 이상
- Safari 14 이상
2. 설정
이 Codelab에서는 glitch라는 서비스를 사용합니다. 이 서비스에서는 자바스크립트로 클라이언트 및 서버 측 코드를 수정하고 즉시 배포할 수 있습니다.
https://glitch.com/edit/#!/webauthn-codelab-start로 이동합니다.
사용 방법 보기
다음 단계에 따라 웹사이트의 초기 상태를 확인합니다.
표시 >
새 창에서 열기를 클릭하여 실제 웹사이트를 확인합니다.
- 원하는 사용자 이름을 입력하고 다음을 클릭합니다.
- 비밀번호를 입력하고 로그인을 클릭합니다.
비밀번호는 무시되지만 여전히 인증되어 있습니다. 홈페이지가 표시됩니다.
- 재인증 시도를 클릭하고 두 번째, 세 번째, 네 번째 단계를 반복합니다.
- 로그아웃을 클릭합니다.
로그인할 때마다 비밀번호를 입력해야 합니다. 이렇게 하면 재인증해야 웹사이트의 중요한 섹션에 액세스할 수 있는 사용자가 에뮬레이션됩니다.
코드 리믹스
- WebAuthn / FIDO2 API Codelab으로 이동합니다.
- 프로젝트를 포크하고 새 URL에서 자체 버전을 계속 사용하려면 프로젝트 이름 > 프로젝트 리믹스
를 클릭합니다.
3. 지문으로 사용자 인증 정보 등록하기
기기에 내장되어 있으며 사용자의 ID를 확인하는 인증자인 UVPA에 의해 생성된 사용자 인증 정보를 등록해야 합니다. 사용자 기기에 따라 일반적으로 지문 센서의 형태를 띱니다.
이 기능을 /home
페이지에 추가합니다.
registerCredential()
함수 만들기
새 사용자 인증 정보를 등록하는 registerCredential()
함수를 만듭니다.
public/client.js
export const registerCredential = async () => {
};
서버 엔드포인트에서 본인 확인 및 기타 옵션 가져오기
사용자에게 새 사용자 인증 정보를 등록하도록 요청하기 전에 WebAuthn에서 전달할 본인 확인 등의 매개변수를 반환하도록 서버에 요청합니다. 다행히 이러한 매개변수로 응답하는 서버 엔드포인트가 이미 있습니다.
다음 코드를 registerCredential()
에 추가합니다.
public/client.js
const opts = {
attestation: 'none',
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'required',
requireResidentKey: false
}
};
const options = await _fetch('/auth/registerRequest', opts);
서버와 클라이언트 간 프로토콜은 WebAuthn 사양의 일부가 아닙니다. 그러나 이 Codelab은 WebAuthn 사양과 일치하도록 설계되었으며 서버에 전달하는 JSON 객체는 PublicKeyCredentialCreationOptions
와 매우 유사하기 때문에 직관적입니다. 다음 표에는 서버에 전달할 수 있는 중요한 매개변수가 표시되어 있으며, 실행하는 작업이 설명되어 있습니다.
매개변수 | 설명 | ||
| 증명 전달에 관한 환경설정: | ||
| 인증자가 중복 항목의 생성을 방지할 수 있는 | ||
|
| 사용 가능한 인증자를 필터링합니다. 인증자를 기기에 연결하려면 ' | |
| 인증자 로컬 사용자 확인이 ' | ||
| 생성된 사용자 인증 정보를 향후 계정 선택 도구 UX에 사용할 수 있다면 |
이러한 옵션에 대한 자세한 내용은 5.4. 사용자 인증 정보 생성 옵션(사전 PublicKeyCredentialCreationOptions
)을 참고하세요.
다음은 서버에서 받는 옵션의 예입니다.
{
"rp": {
"name": "WebAuthn Codelab",
"id": "webauthn-codelab.glitch.me"
},
"user": {
"displayName": "User Name",
"id": "...",
"name": "test"
},
"challenge": "...",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}, {
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [
{
"id": "...",
"type": "public-key",
"transports": [
"internal"
]
}
],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"userVerification": "required"
}
}
사용자 인증 정보 만들기
- 이러한 옵션은 HTTP 프로토콜을 거치도록 인코딩된 상태로 제공되므로 일부 매개변수를 다시 바이너리, 구체적으로는
user.id
,challenge
,excludeCredentials
배열에 포함된id
인스턴스로 변환합니다.
public/client.js
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);
if (options.excludeCredentials) {
for (let cred of options.excludeCredentials) {
cred.id = base64url.decode(cred.id);
}
}
navigator.credentials.create()
메서드를 호출하여 새 사용자 인증 정보를 만듭니다.
브라우저는 이 호출을 통해 인증자와 상호작용하며 UVPA를 통해 사용자 ID를 확인하려고 시도합니다.
public/client.js
const cred = await navigator.credentials.create({
publicKey: options,
});
사용자가 신원을 확인하면 서버에 전송할 수 있는 사용자 인증 정보 객체를 받아 인증자를 등록해야 합니다.
서버 엔드포인트에 사용자 인증 정보 등록하기
다음은 수신해야 하는 사용자 인증 정보 객체의 예입니다.
{
"id": "...",
"rawId": "...",
"type": "public-key",
"response": {
"clientDataJSON": "...",
"attestationObject": "..."
}
}
- 사용자 인증 정보를 등록하는 옵션 객체를 수신할 때와 마찬가지로 사용자 인증 정보의 바이너리 매개변수를 인코딩하여 문자열로 서버로 전송할 수 있습니다.
public/client.js
const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;
if (cred.response) {
const clientDataJSON =
base64url.encode(cred.response.clientDataJSON);
const attestationObject =
base64url.encode(cred.response.attestationObject);
credential.response = {
clientDataJSON,
attestationObject,
};
}
- 사용자가 돌아오면 인증에 사용할 수 있도록 사용자 인증 정보 ID를 로컬에 저장합니다.
public/client.js
localStorage.setItem(`credId`, credential.id);
- 객체를 서버로 전송하고 객체가
HTTP code 200
을 반환하면 새 사용자 인증 정보가 성공적으로 등록된 것으로 간주합니다.
public/client.js
return await _fetch('/auth/registerResponse' , credential);
이제 전체 registerCredential()
함수가 있습니다.
이 섹션의 최종 코드
public/client.js
...
export const registerCredential = async () => {
const opts = {
attestation: 'none',
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'required',
requireResidentKey: false
}
};
const options = await _fetch('/auth/registerRequest', opts);
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);
if (options.excludeCredentials) {
for (let cred of options.excludeCredentials) {
cred.id = base64url.decode(cred.id);
}
}
const cred = await navigator.credentials.create({
publicKey: options
});
const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;
if (cred.response) {
const clientDataJSON =
base64url.encode(cred.response.clientDataJSON);
const attestationObject =
base64url.encode(cred.response.attestationObject);
credential.response = {
clientDataJSON,
attestationObject
};
}
localStorage.setItem(`credId`, credential.id);
return await _fetch('/auth/registerResponse' , credential);
};
...
4. 사용자 인증 정보를 등록, 가져오기, 삭제하는 UI 빌드하기
등록된 사용자 인증 정보와 버튼 중 삭제할 목록을 만들어 두는 것이 좋습니다.
UI 자리표시자 빌드하기
사용자 인증 정보와 버튼을 나열하여 새 사용자 인증 정보를 등록할 수 있는 UI를 추가합니다. 기능의 사용 가능 여부에 따라 경고 메시지나 버튼에서 hidden
클래스를 삭제하여 새 사용자 인증 정보를 등록합니다. ul#list
는 등록된 사용자 인증 정보 목록을 추가하기 위한 자리표시자입니다.
views/home.html
<p id="uvpa_unavailable" class="hidden">
This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
Your registered credentials:
</h3>
<section>
<div id="list"></div>
</section>
<mwc-button id="register" class="hidden" icon="fingerprint" raised>Add a credential</mwc-button>
특징 감지 및 UVPA 가용성
UVPA 가용성을 확인하려면 다음 단계를 따르세요.
window.PublicKeyCredential
을 확인하여 WebAuthn을 사용할 수 있는지 확인합니다.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
을 호출하여 UVPA를 사용할 수 있는지 확인합니다. 사용 가능한 경우 새 사용자 인증 정보를 등록하는 버튼이 표시됩니다. 둘 다 사용할 수 없는 경우 경고 메시지가 표시됩니다.
views/home.html
const register = document.querySelector('#register');
if (window.PublicKeyCredential) {
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(uvpaa => {
if (uvpaa) {
register.classList.remove('hidden');
} else {
document
.querySelector('#uvpa_unavailable')
.classList.remove('hidden');
}
});
} else {
document
.querySelector('#uvpa_unavailable')
.classList.remove('hidden');
}
사용자 인증 정보 목록 가져오기 및 표시하기
- 등록된 사용자 인증 정보를 가져와 목록에 표시할 수 있도록
getCredentials()
함수를 만듭니다. 다행히/auth/getKeys
서버에는 이미 로그인된 사용자의 사용자 인증 정보를 가져올 수 있는 유용한 엔드포인트가 있습니다.
반환된 JSON에는 id
와 publicKey
같은 사용자 인증 정보가 포함됩니다. HTML을 작성하여 사용자에게 표시할 수 있습니다.
views/home.html
const getCredentials = async () => {
const res = await _fetch('/auth/getKeys');
const list = document.querySelector('#list');
const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
<div class="mdc-card credential">
<span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
<pre class="public-key">${cred.publicKey}</pre>
<div class="mdc-card__actions">
<mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
</div>
</div>`) : html`
<p>No credentials found.</p>
`}`;
render(creds, list);
};
getCredentials()
를 호출하여 사용자가/home
페이지에 방문하자마자 사용 가능한 사용자 인증 정보를 표시합니다.
views/home.html
getCredentials();
사용자 인증 정보 삭제하기
사용자 인증 정보 목록에 각 사용자 인증 정보를 삭제하는 버튼을 추가했습니다. credId
쿼리 매개변수와 함께 /auth/removeKey
에 요청을 전송하여 삭제할 수 있습니다.
public/client.js
export const unregisterCredential = async (credId) => {
localStorage.removeItem('credId');
return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
- 기존
import
문에unregisterCredential
을 추가합니다.
views/home.html
import { _fetch, unregisterCredential } from '/client.js';
- 사용자가 삭제를 클릭할 때 호출할 함수를 추가합니다.
views/home.html
const removeCredential = async e => {
try {
await unregisterCredential(e.target.id);
getCredentials();
} catch (e) {
alert(e);
}
};
사용자 인증 정보 등록하기
사용자가 사용자 인증 정보 추가를 클릭하면 registerCredential()
을 호출하여 새 사용자 인증 정보를 등록할 수 있습니다.
- 기존
import
문에registerCredential
을 추가합니다.
views/home.html
import { _fetch, registerCredential, unregisterCredential } from '/client.js';
navigator.credentials.create()
옵션을 사용하여registerCredential()
을 호출합니다.
등록 후 getCredentials()
를 호출하여 사용자 인증 정보 목록을 갱신해야 합니다.
views/home.html
register.addEventListener('click', e => {
registerCredential().then(user => {
getCredentials();
}).catch(e => alert(e));
});
이제 새 사용자 인증 정보를 등록하고 그에 관한 정보를 표시할 수 있습니다. 라이브 웹사이트에서 사용해 볼 수 있습니다.
이 섹션의 최종 코드
views/home.html
...
<p id="uvpa_unavailable" class="hidden">
This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
Your registered credentials:
</h3>
<section>
<div id="list"></div>
<mwc-fab id="register" class="hidden" icon="add"></mwc-fab>
</section>
<mwc-button raised><a href="/reauth">Try reauth</a></mwc-button>
<mwc-button><a href="/auth/signout">Sign out</a></mwc-button>
</main>
<script type="module">
import { _fetch, registerCredential, unregisterCredential } from '/client.js';
import { html, render } from 'https://unpkg.com/lit-html@1.0.0/lit-html.js?module';
const register = document.querySelector('#register');
if (window.PublicKeyCredential) {
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(uvpaa => {
if (uvpaa) {
register.classList.remove('hidden');
} else {
document
.querySelector('#uvpa_unavailable')
.classList.remove('hidden');
}
});
} else {
document
.querySelector('#uvpa_unavailable')
.classList.remove('hidden');
}
const getCredentials = async () => {
const res = await _fetch('/auth/getKeys');
const list = document.querySelector('#list');
const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
<div class="mdc-card credential">
<span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
<pre class="public-key">${cred.publicKey}</pre>
<div class="mdc-card__actions">
<mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
</div>
</div>`) : html`
<p>No credentials found.</p>
`}`;
render(creds, list);
};
getCredentials();
const removeCredential = async e => {
try {
await unregisterCredential(e.target.id);
getCredentials();
} catch (e) {
alert(e);
}
};
register.addEventListener('click', e => {
registerCredential({
attestation: 'none',
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'required',
requireResidentKey: false
}
})
.then(user => {
getCredentials();
})
.catch(e => alert(e));
});
</script>
...
public/client.js
...
export const unregisterCredential = async (credId) => {
localStorage.removeItem('credId');
return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
...
5. 지문으로 사용자 인증하기
이제 사용자 인증 정보를 등록하여 사용자 인증 방법으로 사용할 수 있습니다. 이제 웹사이트에 재인증 기능을 추가합니다. 사용자 환경은 다음과 같습니다.
사용자가 /reauth
페이지를 방문하면 생체 인식 인증이 가능한 경우 인증 버튼이 표시됩니다. 지문(UVPA)을 통한 인증은 인증을 탭하고 인증을 완료한 다음 /home
페이지로 이동하면 시작됩니다. 생체 인식 인증을 사용할 수 없거나 생체 인식 인증에 실패하면 기존 비밀번호 양식을 사용하도록 UI가 대체됩니다.
authenticate()
함수 만들기
지문으로 사용자의 신원을 확인하는 authenticate()
라는 함수를 만듭니다. 여기에 자바스크립트 코드를 추가합니다.
public/client.js
export const authenticate = async () => {
};
서버 엔드포인트에서 본인 확인 및 기타 옵션 가져오기
- 인증 전에 사용자에게 저장된 사용자 인증 정보 ID가 있는지 확인하고, 있는 경우 쿼리 매개변수로 설정하세요.
다른 옵션과 함께 사용자 인증 정보 ID를 제공하면 서버가 관련 allowCredentials
를 제공할 수 있으며, 이렇게 하면 사용자 인증을 안정적으로 실행할 수 있습니다.
public/client.js
const opts = {};
let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
url += `?credId=${encodeURIComponent(credId)}`;
}
- 사용자에게 인증을 요청하기 전에 서버에 본인 확인 요청 및 기타 매개변수를 다시 전송하도록 요청하세요.
opts
를 인수로_fetch()
를 호출하여 POST 요청을 서버에 전송합니다.
public/client.js
const options = await _fetch(url, opts);
다음은 받는 옵션의 예입니다(PublicKeyCredentialRequestOptions
에 부합).
{
"challenge": "...",
"timeout": 1800000,
"rpId": "webauthn-codelab.glitch.me",
"userVerification": "required",
"allowCredentials": [
{
"id": "...",
"type": "public-key",
"transports": [
"internal"
]
}
]
}
여기에서 가장 중요한 옵션은 allowCredentials
입니다. 서버로부터 옵션을 수신할 때 allowCredentials
는 서버 측에서 쿼리 매개변수 ID의 사용자 인증 정보를 찾을 수 있는지에 따라 배열의 단일 객체이거나 빈 배열이어야 합니다.
allowCredentials
가 빈 배열인 경우null
을 사용하여 프로미스를 확인합니다. 그러면 UI가 비밀번호를 묻는 요청으로 대체됩니다.
if (options.allowCredentials.length === 0) {
console.info('No registered credentials found.');
return Promise.resolve(null);
}
로컬에서 사용자 인증 및 사용자 인증 정보 가져오기
- 이러한 옵션은 HTTP 프로토콜을 거치도록 인코딩된 상태로 제공되므로 일부 매개변수를 다시 바이너리, 구체적으로는
challenge
및allowCredentials
배열에 포함된id
인스턴스로 변환합니다.
public/client.js
options.challenge = base64url.decode(options.challenge);
for (let cred of options.allowCredentials) {
cred.id = base64url.decode(cred.id);
}
- UVPA를 통해 사용자의 신원을 확인하려면
navigator.credentials.get()
메서드를 호출합니다.
public/client.js
const cred = await navigator.credentials.get({
publicKey: options
});
사용자가 신원을 확인하면 서버에 전송할 수 있는 사용자 인증 정보 객체를 받아 사용자를 인증해야 합니다.
사용자 인증 정보 확인하기
다음은 수신한 PublicKeyCredential
객체(response
는 AuthenticatorAssertionResponse
임)의 예입니다.
{
"id": "...",
"type": "public-key",
"rawId": "...",
"response": {
"clientDataJSON": "...",
"authenticatorData": "...",
"signature": "...",
"userHandle": ""
}
}
- 사용자 인증 정보의 바이너리 매개변수를 인코딩하여 문자열로 서버로 전송할 수 있습니다.
public/client.js
const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);
if (cred.response) {
const clientDataJSON =
base64url.encode(cred.response.clientDataJSON);
const authenticatorData =
base64url.encode(cred.response.authenticatorData);
const signature =
base64url.encode(cred.response.signature);
const userHandle =
base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
}
- 객체를 서버로 전송하고 객체가
HTTP code 200
을 반환하면 사용자가 로그인된 것으로 간주합니다.
public/client.js
return await _fetch(`/auth/signinResponse`, credential);
이제 전체 authentication()
함수가 있습니다.
이 섹션의 최종 코드
public/client.js
...
export const authenticate = async () => {
const opts = {};
let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
url += `?credId=${encodeURIComponent(credId)}`;
}
const options = await _fetch(url, opts);
if (options.allowCredentials.length === 0) {
console.info('No registered credentials found.');
return Promise.resolve(null);
}
options.challenge = base64url.decode(options.challenge);
for (let cred of options.allowCredentials) {
cred.id = base64url.decode(cred.id);
}
const cred = await navigator.credentials.get({
publicKey: options
});
const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);
if (cred.response) {
const clientDataJSON =
base64url.encode(cred.response.clientDataJSON);
const authenticatorData =
base64url.encode(cred.response.authenticatorData);
const signature =
base64url.encode(cred.response.signature);
const userHandle =
base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
}
return await _fetch(`/auth/signinResponse`, credential);
};
...
6. 재인증 환경 사용 설정하기
UI 빌드하기
사용자가 돌아오면 최대한 쉽고 안전하게 재인증할 수 있도록 만들고자 합니다. 생체 인식 인증은 바로 여기에서 중요합니다. 하지만 다음과 같은 경우에는 생체 인식 인증이 작동하지 않을 수 있습니다.
- UVPA를 사용할 수 없습니다.
- 사용자가 아직 기기에 사용자 인증 정보를 등록하지 않았습니다.
- 스토리지가 삭제되고 기기에 더 이상 사용자 인증 정보 ID가 저장되지 않습니다.
- 사용자의 손가락이 젖어 있거나 마스크를 착용하는 등의 이유로 인해 본인 확인을 할 수 없습니다.
따라서 다른 로그인 옵션을 대체로 제공하는 것이 항상 중요합니다. 이 Codelab에서는 양식 기반 비밀번호 솔루션을 사용합니다.
- 비밀번호 양식 외에 생체 인식 인증을 호출하는 인증 버튼을 표시하도록 UI를 추가합니다.
hidden
클래스를 사용하여 사용자의 상태에 따라 버튼 중 하나를 선택적으로 표시하거나 숨깁니다.
views/reauth.html
<div id="uvpa_available" class="hidden">
<h2>
Verify your identity
</h2>
<div>
<mwc-button id="reauth" raised>Authenticate</mwc-button>
</div>
<div>
<mwc-button id="cancel">Sign-in with password</mwc-button>
</div>
</div>
- 양식에
class="hidden"
을 추가합니다.
views/reauth.html
<form id="form" method="POST" action="/auth/password" class="hidden">
특징 감지 및 UVPA 가용성
다음 조건 중 하나가 충족되면 사용자는 비밀번호로 로그인해야 합니다.
- WebAuthn을 사용할 수 없습니다.
- UVPA를 사용할 수 없습니다.
- 이 UVPA의 사용자 인증 정보 ID를 검색할 수 없습니다.
인증 버튼을 선택적으로 표시하거나 숨깁니다.
views/reauth.html
if (window.PublicKeyCredential) {
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(uvpaa => {
if (uvpaa && localStorage.getItem(`credId`)) {
document
.querySelector('#uvpa_available')
.classList.remove('hidden');
} else {
form.classList.remove('hidden');
}
});
} else {
form.classList.remove('hidden');
}
비밀번호 양식으로 대체하기
사용자가 비밀번호로 로그인하도록 선택할 수도 있어야 합니다.
사용자가 비밀번호로 로그인을 클릭하면 비밀번호 양식을 표시하고 인증 버튼을 숨깁니다.
views/reauth.html
const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
form.classList.remove('hidden');
document
.querySelector('#uvpa_available')
.classList.add('hidden');
});
생체 인식 인증 호출하기
마지막으로 생체 인식 인증을 사용 설정합니다.
- 기존
import
문에authenticate
를 추가합니다.
views/reauth.html
import { _fetch, authenticate } from '/client.js';
- 사용자가 인증을 탭하여 생체 인식 인증을 시작하면
authenticate()
를 호출합니다.
생체 인식 인증 실패가 비밀번호 양식으로 대체되었는지 확인합니다.
views/reauth.html
const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
authenticate().then(user => {
if (user) {
location.href = '/home';
} else {
throw 'User not found.';
}
}).catch(e => {
console.error(e.message || e);
alert('Authentication failed. Use password to sign-in.');
form.classList.remove('hidden');
document.querySelector('#uvpa_available').classList.add('hidden');
});
});
이 섹션의 최종 코드
views/reauth.html
...
<main class="content">
<div id="uvpa_available" class="hidden">
<h2>
Verify your identity
</h2>
<div>
<mwc-button id="reauth" raised>Authenticate</mwc-button>
</div>
<div>
<mwc-button id="cancel">Sign-in with password</mwc-button>
</div>
</div>
<form id="form" method="POST" action="/auth/password" class="hidden">
<h2>
Enter a password
</h2>
<input type="hidden" name="username" value="{{username}}" />
<div class="mdc-text-field mdc-text-field--filled">
<span class="mdc-text-field__ripple"></span>
<label class="mdc-floating-label" id="password-label">password</label>
<input type="password" class="mdc-text-field__input" aria-labelledby="password-label" name="password" />
<span class="mdc-line-ripple"></span>
</div>
<input type="submit" class="mdc-button mdc-button--raised" value="Sign-In" />
<p class="instructions">password will be ignored in this demo.</p>
</form>
</main>
<script src="https://unpkg.com/material-components-web@7.0.0/dist/material-components-web.min.js"></script>
<script type="module">
new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));
import { _fetch, authenticate } from '/client.js';
const form = document.querySelector('#form');
form.addEventListener('submit', e => {
e.preventDefault();
const form = new FormData(e.target);
const cred = {};
form.forEach((v, k) => cred[k] = v);
_fetch(e.target.action, cred)
.then(user => {
location.href = '/home';
})
.catch(e => alert(e));
});
if (window.PublicKeyCredential) {
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(uvpaa => {
if (uvpaa && localStorage.getItem(`credId`)) {
document
.querySelector('#uvpa_available')
.classList.remove('hidden');
} else {
form.classList.remove('hidden');
}
});
} else {
form.classList.remove('hidden');
}
const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
form.classList.remove('hidden');
document
.querySelector('#uvpa_available')
.classList.add('hidden');
});
const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
authenticate().then(user => {
if (user) {
location.href = '/home';
} else {
throw 'User not found.';
}
}).catch(e => {
console.error(e.message || e);
alert('Authentication failed. Use password to sign-in.');
form.classList.remove('hidden');
document.querySelector('#uvpa_available').classList.add('hidden');
});
});
</script>
...
7. 축하합니다
이 Codelab을 완료했습니다.
자세히 알아보기
- 웹 인증: 공개 키 사용자 인증 정보 수준 1에 액세스하기 위한 API
- WebAuthn API 소개
- FIDO WebAuthn 워크숍
- WebAuthn 가이드: DUOSEC
- 첫 Android FIDO2 API
도움을 주신 FIDO Alliance의 유리 에커만 님께 감사 말씀을 드립니다.