CINXE.COM

Build your first WebAuthn app  |  Google for Developers

<!doctype html> <html lang="en" dir="ltr"> <head> <meta name="google-signin-client-id" content="721724668570-nbkv1cfusk7kk4eni4pjvepaus73b13t.apps.googleusercontent.com"> <meta name="google-signin-scope" content="profile email https://www.googleapis.com/auth/developerprofiles https://www.googleapis.com/auth/developerprofiles.award"> <meta property="og:site_name" content="Google for Developers"> <meta property="og:type" content="website"><meta name="theme-color" content="#1a73e8"><meta charset="utf-8"> <meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="manifest" href="/_pwa/developers/manifest.json" crossorigin="use-credentials"> <link rel="preconnect" href="//www.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.googleapis.com" crossorigin> <link rel="preconnect" href="//apis.google.com" crossorigin> <link rel="preconnect" href="//www.google-analytics.com" crossorigin><link rel="stylesheet" href="//fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"> <link rel="stylesheet" href="//fonts.googleapis.com/css2?family=Material+Icons&family=Material+Symbols+Outlined&display=block"><link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/css/app.css"> <link rel="shortcut icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/images/favicon-new.png"> <link rel="apple-touch-icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/images/touchicon-180-new.png"><link rel="canonical" href="https://developers.google.com/codelabs/webauthn-reauth"><link rel="search" type="application/opensearchdescription+xml" title="Google for Developers" href="https://developers.google.com/s/opensearch.xml"> <link rel="alternate" hreflang="en" href="https://developers.google.com/codelabs/webauthn-reauth" /><link rel="alternate" hreflang="x-default" href="https://developers.google.com/codelabs/webauthn-reauth" /><link rel="alternate" hreflang="ar" href="https://developers.google.com/codelabs/webauthn-reauth?hl=ar" /><link rel="alternate" hreflang="bn" href="https://developers.google.com/codelabs/webauthn-reauth?hl=bn" /><link rel="alternate" hreflang="zh-Hans" href="https://developers.google.com/codelabs/webauthn-reauth?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant" href="https://developers.google.com/codelabs/webauthn-reauth?hl=zh-tw" /><link rel="alternate" hreflang="fa" href="https://developers.google.com/codelabs/webauthn-reauth?hl=fa" /><link rel="alternate" hreflang="fr" href="https://developers.google.com/codelabs/webauthn-reauth?hl=fr" /><link rel="alternate" hreflang="de" href="https://developers.google.com/codelabs/webauthn-reauth?hl=de" /><link rel="alternate" hreflang="he" href="https://developers.google.com/codelabs/webauthn-reauth?hl=he" /><link rel="alternate" hreflang="hi" href="https://developers.google.com/codelabs/webauthn-reauth?hl=hi" /><link rel="alternate" hreflang="id" href="https://developers.google.com/codelabs/webauthn-reauth?hl=id" /><link rel="alternate" hreflang="it" href="https://developers.google.com/codelabs/webauthn-reauth?hl=it" /><link rel="alternate" hreflang="ja" href="https://developers.google.com/codelabs/webauthn-reauth?hl=ja" /><link rel="alternate" hreflang="ko" href="https://developers.google.com/codelabs/webauthn-reauth?hl=ko" /><link rel="alternate" hreflang="pl" href="https://developers.google.com/codelabs/webauthn-reauth?hl=pl" /><link rel="alternate" hreflang="pt-BR" href="https://developers.google.com/codelabs/webauthn-reauth?hl=pt-br" /><link rel="alternate" hreflang="ru" href="https://developers.google.com/codelabs/webauthn-reauth?hl=ru" /><link rel="alternate" hreflang="es-419" href="https://developers.google.com/codelabs/webauthn-reauth?hl=es-419" /><link rel="alternate" hreflang="th" href="https://developers.google.com/codelabs/webauthn-reauth?hl=th" /><link rel="alternate" hreflang="tr" href="https://developers.google.com/codelabs/webauthn-reauth?hl=tr" /><link rel="alternate" hreflang="vi" href="https://developers.google.com/codelabs/webauthn-reauth?hl=vi" /><title>Build your first WebAuthn app &nbsp;|&nbsp; Google for Developers</title> <meta property="og:title" content="Build your first WebAuthn app &nbsp;|&nbsp; Google for Developers"><meta name="description" content="Learn how to build a website with a simple reauthentication functionality that uses a fingerprint sensor."> <meta property="og:description" content="Learn how to build a website with a simple reauthentication functionality that uses a fingerprint sensor."><meta property="og:url" content="https://developers.google.com/codelabs/webauthn-reauth"><meta property="og:locale" content="en"> <link rel="stylesheet" href="/extras.css"></head> <body class="" template="codelab" theme="google-blue" type="codelab" layout="docs" concierge='closed' display-toc pending> <devsite-progress type="indeterminate" id="app-progress"></devsite-progress> <section class="devsite-wrapper"> <devsite-cookie-notification-bar></devsite-cookie-notification-bar><devsite-header role="banner"> <div class="devsite-header--inner nocontent"> <div class="devsite-top-logo-row-wrapper-wrapper"> <div class="devsite-top-logo-row-wrapper"> <div class="devsite-top-logo-row"> <button type="button" id="devsite-hamburger-menu" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Navigation menu button" visually-hidden aria-label="Open menu"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="googleForDevelopers" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/images/lockup-new.svg" class="devsite-site-logo" alt="Google for Developers"> </picture> </a> </div> <div class="devsite-top-logo-row-middle"> <div class="devsite-header-upper-tabs"> </div> <devsite-search enable-signin enable-search enable-suggestions enable-query-completion tenant-name="Google for Developers" > <form class="devsite-search-form" action="https://developers.google.com/s/results" method="GET"> <div class="devsite-search-container"> <button type="button" search-open class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Open search"></button> <div class="devsite-searchbox"> <input aria-activedescendant="" aria-autocomplete="list" aria-label="Search" aria-expanded="false" aria-haspopup="listbox" autocomplete="off" class="devsite-search-field devsite-search-query" name="q" placeholder="Search" role="combobox" type="text" value="" > <div class="devsite-search-image material-icons" aria-hidden="true"> </div> <div class="devsite-search-shortcut-icon-container" aria-hidden="true"> <kbd class="devsite-search-shortcut-icon">/</kbd> </div> </div> </div> </form> <button type="button" search-close class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Close search"></button> </devsite-search> </div> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es" >Español</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> <devsite-user enable-profiles fp-auth id="devsite-user"> <span class="button devsite-top-button" aria-hidden="true" visually-hidden>Sign in</span> </devsite-user> </div> </div> </div> </div> </devsite-header> <devsite-book-nav scrollbars hidden> <div class="devsite-book-nav-filter" hidden> <span class="filter-list-icon material-icons" aria-hidden="true"></span> <input type="text" placeholder="Filter" aria-label="Type to filter" role="searchbox"> <span class="filter-clear-button hidden" data-title="Clear filter" aria-label="Clear filter" role="button" tabindex="0"></span> </div> <nav class="devsite-book-nav devsite-nav nocontent" aria-label="Side menu"> <div class="devsite-mobile-header"> <button type="button" id="devsite-close-nav" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Close navigation" aria-label="Close navigation"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="googleForDevelopers" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/images/lockup-new.svg" class="devsite-site-logo" alt="Google for Developers"> </picture> </a> </div> </div> <div class="devsite-book-nav-wrapper"> <div class="devsite-mobile-nav-top"> <ul class="devsite-nav-list"> </ul> </div> </div> </nav> </devsite-book-nav> <section id="gc-wrapper"> <main role="main" class="devsite-main-content" > <devsite-content> <article class="devsite-article"><style> body { transition: opacity ease-in 0.2s; } body[unresolved] { opacity: 0; display: block; overflow: hidden; position: relative; } </style> <div class="devsite-article-meta nocontent" role="navigation"> <ul class="devsite-breadcrumb-list" > </ul> </div> <h1 class="devsite-page-title" tabindex="-1"> Build your first WebAuthn app </h1> <devsite-feature-tooltip ack-key="AckCollectionsBookmarkTooltipDismiss" analytics-category="Site-Wide Custom Events" analytics-action-show="Callout Profile displayed" analytics-action-close="Callout Profile dismissed" analytics-label="Create Collection Callout" class="devsite-page-bookmark-tooltip nocontent" dismiss-button="true" id="devsite-collections-dropdown" dismiss-button-text="Dismiss" close-button-text="Got it"> <devsite-bookmark></devsite-bookmark> <span slot="popout-heading"> Stay organized with collections </span> <span slot="popout-contents"> Save and categorize content based on your preferences. </span> </devsite-feature-tooltip> <devsite-toc class="devsite-nav" depth="1" devsite-toc-embedded > </devsite-toc> <div class="devsite-article-body clearfix "> <google-codelab-analytics gaid="UA-49880327-14" ga4id="G-JTFZSJVVVZ"></google-codelab-analytics> <google-codelab codelab-gaid="" codelab-ga4id="" doc-id="1mpr1-sc9WSPgGzVXn7mjLJl4G9iCRzy90ycfE_5QwSQ" id="webauthn-reauth" title="Build your first WebAuthn app" no-tooltip="" environment="web" category="web,identity" feedback-link="https://github.com/googlecodelabs/feedback/issues/new?title=[webauth-reauth]" layout="paginated" > <google-codelab-step label="Before you begin" duration="2" step="0"> <google-codelab-about codelab-title="Build your first WebAuthn app" authors="Eiji Kitamura" last-updated="2024-09-18T19:33:22Z" duration="49"> </google-codelab-about> <h2 class="step-title" id="0" data-text="Before you begin" tabindex="-1"> 1. Before you begin </h2> <p>The Web Authentication API, also known as WebAuthn, lets you create and use origin-scoped, public-key credentials to authenticate users.</p> <p>The API supports the use of BLE, NFC, and USB-roaming U2F or FIDO2 authenticators—also known as security keys—as well as a platform authenticator, which lets users authenticate with their fingerprints or screen locks.</p> <p>In this codelab, you build a website with a simple reauthentication functionality that uses a fingerprint sensor. Reauthentication protects account data because it requires users who already signed in to a website to authenticate again when they try to enter important sections of the website or revisit the website after a certain amount of time.</p> <aside class="special"><p><strong>Note:</strong> This codelab sometimes refers to <em>User Verifying Platform Authenticator (UVPA)</em> as <em>biometric</em> or <em>fingerprint</em> to simplify the story. A <em>platform authenticator</em> is an authenticator built into a device. The <em>user verifying</em> means that the authenticator has an ability to verify the user, typically with a fingerprint sensor, but it could be with facial recognition, a PIN, a password, or pattern depending on the device. These are usually referred to as <em>screen lock</em> on Android and <em>Touch ID</em> or <em>Face ID</em> on iOS.</p> </aside> <aside class="special"><p><strong>Note:</strong> This codelab doesn&#39;t teach you how to build a FIDO server. It uses <a href="https://github.com/MasterKale/SimpleWebAuthn" target="_blank">SimpleWebAuthn</a>, but it doesn&#39;t mean that you verified its functionality or guaranteed its quality. For other options, see <a href="https://fidoalliance.org/certification/fido-certified-products/" target="_blank">FIDO Alliance official page</a>. For open source libraries, see <a href="https://webauthn.io/" target="_blank">webauthn.io</a> or <a href="https://bit.ly/AwesomeWebAuthn" target="_blank">AwesomeWebAuthn</a>.</p> </aside> <h2 is-upgraded id="prerequisites" data-text="Prerequisites" tabindex="-1">Prerequisites</h2> <ul> <li>Basic understanding of how WebAuthn works</li> <li>Basic programming skills with JavaScript</li> </ul> <h2 is-upgraded id="what-youll-do" data-text="What you'll do" tabindex="-1">What you'll do</h2> <ul> <li>Build a website with a simple reauthentication functionality that uses a fingerprint sensor</li> </ul> <h2 is-upgraded id="what-youll-need" data-text="What you'll need" tabindex="-1">What you'll need</h2> <ul> <li>One of the following devices: <ul> <li>An Android device, preferably with a biometric sensor</li> <li>An iPhone or iPad with Touch ID or Face ID on iOS 14 or higher</li> <li>A MacBook Pro or Air with Touch ID on macOS Big Sur or higher</li> <li>Windows 10 19H1 or higher with Windows Hello set up</li> </ul> </li> <li>One of the following browsers: <ul> <li>Google Chrome 67 or higher</li> <li>Microsoft Edge 85 or higher</li> <li>Safari 14 or higher</li> </ul> </li> </ul> </google-codelab-step> <google-codelab-step label="Get set up" duration="2" step="1"> <h2 class="step-title" id="1" data-text="Get set up" tabindex="-1"> 2. Get set up </h2> <p>In this codelab, you use a service called <a href="https://glitch.com/" target="_blank">glitch</a>. This is where you can edit client and server-side code with JavaScript, and deploy them instantly.</p> <p>Navigate to <a href="https://glitch.com/edit/#!/webauthn-codelab-start" target="_blank">https://glitch.com/edit/#!/webauthn-codelab-start</a>.</p> <h2 is-upgraded id="see-how-it-works" data-text="See how it works" tabindex="-1">See how it works</h2> <p>Follow these steps to see the initial state of the website:</p> <ol type="1"> <li>Click <img alt="62bb7a6aac381af8.png" style="width: 25.60px" src="/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/62bb7a6aac381af8_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> <strong>Show</strong> &gt; <img alt="3343769d04c09851.png" style="width: 29.41px" src="/static/codelabs/webauthn-reauth/img/3343769d04c09851.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/3343769d04c09851_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> <strong>In a New Window</strong> to see <a href="https://webauthn-codelab-start.glitch.me" target="_blank">the live website</a>.</li> <li>Enter a username of your choice and click <strong>Next</strong>.</li> <li>Enter a password and click <strong>Sign-in</strong>.</li> </ol> <p>The password is ignored, but you&#39;re still authenticated. You land at the home page.</p> <ol type="1" start="4"> <li>Click <strong>Try reauth</strong>, and repeat the second, third, and fourth steps.</li> <li>Click <strong>Sign out</strong>.</li> </ol> <p>Notice that you must enter the password every time that you try to sign in. This emulates a user who needs to reauthenticate before they can access an important section of a website.</p> <h2 is-upgraded id="remix-the-code" data-text="Remix the code" tabindex="-1">Remix the code</h2> <ol type="1"> <li>Navigate to <a href="https://glitch.com/edit/#!/webauthn-codelab-start?path=README.md%3A1%3A0" target="_blank">WebAuthn / FIDO2 API Codelab</a>.</li> <li>Click the name of your project &gt; <strong>Remix Project </strong><img alt="306122647ce93305.png" style="width: 20.00px" src="/static/codelabs/webauthn-reauth/img/306122647ce93305.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/306122647ce93305_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> to fork the project and continue with your own version at a new URL.</li> </ol> <p class="image-container"><img alt="8d42bd24f0fd185c.png" style="width: 211.50px" src="/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/8d42bd24f0fd185c_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> </google-codelab-step> <google-codelab-step label="Register a credential with a fingerprint" duration="15" step="2"> <h2 class="step-title" id="2" data-text="Register a credential with a fingerprint" tabindex="-1"> 3. Register a credential with a fingerprint </h2> <p>You need to register a credential generated by a UVPA, an authenticator that is built into the device and verifies the user&#39;s identity. This is typically seen as a fingerprint sensor depending on the user&#39;s device.</p> <p>You add this feature to the <code translate="no" dir="ltr">/home</code> page:</p> <p class="image-container"><img alt="260aab9f1a2587a7.png" style="width: 239.50px" src="/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/260aab9f1a2587a7_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <h2 is-upgraded id="create-registercredential-function" data-text="Create registerCredential() function" tabindex="-1">Create <code translate="no" dir="ltr">registerCredential()</code> function</h2> <p>Create a <code translate="no" dir="ltr">registerCredential()</code> function, which registers a new credential.</p> <h3 is-upgraded id="publicclient.js" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">export const registerCredential = async () =&gt; { }; </code></pre> <aside class="special"><p><strong>Note:</strong> You add <code translate="no" dir="ltr">export</code> before the definition because this file is a JavaScript module. Also, you append <code translate="no" dir="ltr">async</code> before the function call so that you can call <code translate="no" dir="ltr">await</code> inside the function.</p> </aside> <h2 is-upgraded id="obtain-the-challenge-and-other-options-from-server-endpoint" data-text="Obtain the challenge and other options from server endpoint" tabindex="-1">Obtain the challenge and other options from server endpoint</h2> <p>Before you ask the user to register a new credential, request that the server return parameters to pass in WebAuthn, including a challenge. Luckily, you already have a server endpoint that responds with such parameters.</p> <p>Add the following code to <code translate="no" dir="ltr">registerCredential()</code>.</p> <h3 is-upgraded id="publicclient.js_1" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const opts = { attestation: &#39;none&#39;, authenticatorSelection: { authenticatorAttachment: &#39;platform&#39;, userVerification: &#39;required&#39;, requireResidentKey: false } }; const options = await _fetch(&#39;/auth/registerRequest&#39;, opts); </code></pre> <aside class="special"><p><strong>Note:</strong> The <code translate="no" dir="ltr">_fetch()</code> function in this codelab is predefined with POST, <code translate="no" dir="ltr">application/json</code> type taking options as the body. It parses and returns the resulting JSON from the server.</p> </aside> <p>The protocol between a server and a client is not a part of the WebAuthn specification. However, this codelab is designed to align with the WebAuthn specification and the JSON object that you pass to the server is very similar to <a href="https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialcreationoptions" target="_blank"><code translate="no" dir="ltr">PublicKeyCredentialCreationOptions</code></a> so that it&#39;s intuitive for you. The following table contains the important parameters that you can pass to the server and explains what they do:</p> <table class="vertical-rules"> <tr><td colspan="1" rowspan="1"><p><strong>Parameters </strong></p> </td><td colspan="2" rowspan="1"><p><strong>Descriptions</strong></p> </td><td colspan="1" rowspan="1"></td></tr> <tr><td colspan="1" rowspan="1"><p><code translate="no" dir="ltr">attestation</code></p> </td><td colspan="2" rowspan="1"><p>Preference for attestation conveyance—<code translate="no" dir="ltr">none</code>, <code translate="no" dir="ltr">indirect</code>, or <code translate="no" dir="ltr">direct</code>. Choose <code translate="no" dir="ltr">none</code> unless you need one.</p> </td><td colspan="1" rowspan="1"></td></tr> <tr><td colspan="1" rowspan="1"><p><code translate="no" dir="ltr">excludeCredentials</code></p> </td><td colspan="2" rowspan="1"><p>Array of <a href="https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor" target="_blank"><code translate="no" dir="ltr">PublicKeyCredentialDescriptor</code></a> so that the authenticator can avoid creating duplicate ones.</p> </td><td colspan="1" rowspan="1"></td></tr> <tr><td colspan="1" rowspan="1"><p><code translate="no" dir="ltr">authenticatorSelection</code></p> </td><td colspan="1" rowspan="1"><p><code translate="no" dir="ltr">authenticatorAttachment</code></p> </td><td colspan="1" rowspan="1"><p>Filter available authenticators. If you want an authenticator attached to the device, use &#34;<code translate="no" dir="ltr">platform</code>&#34;. For roaming authenticators, use &#34;<code translate="no" dir="ltr">cross-platform</code>&#34;.</p> </td></tr> <tr><td colspan="1" rowspan="1"></td><td colspan="1" rowspan="1"><p><code translate="no" dir="ltr">userVerification</code></p> </td><td colspan="1" rowspan="1"><p>Determine whether authenticator local user verification is &#34;<code translate="no" dir="ltr">required</code>&#34;, &#34;<code translate="no" dir="ltr">preferred</code>&#34;, or &#34;<code translate="no" dir="ltr">discouraged</code>&#34;. If you want fingerprint or screen-lock authentication, use &#34;<code translate="no" dir="ltr">required</code>&#34;.</p> </td></tr> <tr><td colspan="1" rowspan="1"></td><td colspan="1" rowspan="1"><p><code translate="no" dir="ltr">requireResidentKey</code></p> </td><td colspan="1" rowspan="1"><p>Use <code translate="no" dir="ltr">true</code> if the created credential should be available for future account picker UX.</p> </td></tr> </table> <p>To learn more about these options, see <a href="https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialcreationoptions" target="_blank">5.4. Options for Credential Creation (dictionary <code translate="no" dir="ltr">PublicKeyCredentialCreationOptions</code>)</a>.</p> <aside class="special"><p><strong>Note:</strong> You can give the parameters different values and see what happens.</p> </aside> <p>The following are example options that you receive from the server.</p> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">{ &#34;rp&#34;: { &#34;name&#34;: &#34;WebAuthn Codelab&#34;, &#34;id&#34;: &#34;webauthn-codelab.glitch.me&#34; }, &#34;user&#34;: { &#34;displayName&#34;: &#34;User Name&#34;, &#34;id&#34;: &#34;...&#34;, &#34;name&#34;: &#34;test&#34; }, &#34;challenge&#34;: &#34;...&#34;, &#34;pubKeyCredParams&#34;: [ { &#34;type&#34;: &#34;public-key&#34;, &#34;alg&#34;: -7 }, { &#34;type&#34;: &#34;public-key&#34;, &#34;alg&#34;: -257 } ], &#34;timeout&#34;: 1800000, &#34;attestation&#34;: &#34;none&#34;, &#34;excludeCredentials&#34;: [ { &#34;id&#34;: &#34;...&#34;, &#34;type&#34;: &#34;public-key&#34;, &#34;transports&#34;: [ &#34;internal&#34; ] } ], &#34;authenticatorSelection&#34;: { &#34;authenticatorAttachment&#34;: &#34;platform&#34;, &#34;userVerification&#34;: &#34;required&#34; } } </code></pre> <h2 is-upgraded id="create-a-credential" data-text="Create a credential" tabindex="-1">Create a credential</h2> <ol type="1"> <li>Because these options are delivered encoded to go through HTTP protocol, convert some parameters back to binary, specifically, <code translate="no" dir="ltr">user.id</code>, <code translate="no" dir="ltr">challenge</code> and instances of <code translate="no" dir="ltr">id</code> included in the <code translate="no" dir="ltr">excludeCredentials</code> array:</li> </ol> <h3 is-upgraded id="publicclient.js_2" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">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); } } </code></pre> <aside class="special"><p><strong>Note:</strong> You see an error message that says <code translate="no" dir="ltr">'base64url' is not defined.</code> with red dots on Glitch.com UI, but you can ignore it.</p> </aside> <ol type="1" start="2"> <li>Call the <code translate="no" dir="ltr">navigator.credentials.create()</code> method to create a new credential.</li> </ol> <p>With this call, the browser interacts with the authenticator and tries to verify the user&#39;s identity with the UVPA.</p> <h3 is-upgraded id="publicclient.js_3" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const cred = await navigator.credentials.create({ publicKey: options, }); </code></pre> <aside class="special"><p><strong>Note:</strong> Even if the user uses a biometric sensor to create a new credential, the server never sees the biometric information. The authenticator checks that the biometric information that it stored matches the user in front of the device before it creates a new credential or signs with it. The biometric information never leaves the authenticator.</p> </aside> <p>Once the user verifies their identity, you should receive a credential object that you can send to the server and register the authenticator.</p> <h2 is-upgraded id="register-the-credential-to-the-server-endpoint" data-text="Register the credential to the server endpoint" tabindex="-1">Register the credential to the server endpoint</h2> <p>Here&#39;s an example credential object that you should have received.</p> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">{ &#34;id&#34;: &#34;...&#34;, &#34;rawId&#34;: &#34;...&#34;, &#34;type&#34;: &#34;public-key&#34;, &#34;response&#34;: { &#34;clientDataJSON&#34;: &#34;...&#34;, &#34;attestationObject&#34;: &#34;...&#34; } } </code></pre> <ol type="1"> <li>Like when you received an option object for registering a credential, encode the binary parameters of the credential so that it can be delivered to the server as a string:</li> </ol> <h3 is-upgraded id="publicclient.js_4" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">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, }; } </code></pre> <ol type="1" start="2"> <li>Store the credential ID locally so that you can use it for authentication when the user comes back:</li> </ol> <h3 is-upgraded id="publicclient.js_5" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">localStorage.setItem(`credId`, credential.id); </code></pre> <ol type="1" start="3"> <li>Send the object to the server and, if it returns <code translate="no" dir="ltr">HTTP code 200</code>, consider the new credential as successfully registered.</li> </ol> <h3 is-upgraded id="publicclient.js_6" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">return await _fetch(&#39;/auth/registerResponse&#39; , credential); </code></pre> <p>You now have the complete <code translate="no" dir="ltr">registerCredential()</code> function!</p> <h2 is-upgraded id="final-code-for-this-section" data-text="Final code for this section" tabindex="-1">Final code for this section</h2> <h3 is-upgraded id="publicclient.js_7" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">... export const registerCredential = async () =&gt; { const opts = { attestation: &#39;none&#39;, authenticatorSelection: { authenticatorAttachment: &#39;platform&#39;, userVerification: &#39;required&#39;, requireResidentKey: false } }; const options = await _fetch(&#39;/auth/registerRequest&#39;, 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(&#39;/auth/registerResponse&#39; , credential); }; ... </code></pre> </google-codelab-step> <google-codelab-step label="Build the UI to register, get, and remove credentials" duration="10" step="3"> <h2 class="step-title" id="3" data-text="Build the UI to register, get, and remove credentials" tabindex="-1"> 4. Build the UI to register, get, and remove credentials </h2> <p>It&#39;s nice to have a list of registered credentials and buttons to remove them.</p> <p class="image-container"><img alt="9b5b5ae4a7b316bd.png" style="width: 269.25px" src="/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/9b5b5ae4a7b316bd_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <h2 is-upgraded id="build-ui-placeholder" data-text="Build UI placeholder" tabindex="-1">Build UI placeholder</h2> <p>Add UI to list credentials and a button to register a new credential. Depending on whether the feature is available or not, you remove the <code translate="no" dir="ltr">hidden</code> class from either the warning message or the button to register a new credential. <code translate="no" dir="ltr">ul#list</code> is the placeholder for adding a list of registered credentials.</p> <h3 is-upgraded id="viewshome.html" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">&lt;p id=&#34;uvpa_unavailable&#34; class=&#34;hidden&#34;&gt; This device does not support User Verifying Platform Authenticator. You can&#39;t register a credential. &lt;/p&gt; &lt;h3 class=&#34;mdc-typography mdc-typography--headline6&#34;&gt; Your registered credentials: &lt;/h3&gt; &lt;section&gt; &lt;div id=&#34;list&#34;&gt;&lt;/div&gt; &lt;/section&gt; &lt;mwc-button id=&#34;register&#34; class=&#34;hidden&#34; icon=&#34;fingerprint&#34; raised&gt;Add a credential&lt;/mwc-button&gt; </code></pre> <h2 is-upgraded id="feature-detection-and-uvpa-availability" data-text="Feature detection and UVPA availability" tabindex="-1">Feature detection and UVPA availability</h2> <p>Follow these steps to check the UVPA availability:</p> <ol type="1"> <li>Examine <code translate="no" dir="ltr">window.PublicKeyCredential</code> to check if WebAuthn is available.</li> <li>Call <code translate="no" dir="ltr">PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()</code> to check if a UVPA is available . If they&#39;re available, you show the button to register a new credential. If either of them are not available, you show the warning message.</li> </ol> <h3 is-upgraded id="viewshome.html_1" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const register = document.querySelector(&#39;#register&#39;); if (window.PublicKeyCredential) { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() .then(uvpaa =&gt; { if (uvpaa) { register.classList.remove(&#39;hidden&#39;); } else { document .querySelector(&#39;#uvpa_unavailable&#39;) .classList.remove(&#39;hidden&#39;); } }); } else { document .querySelector(&#39;#uvpa_unavailable&#39;) .classList.remove(&#39;hidden&#39;); } </code></pre> <h2 is-upgraded id="get-and-display-a-list-of-credentials" data-text="Get and display a list of credentials" tabindex="-1">Get and display a list of credentials</h2> <ol type="1"> <li>Create a <code translate="no" dir="ltr">getCredentials()</code> function so that you can get registered credentials and display them in a list. Luckily, you already have a handy endpoint on the server <code translate="no" dir="ltr">/auth/getKeys</code> from which you can fetch registered credentials for the signed-in user.</li> </ol> <p>The returned JSON includes credential information, such as <code translate="no" dir="ltr">id</code> and <code translate="no" dir="ltr">publicKey</code>. You can build HTML to show them to the user.</p> <h3 is-upgraded id="viewshome.html_2" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const getCredentials = async () =&gt; { const res = await _fetch(&#39;/auth/getKeys&#39;); const list = document.querySelector(&#39;#list&#39;); const creds = html`${res.credentials.length &gt; 0 ? res.credentials.map(cred =&gt; html` &lt;div class=&#34;mdc-card credential&#34;&gt; &lt;span class=&#34;mdc-typography mdc-typography--body2&#34;&gt;${cred.credId}&lt;/span&gt; &lt;pre class=&#34;public-key&#34;&gt;${cred.publicKey}&lt;/pre&gt; &lt;div class=&#34;mdc-card__actions&#34;&gt; &lt;mwc-button id=&#34;${cred.credId}&#34; @click=&#34;${removeCredential}&#34; raised&gt;Remove&lt;/mwc-button&gt; &lt;/div&gt; &lt;/div&gt;`) : html` &lt;p&gt;No credentials found.&lt;/p&gt; `}`; render(creds, list); }; </code></pre> <aside class="special"><p><strong>Note:</strong> You&#39;re using a library called <code translate="no" dir="ltr">lit-html</code> for handy template building. For more information, see <a href="https://lit-html.polymer-project.org/" target="_blank">lit-html</a>.</p> </aside> <ol type="1" start="2"> <li>Invoke <code translate="no" dir="ltr">getCredentials()</code>to display available credentials as soon as the user lands on the <code translate="no" dir="ltr">/home</code> page.</li> </ol> <h3 is-upgraded id="viewshome.html_3" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">getCredentials(); </code></pre> <h2 is-upgraded id="remove-the-credential" data-text="Remove the credential" tabindex="-1">Remove the credential</h2> <p>In the list of credentials, you added a button to remove each credential. You can send a request to <code translate="no" dir="ltr">/auth/removeKey</code> along with the <code translate="no" dir="ltr">credId</code> query parameter to remove them.</p> <h3 is-upgraded id="publicclient.js_8" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">export const unregisterCredential = async (credId) =&gt; { localStorage.removeItem(&#39;credId&#39;); return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`); }; </code></pre> <ol type="1"> <li>Append <code translate="no" dir="ltr">unregisterCredential</code> to the existing <code translate="no" dir="ltr">import</code> statement.</li> </ol> <h3 is-upgraded id="viewshome.html_4" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">import { _fetch, unregisterCredential } from &#39;/client.js&#39;; </code></pre> <ol type="1" start="2"> <li>Add a function to call when the user clicks <strong>Remove</strong>.</li> </ol> <h3 is-upgraded id="viewshome.html_5" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const removeCredential = async e =&gt; { try { await unregisterCredential(e.target.id); getCredentials(); } catch (e) { alert(e); } }; </code></pre> <h2 is-upgraded id="register-a-credential" data-text="Register a credential" tabindex="-1">Register a credential</h2> <p>You can call <code translate="no" dir="ltr">registerCredential()</code> to register a new credential when the user clicks <strong>Add a credential</strong>.</p> <ol type="1"> <li>Append <code translate="no" dir="ltr">registerCredential</code> to the existing <code translate="no" dir="ltr">import</code> statement.</li> </ol> <h3 is-upgraded id="viewshome.html_6" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">import { _fetch, registerCredential, unregisterCredential } from &#39;/client.js&#39;; </code></pre> <ol type="1" start="2"> <li>Invoke <code translate="no" dir="ltr">registerCredential()</code> with options for <code translate="no" dir="ltr">navigator.credentials.create()</code>.</li> </ol> <p>Don&#39;t forget to renew the credential list by calling <code translate="no" dir="ltr">getCredentials()</code> after registration.</p> <h3 is-upgraded id="viewshome.html_7" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">register.addEventListener(&#39;click&#39;, e =&gt; { registerCredential().then(user =&gt; { getCredentials(); }).catch(e =&gt; alert(e)); }); </code></pre> <p>Now you should be able to register a new credential and display information about it. You may try it on your live website.</p> <h2 is-upgraded id="final-code-for-this-section_1" data-text="Final code for this section" tabindex="-1">Final code for this section</h2> <h3 is-upgraded id="viewshome.html_8" data-text="views/home.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/home.html" target="_blank">views/home.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">... &lt;p id=&#34;uvpa_unavailable&#34; class=&#34;hidden&#34;&gt; This device does not support User Verifying Platform Authenticator. You can&#39;t register a credential. &lt;/p&gt; &lt;h3 class=&#34;mdc-typography mdc-typography--headline6&#34;&gt; Your registered credentials: &lt;/h3&gt; &lt;section&gt; &lt;div id=&#34;list&#34;&gt;&lt;/div&gt; &lt;mwc-fab id=&#34;register&#34; class=&#34;hidden&#34; icon=&#34;add&#34;&gt;&lt;/mwc-fab&gt; &lt;/section&gt; &lt;mwc-button raised&gt;&lt;a href=&#34;/reauth&#34;&gt;Try reauth&lt;/a&gt;&lt;/mwc-button&gt; &lt;mwc-button&gt;&lt;a href=&#34;/auth/signout&#34;&gt;Sign out&lt;/a&gt;&lt;/mwc-button&gt; &lt;/main&gt; &lt;script type=&#34;module&#34;&gt; import { _fetch, registerCredential, unregisterCredential } from &#39;/client.js&#39;; import { html, render } from &#39;https://unpkg.com/lit-html@1.0.0/lit-html.js?module&#39;; const register = document.querySelector(&#39;#register&#39;); if (window.PublicKeyCredential) { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() .then(uvpaa =&gt; { if (uvpaa) { register.classList.remove(&#39;hidden&#39;); } else { document .querySelector(&#39;#uvpa_unavailable&#39;) .classList.remove(&#39;hidden&#39;); } }); } else { document .querySelector(&#39;#uvpa_unavailable&#39;) .classList.remove(&#39;hidden&#39;); } const getCredentials = async () =&gt; { const res = await _fetch(&#39;/auth/getKeys&#39;); const list = document.querySelector(&#39;#list&#39;); const creds = html`${res.credentials.length &gt; 0 ? res.credentials.map(cred =&gt; html` &lt;div class=&#34;mdc-card credential&#34;&gt; &lt;span class=&#34;mdc-typography mdc-typography--body2&#34;&gt;${cred.credId}&lt;/span&gt; &lt;pre class=&#34;public-key&#34;&gt;${cred.publicKey}&lt;/pre&gt; &lt;div class=&#34;mdc-card__actions&#34;&gt; &lt;mwc-button id=&#34;${cred.credId}&#34; @click=&#34;${removeCredential}&#34; raised&gt;Remove&lt;/mwc-button&gt; &lt;/div&gt; &lt;/div&gt;`) : html` &lt;p&gt;No credentials found.&lt;/p&gt; `}`; render(creds, list); }; getCredentials(); const removeCredential = async e =&gt; { try { await unregisterCredential(e.target.id); getCredentials(); } catch (e) { alert(e); } }; register.addEventListener(&#39;click&#39;, e =&gt; { registerCredential({ attestation: &#39;none&#39;, authenticatorSelection: { authenticatorAttachment: &#39;platform&#39;, userVerification: &#39;required&#39;, requireResidentKey: false } }) .then(user =&gt; { getCredentials(); }) .catch(e =&gt; alert(e)); }); &lt;/script&gt; ... </code></pre> <h3 is-upgraded id="publicclient.js_9" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">... export const unregisterCredential = async (credId) =&gt; { localStorage.removeItem(&#39;credId&#39;); return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`); }; ... </code></pre> </google-codelab-step> <google-codelab-step label="Authenticate the user with a fingerprint" duration="10" step="4"> <h2 class="step-title" id="4" data-text="Authenticate the user with a fingerprint" tabindex="-1"> 5. Authenticate the user with a fingerprint </h2> <p>You now have a credential registered and ready to use as a way to authenticate the user. Now you add reauthentication functionality to the website. Here&#39;s the user experience:</p> <p>When a user lands on the <code translate="no" dir="ltr">/reauth</code> page, they see an <strong>Authenticate</strong> button if biometric authentication is possible. Authentication with a fingerprint (UVPA) starts when they tap <strong>Authenticate</strong>, successfully authenticate, and then land on the <code translate="no" dir="ltr">/home</code> page. If biometric authentication is not available or an authentication with biometric fails, the UI falls back to use the existing password form.</p> <p class="image-container"><img alt="b8770c4e7475b075.png" style="width: 239.50px" src="/static/codelabs/webauthn-reauth/img/b8770c4e7475b075.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/b8770c4e7475b075_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <h2 is-upgraded id="create-authenticate-function" data-text="Create authenticate() function" tabindex="-1">Create <code translate="no" dir="ltr">authenticate()</code> function</h2> <p>Create a function called <code translate="no" dir="ltr">authenticate()</code>, which verifies the user&#39;s identity with a fingerprint. You add JavaScript code here:</p> <h3 is-upgraded id="publicclient.js_10" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">export const authenticate = async () =&gt; { }; </code></pre> <h2 is-upgraded id="obtain-the-challenge-and-other-options-from-server-endpoint_1" data-text="Obtain the challenge and other options from server endpoint" tabindex="-1">Obtain the challenge and other options from server endpoint</h2> <ol type="1"> <li>Before authentication, examine if the user has a stored credential ID and set it as a query parameter if they do.</li> </ol> <p>When you provide a credential ID along with other options, the server can provide relevant <code translate="no" dir="ltr">allowCredentials</code> and this makes user verification reliable.</p> <h3 is-upgraded id="publicclient.js_11" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const opts = {}; let url = &#39;/auth/signinRequest&#39;; const credId = localStorage.getItem(`credId`); if (credId) { url += `?credId=${encodeURIComponent(credId)}`; } </code></pre> <ol type="1" start="2"> <li>Before you ask the user to authenticate, ask the server to send back a challenge and other parameters. Call <code translate="no" dir="ltr">_fetch()</code> with <code translate="no" dir="ltr">opts</code> as an argument to send a POST request to the server.</li> </ol> <h3 is-upgraded id="publicclient.js_12" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const options = await _fetch(url, opts); </code></pre> <p>Here are example options you should receive (aligns with <a href="https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions" target="_blank"><code translate="no" dir="ltr">PublicKeyCredentialRequestOptions</code></a>).</p> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">{ &#34;challenge&#34;: &#34;...&#34;, &#34;timeout&#34;: 1800000, &#34;rpId&#34;: &#34;webauthn-codelab.glitch.me&#34;, &#34;userVerification&#34;: &#34;required&#34;, &#34;allowCredentials&#34;: [ { &#34;id&#34;: &#34;...&#34;, &#34;type&#34;: &#34;public-key&#34;, &#34;transports&#34;: [ &#34;internal&#34; ] } ] } </code></pre> <aside class="special"><p><strong>Note:</strong> To learn more about these options, see <a href="https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions" target="_blank">the Web Authentication API specification</a>.</p> </aside> <p>The most important option here is <code translate="no" dir="ltr">allowCredentials</code>. When you receive options from the server, <code translate="no" dir="ltr">allowCredentials</code> should be either a single object in an array or an empty array depending on whether a credential with the ID in the query parameter is found on the server side.</p> <ol type="1" start="3"> <li>Resolve the promise with <code translate="no" dir="ltr">null</code> when <code translate="no" dir="ltr">allowCredentials</code> is an empty array so that the UI falls back to asking for a password.</li> </ol> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">if (options.allowCredentials.length === 0) { console.info(&#39;No registered credentials found.&#39;); return Promise.resolve(null); } </code></pre> <h2 is-upgraded id="locally-verify-the-user-and-get-a-credential" data-text="Locally verify the user and get a credential" tabindex="-1">Locally verify the user and get a credential</h2> <ol type="1"> <li>Because these options are delivered encoded in order to go through HTTP protocol, convert some parameters back to binary, specifically <code translate="no" dir="ltr">challenge</code> and instances of <code translate="no" dir="ltr">id</code> included in the <code translate="no" dir="ltr">allowCredentials</code> array:</li> </ol> <h3 is-upgraded id="publicclient.js_13" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">options.challenge = base64url.decode(options.challenge); for (let cred of options.allowCredentials) { cred.id = base64url.decode(cred.id); } </code></pre> <ol type="1" start="2"> <li>Call the <code translate="no" dir="ltr">navigator.credentials.get()</code> method to verify the user&#39;s identity with a UVPA.</li> </ol> <h3 is-upgraded id="publicclient.js_14" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const cred = await navigator.credentials.get({ publicKey: options }); </code></pre> <p>Once the user verifies their identity, you should receive a credential object that you can send to the server and authenticate the user.</p> <h2 is-upgraded id="verify-the-credential" data-text="Verify the credential" tabindex="-1">Verify the credential</h2> <p>Here&#39;s an example <a href="https://w3c.github.io/webauthn/#publickeycredential" target="_blank"><code translate="no" dir="ltr">PublicKeyCredential</code></a> object (<code translate="no" dir="ltr">response</code> is <a href="https://www.google.com/url?q=https://www.w3.org/TR/webauthn/%23iface-authenticatorassertionresponse&sa=D&ust=1603961749337000&usg=AOvVaw3UFO6uc9BWVrSb2dYaHNLH" target="_blank"><code translate="no" dir="ltr">AuthenticatorAssertionResponse</code></a>) that you should have received:</p> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">{ &#34;id&#34;: &#34;...&#34;, &#34;type&#34;: &#34;public-key&#34;, &#34;rawId&#34;: &#34;...&#34;, &#34;response&#34;: { &#34;clientDataJSON&#34;: &#34;...&#34;, &#34;authenticatorData&#34;: &#34;...&#34;, &#34;signature&#34;: &#34;...&#34;, &#34;userHandle&#34;: &#34;&#34; } } </code></pre> <ol type="1"> <li>Encode the binary parameters of the credential so that it can be delivered to the server as a string:</li> </ol> <h3 is-upgraded id="publicclient.js_15" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">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, }; } </code></pre> <ol type="1" start="2"> <li>Send the object to the server and, if it returns <code translate="no" dir="ltr">HTTP code 200</code>, consider the user as successfully signed in:</li> </ol> <h3 is-upgraded id="publicclient.js_16" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">return await _fetch(`/auth/signinResponse`, credential); </code></pre> <aside class="warning"><p><strong>Note:</strong> The server needs to verify that the <code translate="no" dir="ltr">clientDataJSON</code> is correct, compute its own version of the attestation signature with the public key that it stored at registration time, and compare the result against the signature that the browser presented.</p> <p>However, again, in this codelab, you won&#39;t learn how to execute these verifications on the server side. Please don&#39;t copy the code in this codelab for your production environment. You can find third-party solutions at <a href="https://fidoalliance.org/certification/fido-certified-products/" target="_blank">FIDO Alliance official page</a>, or open source libraries at <a href="https://webauthn.io/" target="_blank">webauthn.io</a> or <a href="https://bit.ly/AwesomeWebAuthn" target="_blank">AwesomeWebAuthn</a>.</p> </aside> <p>You now have the complete <code translate="no" dir="ltr">authentication()</code> function!</p> <h2 is-upgraded id="final-code-for-this-section_2" data-text="Final code for this section" tabindex="-1">Final code for this section</h2> <h3 is-upgraded id="publicclient.js_17" data-text="public/client.js" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/public/client.js" target="_blank">public/client.js</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">... export const authenticate = async () =&gt; { const opts = {}; let url = &#39;/auth/signinRequest&#39;; const credId = localStorage.getItem(`credId`); if (credId) { url += `?credId=${encodeURIComponent(credId)}`; } const options = await _fetch(url, opts); if (options.allowCredentials.length === 0) { console.info(&#39;No registered credentials found.&#39;); 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); }; ... </code></pre> </google-codelab-step> <google-codelab-step label="Enable reauthentication experience" duration="10" step="5"> <h2 class="step-title" id="5" data-text="Enable reauthentication experience" tabindex="-1"> 6. Enable reauthentication experience </h2> <h2 is-upgraded id="build-ui" data-text="Build UI" tabindex="-1">Build UI</h2> <p>When the user comes back, you want them to reauthenticate as easily and securely as possible. This is where biometric authentication shines. However, there are cases in which biometric authentication may not work:</p> <ul> <li>The UVPA is not available.</li> <li>The user has not registered any credentials on their device yet.</li> <li>The storage is cleared and the device no longer remembers the credential ID.</li> <li>The user is unable to verify their identity for some reason, such as when their finger is wet or they&#39;re wearing a mask.</li> </ul> <p>That is why it&#39;s always important that you provide other sign-in options as fallbacks. In this codelab, you use the form-based password solution.</p> <p class="image-container"><img alt="19da999b0145054.png" style="width: 269.77px" src="/static/codelabs/webauthn-reauth/img/19da999b0145054.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/19da999b0145054_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <ol type="1"> <li>Add UI to show an authentication button that invokes the biometric authentication in addition to the password form.</li> </ol> <p>Use the <code translate="no" dir="ltr">hidden</code> class to selectively show and hide one of them depending on the user&#39;s state.</p> <aside class="special"><p><strong>Note:</strong> You might think that you can let the user authenticate without pressing the button, but it&#39;s a requirement on Safari to have a user gesture before invoking a biometric authentication.</p> </aside> <h3 is-upgraded id="viewsreauth.html" data-text="views/reauth.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/reauth.html" target="_blank">views/reauth.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">&lt;div id=&#34;uvpa_available&#34; class=&#34;hidden&#34;&gt; &lt;h2&gt; Verify your identity &lt;/h2&gt; &lt;div&gt; &lt;mwc-button id=&#34;reauth&#34; raised&gt;Authenticate&lt;/mwc-button&gt; &lt;/div&gt; &lt;div&gt; &lt;mwc-button id=&#34;cancel&#34;&gt;Sign-in with password&lt;/mwc-button&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <ol type="1" start="2"> <li>Append <code translate="no" dir="ltr">class="hidden"</code> to the form:</li> </ol> <h3 is-upgraded id="viewsreauth.html_1" data-text="views/reauth.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/reauth.html" target="_blank">views/reauth.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">&lt;form id=&#34;form&#34; method=&#34;POST&#34; action=&#34;/auth/password&#34; class=&#34;hidden&#34;&gt; </code></pre> <h2 is-upgraded id="feature-detection-and-uvpa-availability_1" data-text="Feature detection and UVPA availability" tabindex="-1">Feature detection and UVPA availability</h2> <p>Users must sign in with a password if one of these conditions is met:</p> <ul> <li>WebAuthn is not available.</li> <li>UVPA is not available.</li> <li>A credential ID for this UVPA is not discoverable.</li> </ul> <p>Selectively show the authentication button or hide it:</p> <h3 is-upgraded id="viewsreauth.html_2" data-text="views/reauth.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/reauth.html" target="_blank">views/reauth.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">if (window.PublicKeyCredential) { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() .then(uvpaa =&gt; { if (uvpaa &amp;&amp; localStorage.getItem(`credId`)) { document .querySelector(&#39;#uvpa_available&#39;) .classList.remove(&#39;hidden&#39;); } else { form.classList.remove(&#39;hidden&#39;); } }); } else { form.classList.remove(&#39;hidden&#39;); } </code></pre> <h2 is-upgraded id="fallback-to-password-form" data-text="Fallback to password form" tabindex="-1">Fallback to password form</h2> <p>The user should also be able to choose to sign in with a password.</p> <p>Show the password form and hide the authentication button when the user clicks <strong>Sign in with password</strong>:.</p> <h3 is-upgraded id="viewsreauth.html_3" data-text="views/reauth.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/reauth.html" target="_blank">views/reauth.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const cancel = document.querySelector(&#39;#cancel&#39;); cancel.addEventListener(&#39;click&#39;, e =&gt; { form.classList.remove(&#39;hidden&#39;); document .querySelector(&#39;#uvpa_available&#39;) .classList.add(&#39;hidden&#39;); }); </code></pre> <p class="image-container"><img alt="c4a82800889f078c.png" style="width: 269.50px" src="/static/codelabs/webauthn-reauth/img/c4a82800889f078c.png" srcset="https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_36.png 36w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_48.png 48w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_72.png 72w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_96.png 96w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_480.png 480w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_720.png 720w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_856.png 856w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_960.png 960w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_1440.png 1440w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_1920.png 1920w,https://developers.google.com/static/codelabs/webauthn-reauth/img/c4a82800889f078c_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <h2 is-upgraded id="invoke-the-biometric-authentication" data-text="Invoke the biometric authentication" tabindex="-1">Invoke the biometric authentication</h2> <p>Finally, enable the biometric authentication.</p> <ol type="1"> <li>Append <code translate="no" dir="ltr">authenticate</code> to the existing <code translate="no" dir="ltr">import</code> statement:</li> </ol> <h3 is-upgraded id="viewsreauth.html_4" data-text="views/reauth.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/reauth.html" target="_blank">views/reauth.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">import { _fetch, authenticate } from &#39;/client.js&#39;; </code></pre> <ol type="1" start="2"> <li>Invoke <code translate="no" dir="ltr">authenticate()</code> when the user taps <strong>Authenticate</strong> to start the biometric authentication.</li> </ol> <p>Make sure that a failure on biometric authentication falls back to the password form.</p> <h3 is-upgraded id="viewsreauth.html_5" data-text="views/reauth.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/reauth.html" target="_blank">views/reauth.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">const button = document.querySelector(&#39;#reauth&#39;); button.addEventListener(&#39;click&#39;, e =&gt; { authenticate().then(user =&gt; { if (user) { location.href = &#39;/home&#39;; } else { throw &#39;User not found.&#39;; } }).catch(e =&gt; { console.error(e.message || e); alert(&#39;Authentication failed. Use password to sign-in.&#39;); form.classList.remove(&#39;hidden&#39;); document.querySelector(&#39;#uvpa_available&#39;).classList.add(&#39;hidden&#39;); }); }); </code></pre> <h2 is-upgraded id="final-code-for-this-section_3" data-text="Final code for this section" tabindex="-1">Final code for this section</h2> <h3 is-upgraded id="viewsreauth.html_6" data-text="views/reauth.html" tabindex="-1"><a href="https://github.com/googlecodelabs/fido2-codelab/blob/master/views/reauth.html" target="_blank">views/reauth.html</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">... &lt;main class=&#34;content&#34;&gt; &lt;div id=&#34;uvpa_available&#34; class=&#34;hidden&#34;&gt; &lt;h2&gt; Verify your identity &lt;/h2&gt; &lt;div&gt; &lt;mwc-button id=&#34;reauth&#34; raised&gt;Authenticate&lt;/mwc-button&gt; &lt;/div&gt; &lt;div&gt; &lt;mwc-button id=&#34;cancel&#34;&gt;Sign-in with password&lt;/mwc-button&gt; &lt;/div&gt; &lt;/div&gt; &lt;form id=&#34;form&#34; method=&#34;POST&#34; action=&#34;/auth/password&#34; class=&#34;hidden&#34;&gt; &lt;h2&gt; Enter a password &lt;/h2&gt; &lt;input type=&#34;hidden&#34; name=&#34;username&#34; value=&#34;&#123;&#123;username}}&#34; /&gt; &lt;div class=&#34;mdc-text-field mdc-text-field--filled&#34;&gt; &lt;span class=&#34;mdc-text-field__ripple&#34;&gt;&lt;/span&gt; &lt;label class=&#34;mdc-floating-label&#34; id=&#34;password-label&#34;&gt;password&lt;/label&gt; &lt;input type=&#34;password&#34; class=&#34;mdc-text-field__input&#34; aria-labelledby=&#34;password-label&#34; name=&#34;password&#34; /&gt; &lt;span class=&#34;mdc-line-ripple&#34;&gt;&lt;/span&gt; &lt;/div&gt; &lt;input type=&#34;submit&#34; class=&#34;mdc-button mdc-button--raised&#34; value=&#34;Sign-In&#34; /&gt; &lt;p class=&#34;instructions&#34;&gt;password will be ignored in this demo.&lt;/p&gt; &lt;/form&gt; &lt;/main&gt; &lt;script src=&#34;https://unpkg.com/material-components-web@7.0.0/dist/material-components-web.min.js&#34;&gt;&lt;/script&gt; &lt;script type=&#34;module&#34;&gt; new mdc.textField.MDCTextField(document.querySelector(&#39;.mdc-text-field&#39;)); import { _fetch, authenticate } from &#39;/client.js&#39;; const form = document.querySelector(&#39;#form&#39;); form.addEventListener(&#39;submit&#39;, e =&gt; { e.preventDefault(); const form = new FormData(e.target); const cred = {}; form.forEach((v, k) =&gt; cred[k] = v); _fetch(e.target.action, cred) .then(user =&gt; { location.href = &#39;/home&#39;; }) .catch(e =&gt; alert(e)); }); if (window.PublicKeyCredential) { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() .then(uvpaa =&gt; { if (uvpaa &amp;&amp; localStorage.getItem(`credId`)) { document .querySelector(&#39;#uvpa_available&#39;) .classList.remove(&#39;hidden&#39;); } else { form.classList.remove(&#39;hidden&#39;); } }); } else { form.classList.remove(&#39;hidden&#39;); } const cancel = document.querySelector(&#39;#cancel&#39;); cancel.addEventListener(&#39;click&#39;, e =&gt; { form.classList.remove(&#39;hidden&#39;); document .querySelector(&#39;#uvpa_available&#39;) .classList.add(&#39;hidden&#39;); }); const button = document.querySelector(&#39;#reauth&#39;); button.addEventListener(&#39;click&#39;, e =&gt; { authenticate().then(user =&gt; { if (user) { location.href = &#39;/home&#39;; } else { throw &#39;User not found.&#39;; } }).catch(e =&gt; { console.error(e.message || e); alert(&#39;Authentication failed. Use password to sign-in.&#39;); form.classList.remove(&#39;hidden&#39;); document.querySelector(&#39;#uvpa_available&#39;).classList.add(&#39;hidden&#39;); }); }); &lt;/script&gt; ... </code></pre> </google-codelab-step> <google-codelab-step label="Congratulations!" duration="0" step="6"> <h2 class="step-title" id="6" data-text="Congratulations!" tabindex="-1"> 7. Congratulations! </h2> <p>You finished this codelab!</p> <h2 is-upgraded id="learn-more" data-text="Learn more" tabindex="-1">Learn more</h2> <ul> <li><a href="https://www.w3.org/TR/webauthn/" target="_blank">Web Authentication: An API for accessing Public Key Credentials Level 1</a></li> <li><a href="https://medium.com/@herrjemand/introduction-to-webauthn-api-5fd1fb46c285" target="_blank">Introduction to WebAuthn API</a></li> <li><a href="https://slides.com/fidoalliance/jan-2018-fido-seminar-webauthn-tutorial" target="_blank">FIDO WebAuthn Workshop</a></li> <li><a href="https://webauthn.guide/" target="_blank">WebAuthn Guide: DUOSEC</a></li> <li><a href="https://codelabs.developers.google.com/codelabs/fido2-for-android/" target="_blank">Your first Android FIDO2 API</a></li> </ul> <p>Special thanks to <a href="https://twitter.com/herrjemand" target="_blank">Yuriy Ackermann from FIDO Alliance</a> for your help.</p> </google-codelab-step> </google-codelab> </div> <div class="devsite-floating-action-buttons"> </div> </article> <devsite-content-footer class="nocontent"> <p>Except as otherwise noted, the content of this page is licensed under the <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 License</a>, and code samples are licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>. For details, see the <a href="https://developers.google.com/site-policies">Google Developers Site Policies</a>. Java is a registered trademark of Oracle and/or its affiliates.</p> </devsite-content-footer> <devsite-notification > </devsite-notification> <div class="devsite-content-data"> <template class="devsite-content-data-template"> [[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],[],[],[]] </template> </div> </devsite-content> </main> <devsite-footer-promos class="devsite-footer"> </devsite-footer-promos> <devsite-footer-linkboxes class="devsite-footer"> <nav class="devsite-footer-linkboxes nocontent" aria-label="Footer links"> <ul class="devsite-footer-linkboxes-list"> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Connect</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="//googledevelopers.blogspot.com" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Blog </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.instagram.com/googlefordevs/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Instagram </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.linkedin.com/showcase/googledevelopers/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > LinkedIn </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//twitter.com/googledevs" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > X (Twitter) </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//www.youtube.com/user/GoogleDevelopers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 5)" > YouTube </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Programs</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="//www.womentechmakers.com" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Women Techmakers </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/community/gdg" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Google Developer Groups </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/community/experts" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Google Developer Experts </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/community/accelerators" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Accelerators </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/community/gdsc" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 5)" > Google Developer Student Clubs </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Developer consoles</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="//console.developers.google.com" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Google API Console </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//console.cloud.google.com" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Google Cloud Platform Console </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//play.google.com/apps/publish" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Google Play Console </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//console.firebase.google.com" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Firebase Console </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//console.actions.google.com" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 5)" > Actions on Google Console </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//cast.google.com/publish" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 6)" > Cast SDK Developer Console </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//chrome.google.com/webstore/developer/dashboard" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 7)" > Chrome Web Store Dashboard </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//console.home.google.com" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 8)" > Google Home Developer Console </a> </li> </ul> </li> </ul> </nav> </devsite-footer-linkboxes> <devsite-footer-utility class="devsite-footer"> <div class="devsite-footer-utility nocontent"> <nav class="devsite-footer-sites" aria-label="Other Google Developers websites"> <a href="https://developers.google.com/" class="devsite-footer-sites-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Google Developers Link"> <picture> <img class="devsite-footer-sites-logo" src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/images/lockup-google-for-developers.svg" loading="lazy" alt="Google Developers"> </picture> </a> <ul class="devsite-footer-sites-list"> <li class="devsite-footer-sites-item"> <a href="//developer.android.com" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Android Link" > Android </a> </li> <li class="devsite-footer-sites-item"> <a href="//developer.chrome.com/home" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Chrome Link" > Chrome </a> </li> <li class="devsite-footer-sites-item"> <a href="//firebase.google.com" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Firebase Link" > Firebase </a> </li> <li class="devsite-footer-sites-item"> <a href="//cloud.google.com" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Google Cloud Platform Link" > Google Cloud Platform </a> </li> <li class="devsite-footer-sites-item"> <a href="//ai.google.dev/" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Google AI Link" > Google AI </a> </li> <li class="devsite-footer-sites-item"> <a href="/products" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer All products Link" > All products </a> </li> </ul> </nav> <nav class="devsite-footer-utility-links" aria-label="Utility links"> <ul class="devsite-footer-utility-list"> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="/terms/site-terms" data-category="Site-Wide Custom Events" data-label="Footer Terms link" > Terms </a> </li> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/privacy" data-category="Site-Wide Custom Events" data-label="Footer Privacy link" > Privacy </a> </li> <li class="devsite-footer-utility-item glue-cookie-notification-bar-control"> <a class="devsite-footer-utility-link gc-analytics-event" href="#" data-category="Site-Wide Custom Events" data-label="Footer Manage cookies link" aria-hidden="true" > Manage cookies </a> </li> <li class="devsite-footer-utility-item devsite-footer-utility-button"> <span class="devsite-footer-utility-description">Sign up for the Google for Developers newsletter</span> <a class="devsite-footer-utility-link gc-analytics-event" href="/newsletter/subscribe" data-category="Site-Wide Custom Events" data-label="Footer Subscribe link" > Subscribe </a> </li> </ul> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es" >Español</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> </nav> </div> </devsite-footer-utility> <devsite-panel></devsite-panel> <devsite-concierge data-info-panel data-ai-panel data-api-explorer-panel > </devsite-concierge> </section></section> <devsite-sitemask></devsite-sitemask> <devsite-snackbar></devsite-snackbar> <devsite-tooltip ></devsite-tooltip> <devsite-heading-link></devsite-heading-link> <devsite-analytics> <script type="application/json" analytics>[{&#34;dimensions&#34;: {&#34;dimension1&#34;: &#34;Signed out&#34;, &#34;dimension11&#34;: false, &#34;dimension5&#34;: &#34;en&#34;, &#34;dimension3&#34;: false, &#34;dimension6&#34;: &#34;en&#34;}, &#34;gaid&#34;: &#34;UA-24532603-1&#34;, &#34;metrics&#34;: {&#34;ratings_count&#34;: &#34;metric2&#34;, &#34;ratings_value&#34;: &#34;metric1&#34;}, &#34;purpose&#34;: 1}]</script> <script type="application/json" tag-management>{&#34;at&#34;: &#34;True&#34;, &#34;ga4&#34;: [{&#34;id&#34;: &#34;G-272J68FCRF&#34;, &#34;purpose&#34;: 1}], &#34;ga4p&#34;: [{&#34;id&#34;: &#34;G-272J68FCRF&#34;, &#34;purpose&#34;: 1}], &#34;gtm&#34;: [], &#34;parameters&#34;: {&#34;internalUser&#34;: &#34;False&#34;, &#34;language&#34;: {&#34;machineTranslated&#34;: &#34;False&#34;, &#34;requested&#34;: &#34;en&#34;, &#34;served&#34;: &#34;en&#34;}, &#34;pageType&#34;: &#34;codelab&#34;, &#34;projectName&#34;: null, &#34;signedIn&#34;: &#34;False&#34;, &#34;tenant&#34;: &#34;developers&#34;, &#34;recommendations&#34;: {&#34;sourcePage&#34;: &#34;&#34;, &#34;sourceType&#34;: 0, &#34;sourceRank&#34;: 0, &#34;sourceIdenticalDescriptions&#34;: 0, &#34;sourceTitleWords&#34;: 0, &#34;sourceDescriptionWords&#34;: 0, &#34;experiment&#34;: &#34;&#34;}, &#34;experiment&#34;: {&#34;ids&#34;: &#34;&#34;}}}</script> </devsite-analytics> <devsite-badger></devsite-badger> <script nonce="e0MnSZbp3dAVbZ+lAAqd9rs5MxPLVu"> (function(d,e,v,s,i,t,E){d['GoogleDevelopersObject']=i; t=e.createElement(v);t.async=1;t.src=s;E=e.getElementsByTagName(v)[0]; E.parentNode.insertBefore(t,E);})(window, document, 'script', 'https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/js/app_loader.js', '[1,"en",null,"/js/devsite_app_module.js","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers","https://developers-dot-devsite-v2-prod.appspot.com",1,null,["/_pwa/developers/manifest.json","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/images/video-placeholder.svg","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/images/favicon-new.png","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/developers/images/lockup-new.svg","https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"],1,null,[1,6,8,12,14,17,21,25,50,52,63,70,75,76,80,87,91,92,93,97,98,100,101,102,103,104,105,107,108,109,110,112,113,116,117,118,120,122,124,125,126,127,129,130,131,132,133,134,135,136,138,140,141,147,148,149,151,152,156,157,158,159,161,163,164,168,169,170,179,180,182,183,186,191,193,196],"AIzaSyAP-jjEJBzmIyKR4F-3XITp8yM9T1gEEI8","AIzaSyB6xiKGDR5O3Ak2okS4rLkauxGUG7XP0hg","developers.google.com","AIzaSyAQk0fBONSGUqCNznf6Krs82Ap1-NV6J4o","AIzaSyCCxcqdrZ_7QMeLCRY20bh_SXdAYqy70KY",null,null,null,["Search__enable_suggestions_from_borg","Cloud__enable_cloud_dlp_service","Profiles__enable_public_developer_profiles","Profiles__enable_developer_profiles_callout","Profiles__enable_recognition_badges","Cloud__enable_cloud_shell_fte_user_flow","Significatio__enable_by_tenant","CloudShell__cloud_shell_button","Search__enable_page_map","Concierge__enable_concierge_restricted","MiscFeatureFlags__developers_footer_image","Profiles__enable_awarding_url","MiscFeatureFlags__enable_explain_this_code","Cloud__enable_cloud_facet_chat","Concierge__enable_pushui","MiscFeatureFlags__developers_footer_dark_image","Profiles__enable_profile_collections","Cloud__enable_llm_concierge_chat","DevPro__enable_cloud_innovators_plus","MiscFeatureFlags__enable_variable_operator","Search__enable_dynamic_content_confidential_banner","MiscFeatureFlags__emergency_css","Cloud__enable_cloud_shell","Profiles__enable_page_saving","Cloud__enable_legacy_calculator_redirect","Cloud__enable_free_trial_server_call","Search__enable_ai_search_summaries","CloudShell__cloud_code_overflow_menu","Concierge__enable_concierge","Search__enable_ai_search_summaries_restricted","Search__enable_ai_eligibility_checks","Profiles__enable_dashboard_curated_recommendations","Profiles__enable_release_notes_notifications","EngEduTelemetry__enable_engedu_telemetry","BookNav__enable_tenant_cache_key","TpcFeatures__enable_required_headers","Cloud__enable_cloudx_ping","MiscFeatureFlags__enable_view_transitions","Profiles__require_profile_eligibility_for_signin","Experiments__reqs_query_experiments","MiscFeatureFlags__enable_firebase_utm","Profiles__enable_complete_playlist_endpoint","Profiles__enable_completecodelab_endpoint","Cloud__enable_cloudx_experiment_ids","DevPro__enable_developer_subscriptions","MiscFeatureFlags__enable_project_variables","TpcFeatures__enable_mirror_tenant_redirects","Analytics__enable_clearcut_logging"],null,null,"AIzaSyBLEMok-5suZ67qRPzx0qUtbnLmyT_kCVE","https://developerscontentserving-pa.clients6.google.com","AIzaSyCM4QpTRSqP5qI4Dvjt4OAScIN8sOUlO-k","https://developerscontentsearch-pa.clients6.google.com",1,4,null,"https://developerprofiles-pa.clients6.google.com",[1,"developers","Google for Developers","developers.google.com",null,"developers-dot-devsite-v2-prod.appspot.com",null,null,[1,1,[1],null,null,null,null,null,null,null,null,[1],null,null,null,null,null,null,[1],[1,null,null,[1,20],"/recommendations/information"],null,null,null,[1,1,1],[1,1,null,1,1]],null,[null,null,null,null,null,null,"/images/lockup-new.svg","/images/touchicon-180-new.png",null,null,null,null,1,null,null,null,null,null,null,null,null,1,null,null,null,"/images/lockup-dark-theme-new.svg",[]],[],null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,[6,1,14,15,20,22,23,29,32,36],null,[[null,null,null,[3,7,10,2,39,17,4,32,24,11,12,13,34,15,25],null,null,[1,[["docType","Choose a content type",[["Tutorial",null,null,null,null,null,null,null,null,"Tutorial"],["Guide",null,null,null,null,null,null,null,null,"Guide"],["Sample",null,null,null,null,null,null,null,null,"Sample"]]],["product","Choose a product",[["Android",null,null,null,null,null,null,null,null,"Android"],["ARCore",null,null,null,null,null,null,null,null,"ARCore"],["ChromeOS",null,null,null,null,null,null,null,null,"ChromeOS"],["Firebase",null,null,null,null,null,null,null,null,"Firebase"],["Flutter",null,null,null,null,null,null,null,null,"Flutter"],["Assistant",null,null,null,null,null,null,null,null,"Google Assistant"],["GoogleCloud",null,null,null,null,null,null,null,null,"Google Cloud"],["GoogleMapsPlatform",null,null,null,null,null,null,null,null,"Google Maps Platform"],["GooglePay",null,null,null,null,null,null,null,null,"Google Pay & Google Wallet"],["GooglePlay",null,null,null,null,null,null,null,null,"Google Play"],["Tensorflow",null,null,null,null,null,null,null,null,"TensorFlow"]]],["category","Choose a topic",[["AiAndMachineLearning",null,null,null,null,null,null,null,null,"AI and Machine Learning"],["Data",null,null,null,null,null,null,null,null,"Data"],["Enterprise",null,null,null,null,null,null,null,null,"Enterprise"],["Gaming",null,null,null,null,null,null,null,null,"Gaming"],["Mobile",null,null,null,null,null,null,null,null,"Mobile"],["Web",null,null,null,null,null,null,null,null,"Web"]]]]]],[1,1],null,1],[[["UA-24532603-1"],["UA-22084204-5"],null,null,["UA-24532603-5"],null,null,[["G-272J68FCRF"],null,null,[["G-272J68FCRF",2]]],[["UA-24532603-1",2]],null,[["UA-24532603-5",2]],null,1],[[5,4],[3,2],[4,3],[14,11],[16,13],[6,5],[15,12],[13,10],[11,8],[1,1],[12,9]],[[1,1],[2,2]]],null,4,null,null,null,null,null,null,null,null,null,null,null,null,null,"developers.devsite.google"],null,"pk_live_5170syrHvgGVmSx9sBrnWtA5luvk9BwnVcvIi7HizpwauFG96WedXsuXh790rtij9AmGllqPtMLfhe2RSwD6Pn38V00uBCydV4m"]') </script> <devsite-a11y-announce></devsite-a11y-announce> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10