CINXE.COM

Learn how to simplify auth journeys using Credential Manager API in your Android app  |  Google Codelabs

<!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 Codelabs"> <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/codelabs/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/codelabs/css/app.css"> <link rel="shortcut icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/codelabs/images/favicon.png"> <link rel="apple-touch-icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/codelabs/images/touchicon-180.png"><link rel="canonical" href="https://codelabs.developers.google.com/credential-manager-api-for-android"><link rel="search" type="application/opensearchdescription+xml" title="Google Codelabs" href="https://codelabs.developers.google.com/s/opensearch.xml"> <link rel="alternate" hreflang="en" href="https://codelabs.developers.google.com/credential-manager-api-for-android" /><link rel="alternate" hreflang="x-default" href="https://codelabs.developers.google.com/credential-manager-api-for-android" /><link rel="alternate" hreflang="ar" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=ar" /><link rel="alternate" hreflang="bn" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=bn" /><link rel="alternate" hreflang="zh-Hans" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=zh-tw" /><link rel="alternate" hreflang="fa" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=fa" /><link rel="alternate" hreflang="fr" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=fr" /><link rel="alternate" hreflang="de" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=de" /><link rel="alternate" hreflang="he" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=he" /><link rel="alternate" hreflang="hi" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=hi" /><link rel="alternate" hreflang="id" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=id" /><link rel="alternate" hreflang="it" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=it" /><link rel="alternate" hreflang="ja" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=ja" /><link rel="alternate" hreflang="ko" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=ko" /><link rel="alternate" hreflang="pl" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=pl" /><link rel="alternate" hreflang="pt-BR" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=pt-br" /><link rel="alternate" hreflang="ru" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=ru" /><link rel="alternate" hreflang="es-419" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=es-419" /><link rel="alternate" hreflang="th" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=th" /><link rel="alternate" hreflang="tr" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=tr" /><link rel="alternate" hreflang="vi" href="https://codelabs.developers.google.com/credential-manager-api-for-android?hl=vi" /><title>Learn how to simplify auth journeys using Credential Manager API in your Android app &nbsp;|&nbsp; Google Codelabs</title> <meta property="og:title" content="Learn how to simplify auth journeys using Credential Manager API in your Android app &nbsp;|&nbsp; Google Codelabs"><meta name="description" content="Learn how to implement Credential Manager API to provide seamless &amp;amp; secure auth. in your app using passkeys or password."> <meta property="og:description" content="Learn how to implement Credential Manager API to provide seamless &amp;amp; secure auth. in your app using passkeys or password."><meta property="og:url" content="https://codelabs.developers.google.com/credential-manager-api-for-android"><meta property="og:locale" content="en"> <link rel="stylesheet" href="/extras.css"></head> <body class="" template="codelab" theme="codelabs-theme" type="codelab" layout="docs" 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="googleCodelabs" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/codelabs/images/lockup.svg" class="devsite-site-logo" alt="Google Codelabs"> </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 Codelabs" > <form class="devsite-search-form" action="https://codelabs.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="googleCodelabs" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/codelabs/images/lockup.svg" class="devsite-site-logo" alt="Google Codelabs"> </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" has-sidebar > <div class="devsite-sidebar"> <div class="devsite-sidebar-content"> <devsite-toc class="devsite-nav" role="navigation" aria-label="On this page" depth="1" scrollbars ></devsite-toc> <devsite-recommendations-sidebar class="nocontent devsite-nav"> </devsite-recommendations-sidebar> </div> </div> <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"> Learn how to simplify auth journeys using Credential Manager API in your Android app </h1> <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="1CkPRDjJru3s9glZQMX52b7w5j2jK0-b5RFp1I9Afx7w" id="credential-manager-api-for-android" title="Learn how to simplify auth journeys using Credential Manager API in your Android app" no-tooltip="" environment="web" category="" feedback-link="https://github.com/android/identity-samples/issues" layout="Paginated" > <google-codelab-step label="Before you begin" duration="2" step="0"> <google-codelab-about codelab-title="Learn how to simplify auth journeys using Credential Manager API in your Android app" authors="Niharika Arora" last-updated="2023-07-27T18:34:51Z" duration="27"> </google-codelab-about> <h2 class="step-title" id="0" data-text="Before you begin" tabindex="-1"> 1. Before you begin </h2> <p><em>Traditional authentication solutions pose a number of security and usability challenges.</em></p> <p>Passwords are widely used but...</p> <ul> <li>Easily forgotten</li> <li>Users require knowledge to create strong passwords.</li> <li>Easy to phish, harvest and replay by attackers.</li> </ul> <p>Android has worked towards creating <a href="https://developer.android.com/training/sign-in/passkeys" target="_blank">Credential Manager API</a> to simplify the sign-in experience and address security risks by supporting <a href="https://developers.google.com/identity/passkeys/" target="_blank">passkeys</a>, the next generation industry standard for <strong>passwordless authentication</strong>.</p> <p>Credential Manager brings together support for passkeys and combines it with traditional authentication methods such as passwords, Sign in with Google etc.</p> <p>Users will be able to create passkeys, store them in Google Password Manager, which will <strong>sync</strong> those passkeys across the Android devices where the user is signed in. A passkey has to be created, associated with a user account, and have its public key stored on a server before a user can sign in with it.</p> <aside class="special"><p><strong>Note</strong>: Support for 3rd-party password managers is available on devices that run Android 14 or higher.</p> </aside> <p>In this codelab, you will learn how to sign up using passkeys and password using Credential Manager API and use them for future authentication purposes. There are 2 flows including:</p> <ul> <li>Sign up : using passkeys and password.</li> <li>Sign in : using passkeys &amp; saved password.</li> </ul> <h2 is-upgraded id="prerequisites" data-text="Prerequisites" tabindex="-1">Prerequisites</h2> <ul> <li>Basic understanding of how to run apps in Android Studio.</li> <li>Basic understanding of authentication flow in Android apps.</li> <li>Basic understanding of <a href="https://developers.google.com/identity/passkeys/" target="_blank">passkeys</a>.</li> </ul> <h2 class="checklist" is-upgraded id="what-youll-learn" data-text="What you'll learn" tabindex="-1">What you'll learn</h2> <ul class="checklist"> <li>How to create a passkey.</li> <li>How to save password in password manager.</li> <li>How to authenticate users with a passkey or saved password.</li> </ul> <h2 is-upgraded id="what-youll-need" data-text="What you'll need" tabindex="-1">What you'll need</h2> <p>One of the following device combinations:</p> <ul> <li>An Android device that runs Android 9 or higher (for passkeys) and Android 4.4 or higher(for password authentication through Credential Manager API).</li> <li>Device preferably with a biometric sensor.</li> <li>Make sure to register a biometric (or screen lock).</li> <li>Kotlin plugin version : 1.8.10</li> </ul> <aside class="special"><p><strong>Note</strong>: Credential Manager API is supported from Android 4.4 for passwords and sign-in with Google options. Passkeys are supported only on devices that run Android 9 (API level 28)</p> <p>or higher.</p> </aside> </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> <aside class="special"><p><strong>Note</strong>: The first step to enable support for passkeys for your Android app is to associate your app and the website.</p> <p>This sample app requires a <a href="https://developers.google.com/digital-asset-links" target="_blank">digital asset linking</a> to a website for Credential Manager to validate the linking and proceed further, so the rp id used in the mock responses is from a mocked 3P server(Glitch.me). If you want to try your own mock response, try adding your app domain and don&#39;t forget to complete the digital asset linking as mentioned <a href="https://developer.android.com/training/sign-in/passkeys#add-support-dal" target="_blank">here</a>.</p> <p>Use the same debug.keystore mentioned in the project to build debug and release variants to verify the digital asset linking of the package name and sha on your mock server. (This is already being done for you for the sample app in build.gradle).</p> </aside> <ol type="1"> <li>Clone this repo on your laptop from <strong>credman_codelab</strong> branch : <a href="https://github.com/android/identity-samples/tree/credman_codelab" target="_blank">https://github.com/android/identity-samples/tree/credman_codelab</a></li> <li>Go to the <strong>CredentialManager</strong> module and open the project in Android Studio.</li> </ol> <h2 is-upgraded id="lets-see-apps-initial-state" data-text="Lets see app's initial state" tabindex="-1"><strong>Lets see app's initial state</strong></h2> <p>To see how the initial state of the app works, follow these steps:</p> <ol type="1"> <li>Launch the app.</li> <li>You see a main screen with a sign up and sign in button.</li> <li>You can click sign up to sign up using a passkey or a password.</li> <li>You can click sign in to sign in using passkey &amp; saved password.</li> </ol> <p class="image-container"><img alt="8c0019ff9011950a.jpeg" style="width: 199.50px" src="/static/credential-manager-api-for-android/img/8c0019ff9011950a.jpeg" srcset="https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_36.jpeg 36w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_48.jpeg 48w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_72.jpeg 72w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_96.jpeg 96w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_480.jpeg 480w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_720.jpeg 720w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_856.jpeg 856w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_960.jpeg 960w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_1440.jpeg 1440w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_1920.jpeg 1920w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/8c0019ff9011950a_2880.jpeg 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <p>To understand what passkeys are and how they work, see <a href="https://developers.google.com/identity/passkeys#how-do-passkeys-work" target="_blank">How do passkeys work?</a> .</p> </google-codelab-step> <google-codelab-step label="Add the ability to sign up using passkeys" duration="10" step="2"> <h2 class="step-title" id="2" data-text="Add the ability to sign up using passkeys" tabindex="-1"> 3. Add the ability to sign up using passkeys </h2> <p>When signing up for a new account on an Android app that uses the Credential Manager API, users can create a passkey for their account. This passkey will be securely stored on the user&#39;s chosen credential provider and used for future sign-ins, without requiring the user to enter their password each time.</p> <p>Now, you will create a passkey and register user credentials using biometrics/screen lock.</p> <h2 is-upgraded id="sign-up-with-passkey" data-text="Sign up with passkey" tabindex="-1">Sign up with passkey</h2> <p>Inside Credential Manager -&gt; app -&gt; main -&gt; java -&gt; SignUpFragment.kt, you can see a text field &#34;username&#34; and a button to sign up with passkey.</p> <p class="image-container"><img alt="dcc5c529b310f2fb.jpeg" style="width: 199.50px" src="/static/credential-manager-api-for-android/img/dcc5c529b310f2fb.jpeg" srcset="https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_36.jpeg 36w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_48.jpeg 48w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_72.jpeg 72w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_96.jpeg 96w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_480.jpeg 480w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_720.jpeg 720w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_856.jpeg 856w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_960.jpeg 960w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_1440.jpeg 1440w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_1920.jpeg 1920w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/dcc5c529b310f2fb_2880.jpeg 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <h2 is-upgraded id="pass-the-challenge-and-other-json-response-to-createpasskey-call" data-text="Pass the challenge and other json response to createPasskey() call" tabindex="-1">Pass the challenge and other json response to createPasskey() call</h2> <p>Before a passkey is created, you need to request the server to get necessary information to be passed to the Credential Manager API during <strong>createCredential</strong>() call.</p> <p>Luckily, you already have a mock response in your assets(<strong>RegFromServer.txt</strong>) which returns such parameters in this codelab.</p> <ul> <li>In your app, navigate to the <a href="http://signupfragment.kt" target="_blank">SignUpFragment.kt</a>, Find, <strong>signUpWithPasskeys</strong> method where you will write the logic for creating a passkey and letting the user in. You can find the method in the same class.</li> <li>Check the else block with a comment to call createPasskey() and replace with following code :</li> </ul> <h3 is-upgraded id="signupfragment.kt" data-text="SignUpFragment.kt" tabindex="-1">SignUpFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO : Call createPasskey() to signup with passkey</span> <span class="devsite-syntax-n">val</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">data</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">createPasskey</span><span class="devsite-syntax-p">()</span> </code></pre></devsite-code> <p>This method will be called once you have a valid username filled on the screen.</p> <ul> <li>Inside the createPasskey() method, you need to create a <strong>CreatePublicKeyCredentialRequest()</strong> with necessary params returned.</li> </ul> <h3 is-upgraded id="signupfragment.kt_1" data-text="SignUpFragment.kt" tabindex="-1"><strong>SignUpFragment.kt</strong></h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server</span> <span class="devsite-syntax-n">val</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">request</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">CreatePublicKeyCredentialRequest</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">fetchRegistrationJsonFromServer</span><span class="devsite-syntax-p">())</span> </code></pre></devsite-code> <p>This fetchRegistrationJsonFromServer() is a method, which reads registration json response from assets and returns the registration json to be passed while creating passkey.</p> <ul> <li>Find fetchRegistrationJsonFromServer() method and replace the TODO with following code to return json and also <strong>remove</strong> the empty string return statement :</li> </ul> <h3 is-upgraded id="signupfragment.kt_2" data-text="SignUpFragment.kt" tabindex="-1">SignUpFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO fetch registration mock response</span> <span class="devsite-syntax-n">val</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">response</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">requireContext</span><span class="devsite-syntax-p">().</span><span class="devsite-syntax-n">readFromAsset</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s">"RegFromServer"</span><span class="devsite-syntax-p">)</span> <span class="devsite-syntax-c1">//Update userId,challenge, name and Display name in the mock</span> <span class="devsite-syntax-k">return</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">response</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">replace</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s">"&lt;userId&gt;"</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">getEncodedUserId</span><span class="devsite-syntax-p">())</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">replace</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s">"&lt;userName&gt;"</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">binding</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">username</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">text</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">toString</span><span class="devsite-syntax-p">())</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">replace</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s">"&lt;userDisplayName&gt;"</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">binding</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">username</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">text</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">toString</span><span class="devsite-syntax-p">())</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">replace</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s">"&lt;challenge&gt;"</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">getEncodedChallenge</span><span class="devsite-syntax-p">())</span> </code></pre></devsite-code> <ul> <li>Here, you read the registration json from assets.</li> <li>This json has 4 fields to be replaced.</li> <li>UserId needs to be unique so that a user can create multiple passkeys (if required). You replace &lt;userId&gt; with the userId generated.</li> <li>&lt;challenge&gt; also needs to be unique so you will be generating a random unique challenge. The method is already in your code.</li> </ul> <aside class="special"><p>Note: UserId and challenge needs to be encoded using Base64 encoder. (Check <strong>getEncodedUserId()</strong> and <strong>getEncodedChallenge()</strong>)</p> </aside> <p>The following code snippet includes sample options that you receive from the server:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Carbon"><code translate="no" dir="ltr"><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"challenge"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"rp"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"name"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"id"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">},</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"user"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"id"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"name"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"displayName"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">},</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"pubKeyCredParams"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">[</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"type"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"public-key"</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"alg"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">-</span><span class="devsite-syntax-mi">7</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">},</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"type"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"public-key"</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"alg"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">-</span><span class="devsite-syntax-mi">257</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">],</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"timeout"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mi">1800000</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"attestation"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"none"</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"excludeCredentials"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">[],</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"authenticatorSelection"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"authenticatorAttachment"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"platform"</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"requireResidentKey"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kc">true</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"residentKey"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"required"</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"userVerification"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"required"</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p>The following table isn&#39;t exhaustive, but it contains the important parameters in the <code translate="no" dir="ltr">PublicKeyCredentialCreationOptions</code> dictionary:</p> <table class="vertical-rules"> <tr><td colspan="1" rowspan="1"><p><strong>Parameters</strong></p> </td><td colspan="1" rowspan="1"><p><strong>Descriptions</strong></p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-challenge" target="_blank"><code translate="no" dir="ltr">challenge</code></a></p> </td><td colspan="1" rowspan="1"><p>A server-generated random string that contains enough entropy to make guessing it infeasible. It should be at least 16 bytes long. This is required but unused during registration unless doing <a href="https://developer.mozilla.org/docs/Web/API/Web_Authentication_API/Attestation_and_Assertion" target="_blank">attestation</a>.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialuserentity-id" target="_blank"><code translate="no" dir="ltr">user.id</code></a></p> </td><td colspan="1" rowspan="1"><p>A user&#39;s unique ID. This value must not include personally identifying information, for example, e-mail addresses or usernames. A random, 16-byte value generated per account will work well.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialentity-name" target="_blank"><code translate="no" dir="ltr">user.name</code></a></p> </td><td colspan="1" rowspan="1"><p>This field should hold a unique identifier for the account that the user will recognise, like their email address or username. This will be displayed in the account selector. (If using a username, use the same value as in password authentication.)</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialuserentity-displayname" target="_blank"><code translate="no" dir="ltr">user.displayName</code></a></p> </td><td colspan="1" rowspan="1"><p>This field is an optional, more user-friendly name for the account. It&#39;s a human-palatable name for the user account, intended only for display.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialrpentity-id" target="_blank"><code translate="no" dir="ltr">rp.id</code></a></p> </td><td colspan="1" rowspan="1"><p>The Relying Party Entity corresponds to your application details.It needs : </p> <ul> <li>a name (required): your application name</li> <li>an ID (optional): corresponds to the domain or subdomain. If absent, the current domain is used.</li> <li>an icon (optional). </li> </ul> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-pubkeycredparams" target="_blank"><code translate="no" dir="ltr">pubKeyCredParams</code></a></p> </td><td colspan="1" rowspan="1"><p>The Public Key Credential Parameters is a list of allowed algorithms and key types. This list must contain at least one element.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-excludecredentials" target="_blank"><code translate="no" dir="ltr">excludeCredentials</code></a></p> </td><td colspan="1" rowspan="1"><p>The user trying to register a device may have registered other devices. To limit the creation of multiple credentials for the same account on a single authenticator, you can then ignore these devices. The <a href="https://w3c.github.io/webauthn/#dom-publickeycredentialdescriptor-transports" target="_blank">transports</a> member, if provided, should contain the result of calling <a href="https://w3c.github.io/webauthn/#dom-authenticatorattestationresponse-gettransports" target="_blank">getTransports()</a> during the registration of each credential.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredential-authenticatorattachment" target="_blank"><code translate="no" dir="ltr">authenticatorSelection.authenticatorAttachment</code></a></p> </td><td colspan="1" rowspan="1"><p>indicates if the device should be attached on the platform or not or if there is no requirement about it. Set it to &#34;platform&#34;. This indicates that we want an authenticator that is embedded into the platform device, and the user will not be prompted to insert e.g. a USB security key.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><code translate="no" dir="ltr">residentKey</code></p> </td><td colspan="1" rowspan="1"><p>indicate a value &#34;required&#34; to create a passkey.</p> </td></tr> </table> <h2 is-upgraded id="create-a-credential" data-text="Create a credential" tabindex="-1">Create a credential</h2> <ol type="1"> <li>Once you create a CreatePublicKeyCredentialRequest(), you need to call the <strong>createCredential</strong>() call with the created request.</li> </ol> <h3 is-upgraded id="signupfragment.kt_3" data-text="SignUpFragment.kt" tabindex="-1">SignUpFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO call createCredential() with createPublicKeyCredentialRequest</span> <span class="devsite-syntax-k">try</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">response</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">credentialManager</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">createCredential</span><span class="devsite-syntax-p">(</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">requireActivity</span><span class="devsite-syntax-p">(),</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">request</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">as</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">CreatePublicKeyCredentialResponse</span> <span class="devsite-syntax-p">}</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">catch</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">e</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">CreateCredentialException</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">configureProgress</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">View</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">INVISIBLE</span><span class="devsite-syntax-p">)</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">handlePasskeyFailure</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">e</span><span class="devsite-syntax-p">)</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <ul> <li>You pass the required information to createCredential().</li> <li>Once the request is successful, you would see a bottomsheet on your screen prompting you to create a passkey.</li> <li>Now users can verify their identity through biometrics or screen lock etc.</li> <li>You handle the rendered views visibility and handle the exceptions if the request fails or unsuccessful due to some reason. Here the error messages are logged and shown on the app in an error dialog. You can check the full error logs through Android studio or adb debug command</li> </ul> <aside class="warning"><p><strong>Your data is safe:</strong> Even if the user uses a biometric sensor to create a passkey, the server never sees the biometric data. The device only verifies that the stored biometric information matches the user&#39;s one locally to create a passkey or sign in with it. Biometric information never leaves the device.</p> </aside> <p class="image-container"><img alt="93022cb87c00f1fc.png" style="width: 197.00px" src="/static/credential-manager-api-for-android/img/93022cb87c00f1fc.png" srcset="https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_36.png 36w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_48.png 48w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_72.png 72w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_96.png 96w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_480.png 480w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_720.png 720w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_856.png 856w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_960.png 960w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_1440.png 1440w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_1920.png 1920w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/93022cb87c00f1fc_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <aside class="special"><p><strong>Note</strong>: createCredential() call is a suspend function, so this needs to be called through a coroutineScope/another suspend function.</p> </aside> <ol type="1" start="2"> <li>Now, finally you need to complete the registration process by sending the public key credential to the server and letting the user in. The app receives a credential object that contains a public key that you can send to the server to register the passkey.</li> </ol> <p>Here, we have used a mock server, so we just return true indicating that server has saved the registered public key for future authentication and validation purposes.</p> <p>Inside signUpWithPasskeys() method, find relevant comment and replace with following code:</p> <h3 is-upgraded id="signupfragment.kt_4" data-text="SignUpFragment.kt" tabindex="-1">SignUpFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO : complete the registration process after sending public key credential to your server and let the user in</span> <span class="devsite-syntax-n">data</span>?<span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">let</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">registerResponse</span><span class="devsite-syntax-p">()</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">DataProvider</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">setSignedInThroughPasskeys</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">true</span><span class="devsite-syntax-p">)</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">listener</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">showHome</span><span class="devsite-syntax-p">()</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <ul> <li>registerResponse return true indicating (mock) server has saved the public key for future use.</li> <li>You setSignedInThroughPasskeys flag as true, indicating that you are logging in through passkeys.</li> <li>Once logged in, you redirect your user to the home screen.</li> </ul> <p>The following code snippet contains an example options you should receive:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Carbon"><code translate="no" dir="ltr"><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"id"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"rawId"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"type"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"public-key"</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"response"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"clientDataJSON"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"attestationObject"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p>The following table isn&#39;t exhaustive, but it contains the important parameters in <code translate="no" dir="ltr">PublicKeyCredential</code>:</p> <table class="vertical-rules"> <tr><td colspan="1" rowspan="1"><p><strong>Parameters</strong></p> </td><td colspan="1" rowspan="1"><p><strong>Descriptions</strong></p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#credential-id" target="_blank"><code translate="no" dir="ltr">id</code></a></p> </td><td colspan="1" rowspan="1"><p>A Base64URL encoded ID of the created passkey. This ID helps the browser determine whether a matching passkey is in the device upon authentication. This value must be stored in the database on the backend.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#credential-id" target="_blank"><code translate="no" dir="ltr">rawId</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> object version of credential ID.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#client-data" target="_blank"><code translate="no" dir="ltr">response.clientDataJSON</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> object encoded client data.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#attestation-object" target="_blank"><code translate="no" dir="ltr">response.attestationObject</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> encoded attestation object. It contains important information, such as an RP ID, flags, and a public key.</p> </td></tr> </table> <p>Run the app, and you will be able to click on the Sign up with passkeys button and create a passkey.</p> </google-codelab-step> <google-codelab-step label="Save password in Credential Provider" duration="3" step="3"> <h2 class="step-title" id="3" data-text="Save password in Credential Provider" tabindex="-1"> 4. Save password in Credential Provider </h2> <p>In this app, inside your SignUp screen, you already have a sign up with username and password implemented for demonstration purposes.</p> <p>To save the user password credential with their password provider, you will implement a CreatePasswordRequest to pass to createCredential() to save the password.</p> <ul> <li>Find signUpWithPassword() method, replace the TODO to <strong>createPassword</strong> call :</li> </ul> <h3 is-upgraded id="signupfragment.kt_5" data-text="SignUpFragment.kt" tabindex="-1">SignUpFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO : Save the user credential password with their password provider</span> <span class="devsite-syntax-n">createPassword</span><span class="devsite-syntax-p">()</span> </code></pre></devsite-code> <ul> <li>Inside <strong>createPassword()</strong> method, you need to create password request like this, replace the TODO with following code :</li> </ul> <h3 is-upgraded id="signupfragment.kt_6" data-text="SignUpFragment.kt" tabindex="-1">SignUpFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO : CreatePasswordRequest with entered username and password</span> <span class="devsite-syntax-n">val</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">request</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">CreatePasswordRequest</span><span class="devsite-syntax-p">(</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">binding</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">username</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">text</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">toString</span><span class="devsite-syntax-p">(),</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">binding</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">password</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">text</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">toString</span><span class="devsite-syntax-p">()</span> <span class="devsite-syntax-p">)</span> </code></pre></devsite-code> <ul> <li>Next, inside createPassword() method, you need to create credential with create password request and save the user password credential with their password provider, replace the TODO with following code :</li> </ul> <h3 is-upgraded id="signupfragment.kt_7" data-text="SignUpFragment.kt" tabindex="-1">SignUpFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO : Create credential with created password request</span> <span class="devsite-syntax-k">try</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">credentialManager</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">createCredential</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">request</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">requireActivity</span><span class="devsite-syntax-p">())</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">as</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">CreatePasswordResponse</span> <span class="devsite-syntax-p">}</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">catch</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">e</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">Exception</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">Log</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">e</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s">"Auth"</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">" Exception Message : "</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">+</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">e</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">message</span><span class="devsite-syntax-p">)</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <ul> <li>Now you have successfully saved the password credential with the user&#39;s password provider to authenticate via password in just one-tap.</li> </ul> </google-codelab-step> <google-codelab-step label="Add the ability to authenticate with a passkey or password" duration="10" step="4"> <h2 class="step-title" id="4" data-text="Add the ability to authenticate with a passkey or password" tabindex="-1"> 5. Add the ability to authenticate with a passkey or password </h2> <p>Now you are ready to use it as a way to authenticate to your app safely.</p> <p class="image-container"><img alt="629001f4a778d4fb.png" style="width: 202.73px" src="/static/credential-manager-api-for-android/img/629001f4a778d4fb.png" srcset="https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_36.png 36w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_48.png 48w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_72.png 72w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_96.png 96w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_480.png 480w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_720.png 720w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_856.png 856w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_960.png 960w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_1440.png 1440w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_1920.png 1920w,https://codelabs.developers.google.com/static/credential-manager-api-for-android/img/629001f4a778d4fb_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <h2 is-upgraded id="obtain-the-challenge-and-other-options-to-pass-to-getpasskey-call" data-text="Obtain the challenge and other options to pass to getPasskey() call" tabindex="-1">Obtain the challenge and other options to pass to getPasskey() call</h2> <p>Before you ask the user to authenticate, you need to request parameters to pass in WebAuthn json from the server, including a challenge.</p> <p>You already have a mock response in your assets(<strong>AuthFromServer.txt</strong>) which returns such parameters in this codelab.</p> <ul> <li>In your app, navigate to the SignInFragment.kt, find <code translate="no" dir="ltr">signInWithSavedCredentials</code> method where you will write the logic for authenticating through saved passkey or password and let the user in :</li> <li>Check the else block with a comment to call createPasskey() and replace with following code :</li> </ul> <h3 is-upgraded id="signinfragment.kt" data-text="SignInFragment.kt" tabindex="-1">SignInFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO : Call getSavedCredentials() method to signin using passkey/password</span> <span class="devsite-syntax-n">val</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">data</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">getSavedCredentials</span><span class="devsite-syntax-p">()</span> </code></pre></devsite-code> <ul> <li>Inside the getSavedCredentials() method, you need to create a <code translate="no" dir="ltr">GetPublicKeyCredentialOption</code><strong>()</strong> with necessary parameters required to get credentials from your credential provider.</li> </ul> <h3 is-upgraded id="signinfragment.kt_1" data-text="SigninFragment.kt" tabindex="-1"><strong>SigninFragment.kt</strong></h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server</span> <span class="devsite-syntax-n">val</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">getPublicKeyCredentialOption</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">GetPublicKeyCredentialOption</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-n">fetchAuthJsonFromServer</span><span class="devsite-syntax-p">(),</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">null</span><span class="devsite-syntax-p">)</span> </code></pre></devsite-code> <p>This fetchAuthJsonFromServer() is a method, which reads authentication json response from assets and returns the authentication json to retrieve all the passkeys associated with this user account.</p> <p>2nd parameter : clientDataHash - a hash that is used to verify the relying party identity, set only if you have set the GetCredentialRequest.origin. For the sample app, this is null.</p> <p>The 3rd parameter is true if you prefer the operation to return <strong>immediately</strong> when there is no available credential instead of falling back to discovering remote credentials, and false (default) otherwise.</p> <ul> <li>Find fetchAuthJsonFromServer() method and replace the TODO with following code to return json and also <strong>remove</strong> the empty string return statement :</li> </ul> <h3 is-upgraded id="signinfragment.kt_2" data-text="SignInFragment.kt" tabindex="-1">SignInFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO fetch authentication mock json</span> <span class="devsite-syntax-k">return</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">requireContext</span><span class="devsite-syntax-p">().</span><span class="devsite-syntax-n">readFromAsset</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s">"AuthFromServer"</span><span class="devsite-syntax-p">)</span> </code></pre></devsite-code> <p>Note : This codelab&#39;s server is designed to return a JSON that is as similar as possible to the <a href="https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptions" target="_blank"><code translate="no" dir="ltr">PublicKeyCredentialRequestOptions</code></a> dictionary that&#39;s passed to API&#39;s getCredential() call. The following code snippet includes a few example options that you should receive:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Text only"><code translate="no" dir="ltr">{ "challenge": String, "rpId": String, "userVerification": "", "timeout": 1800000 } </code></pre></devsite-code> <p>The following table isn&#39;t exhaustive, but it contains the important parameters in the <code translate="no" dir="ltr">PublicKeyCredentialRequestOptions</code> dictionary:</p> <table class="vertical-rules"> <tr><td colspan="1" rowspan="1"><p><strong>Parameters</strong></p> </td><td colspan="1" rowspan="1"><p><strong>Descriptions</strong></p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-challenge" target="_blank"><code translate="no" dir="ltr">challenge</code></a></p> </td><td colspan="1" rowspan="1"><p>A server-generated challenge in an <code translate="no" dir="ltr">ArrayBuffer</code> object. This is required to prevent replay attacks. Never accept the same challenge in a response twice. Consider it a <a href="https://portswigger.net/web-security/csrf/bypassing-token-validation" target="_blank">CSRF token</a>.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialrequestoptions-rpid" target="_blank"><code translate="no" dir="ltr">rpId</code></a></p> </td><td colspan="1" rowspan="1"><p>An RP ID is a domain. A website can specify either its domain or <a href="https://web.dev/same-site-same-origin/#public-suffix-list-and-etld" target="_blank">a registrable suffix</a>. This value must match the <code translate="no" dir="ltr">rp.id</code> parameter used when the passkey was created.</p> </td></tr> </table> <ul> <li>Next you need to create a PasswordOption() object to retrieve all the saved passwords saved in your password provider through the Credential Manager API for this user account. Inside getSavedCredentials() method, find the TODO and replace with the following :</li> </ul> <h3 is-upgraded id="signinfragment.kt_3" data-text="SigninFragment.kt" tabindex="-1"><strong>SigninFragment.kt</strong></h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO create a PasswordOption to retrieve all the associated user's password</span> <span class="devsite-syntax-n">val</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">getPasswordOption</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">GetPasswordOption</span><span class="devsite-syntax-p">()</span> </code></pre></devsite-code> <h2 is-upgraded id="get-credentials" data-text="Get credentials" tabindex="-1">Get credentials</h2> <ul> <li>Next you need to call getCredential() request with all the above options to retrieve the associated credentials :</li> </ul> <h3 is-upgraded id="signinfragment.kt_4" data-text="SignInFragment.kt" tabindex="-1">SignInFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Genshi"><code translate="no" dir="ltr">//TODO<span class="devsite-syntax-w"> </span>call<span class="devsite-syntax-w"> </span>getCredential()<span class="devsite-syntax-w"> </span>with<span class="devsite-syntax-w"> </span>required<span class="devsite-syntax-w"> </span>credential<span class="devsite-syntax-w"> </span>options val<span class="devsite-syntax-w"> </span>result<span class="devsite-syntax-w"> </span>=<span class="devsite-syntax-w"> </span>try<span class="devsite-syntax-w"> </span>{ <span class="devsite-syntax-w"> </span>credentialManager.getCredential( <span class="devsite-syntax-w"> </span>requireActivity(), <span class="devsite-syntax-w"> </span>GetCredentialRequest( <span class="devsite-syntax-w"> </span>listOf( <span class="devsite-syntax-w"> </span>getPublicKeyCredentialOption, <span class="devsite-syntax-w"> </span>getPasswordOption <span class="devsite-syntax-w"> </span>)<span class="devsite-syntax-w"> </span> <span class="devsite-syntax-w"> </span>) <span class="devsite-syntax-w"> </span>) }<span class="devsite-syntax-w"> </span>catch<span class="devsite-syntax-w"> </span>(e:<span class="devsite-syntax-w"> </span>Exception)<span class="devsite-syntax-w"> </span>{ <span class="devsite-syntax-w"> </span>configureViews(View.INVISIBLE,<span class="devsite-syntax-w"> </span>true) <span class="devsite-syntax-w"> </span>Log.e("Auth",<span class="devsite-syntax-w"> </span>"getCredential<span class="devsite-syntax-w"> </span>failed<span class="devsite-syntax-w"> </span>with<span class="devsite-syntax-w"> </span>exception:<span class="devsite-syntax-w"> </span>"<span class="devsite-syntax-w"> </span>+<span class="devsite-syntax-w"> </span>e.message.toString()) <span class="devsite-syntax-w"> </span>activity?.showErrorAlert( <span class="devsite-syntax-w"> </span>"An<span class="devsite-syntax-w"> </span>error<span class="devsite-syntax-w"> </span>occurred<span class="devsite-syntax-w"> </span>while<span class="devsite-syntax-w"> </span>authenticating<span class="devsite-syntax-w"> </span>through<span class="devsite-syntax-w"> </span>saved<span class="devsite-syntax-w"> </span>credentials.<span class="devsite-syntax-w"> </span>Check<span class="devsite-syntax-w"> </span>logs<span class="devsite-syntax-w"> </span>for<span class="devsite-syntax-w"> </span>additional<span class="devsite-syntax-w"> </span>details" <span class="devsite-syntax-w"> </span>) <span class="devsite-syntax-w"> </span>return<span class="devsite-syntax-w"> </span>null } if<span class="devsite-syntax-w"> </span>(result.credential<span class="devsite-syntax-w"> </span>is<span class="devsite-syntax-w"> </span>PublicKeyCredential)<span class="devsite-syntax-w"> </span>{ <span class="devsite-syntax-w"> </span>val<span class="devsite-syntax-w"> </span>cred<span class="devsite-syntax-w"> </span>=<span class="devsite-syntax-w"> </span>result.credential<span class="devsite-syntax-w"> </span>as<span class="devsite-syntax-w"> </span>PublicKeyCredential <span class="devsite-syntax-w"> </span>DataProvider.setSignedInThroughPasskeys(true) <span class="devsite-syntax-w"> </span>return<span class="devsite-syntax-w"> </span>"Passkey:<span class="devsite-syntax-w"> </span><span class="devsite-syntax-cp">${</span><span class="devsite-syntax-n">cred</span><span class="devsite-syntax-o">.</span><span class="devsite-syntax-n">authenticationResponseJson</span><span class="devsite-syntax-cp">}</span>" } if<span class="devsite-syntax-w"> </span>(result.credential<span class="devsite-syntax-w"> </span>is<span class="devsite-syntax-w"> </span>PasswordCredential)<span class="devsite-syntax-w"> </span>{ <span class="devsite-syntax-w"> </span>val<span class="devsite-syntax-w"> </span>cred<span class="devsite-syntax-w"> </span>=<span class="devsite-syntax-w"> </span>result.credential<span class="devsite-syntax-w"> </span>as<span class="devsite-syntax-w"> </span>PasswordCredential <span class="devsite-syntax-w"> </span>DataProvider.setSignedInThroughPasskeys(false) <span class="devsite-syntax-w"> </span>return<span class="devsite-syntax-w"> </span>"Got<span class="devsite-syntax-w"> </span>Password<span class="devsite-syntax-w"> </span>-<span class="devsite-syntax-w"> </span>User:<span class="devsite-syntax-cp">${</span><span class="devsite-syntax-n">cred</span><span class="devsite-syntax-o">.</span><span class="devsite-syntax-n">id</span><span class="devsite-syntax-cp">}</span><span class="devsite-syntax-w"> </span>Password:<span class="devsite-syntax-w"> </span><span class="devsite-syntax-cp">${</span><span class="devsite-syntax-n">cred</span><span class="devsite-syntax-o">.</span><span class="devsite-syntax-n">password</span><span class="devsite-syntax-cp">}</span>" } if<span class="devsite-syntax-w"> </span>(result.credential<span class="devsite-syntax-w"> </span>is<span class="devsite-syntax-w"> </span>CustomCredential)<span class="devsite-syntax-w"> </span>{ <span class="devsite-syntax-w"> </span>//If<span class="devsite-syntax-w"> </span>you<span class="devsite-syntax-w"> </span>are<span class="devsite-syntax-w"> </span>also<span class="devsite-syntax-w"> </span>using<span class="devsite-syntax-w"> </span>any<span class="devsite-syntax-w"> </span>external<span class="devsite-syntax-w"> </span>sign-in<span class="devsite-syntax-w"> </span>libraries,<span class="devsite-syntax-w"> </span>parse<span class="devsite-syntax-w"> </span>them<span class="devsite-syntax-w"> </span>here<span class="devsite-syntax-w"> </span>with<span class="devsite-syntax-w"> </span>the<span class="devsite-syntax-w"> </span>utility<span class="devsite-syntax-w"> </span>functions<span class="devsite-syntax-w"> </span>provided. } </code></pre></devsite-code> <ul> <li>You pass the required information to getCredential(). This takes the list of credential options and an activity context to render the options in the bottomsheet in that context.</li> <li>Once the request is successful, you would see a bottomsheet on your screen listing all the created credentials for the associated account.</li> <li>Now users can verify their identity through biometrics or screen lock etc to authenticate the chosen credential.</li> <li>You setSignedInThroughPasskeys flag as true, indicating that you are logging in through passkeys. Otherwise false.</li> <li>You handle the rendered views visibility and handle the exceptions if the request fails or unsuccessful due to some reason. Here the error messages are logged and shown on the app in an error dialog. You can check the full error logs through Android studio or adb debug command</li> </ul> <aside class="special"><p><strong>Note</strong>: getCredential() call is a suspend function, so this needs to be called through a coroutineScope/another suspend function.</p> </aside> <ul> <li>Now, finally you need to complete the registration process by sending the public key credential to the server and letting the user in. The app receives a credential object that contains a public key that you can send to the server to authenticate via the passkey.</li> </ul> <p>Here, we have used a mock server, so we just return true indicating that server has validated the public key.</p> <p>Inside <code translate="no" dir="ltr">signInWithSavedCredentials</code>() method, find relevant comment and replace with following code:</p> <h3 is-upgraded id="signinfragment.kt_5" data-text="SignInFragment.kt" tabindex="-1">SignInFragment.kt</h3> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Scilab"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">//TODO : complete the authentication process after validating the public key credential to your server and let the user in.</span> <span class="devsite-syntax-n">data</span>?<span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">let</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">sendSignInResponseToServer</span><span class="devsite-syntax-p">()</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-n">listener</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-n">showHome</span><span class="devsite-syntax-p">()</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <ul> <li>sendSigninResponseToServer() returns true indicating (mock) server has validated the public key for future use.</li> <li>Once logged in, you redirect your user to the home screen.</li> </ul> <p>The following code snippet includes an example <code translate="no" dir="ltr">PublicKeyCredential</code> object:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Carbon"><code translate="no" dir="ltr"><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"id"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"rawId"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"type"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"public-key"</span><span class="devsite-syntax-p">,</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"response"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"clientDataJSON"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"authenticatorData"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"signature"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-s">"userHandle"</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">String</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p>The following table isn&#39;t exhaustive, but it contains the important parameters in the <code translate="no" dir="ltr">PublicKeyCredential</code> object:</p> <table class="vertical-rules"> <tr><td colspan="1" rowspan="1"><p><strong>Parameters</strong></p> </td><td colspan="1" rowspan="1"><p><strong>Descriptions</strong></p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#credential-id" target="_blank"><code translate="no" dir="ltr">id</code></a></p> </td><td colspan="1" rowspan="1"><p>The Base64URL encoded ID of the authenticated passkey credential.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#credential-id" target="_blank"><code translate="no" dir="ltr">rawId</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> object version of credential ID.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#client-data" target="_blank"><code translate="no" dir="ltr">response.clientDataJSON</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> object of client data. This field contains information, such as the challenge and the origin that the RP server needs to verify.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-authenticatorassertionresponse-authenticatordata" target="_blank"><code translate="no" dir="ltr">response.authenticatorData</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> object of authenticator data. This field contains information like RP ID.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-authenticatorassertionresponse-signature" target="_blank"><code translate="no" dir="ltr">response.signature</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> object of the signature. This value is the core of the credential and must be verified on the server.</p> </td></tr> <tr><td colspan="1" rowspan="1"><p><a href="https://w3c.github.io/webauthn/#dom-authenticatorassertionresponse-userhandle" target="_blank"><code translate="no" dir="ltr">response.userHandle</code></a></p> </td><td colspan="1" rowspan="1"><p>An <code translate="no" dir="ltr">ArrayBuffer</code> object that contains the user ID set at creation time. This value can be used instead of the credential ID if the server needs to pick the ID values that it uses, or if the backend wishes to avoid the creation of an index on credential IDs.</p> </td></tr> </table> <p>Run the app, navigate to sign in -&gt; Sign in with passkeys/saved password and try signing in using saved credentials.</p> <h2 is-upgraded id="try-it" data-text="Try it" tabindex="-1">Try it</h2> <p>You implemented the creation of passkeys, saving password in Credential Manager, and authentication through passkeys or saved password using Credential Manager API on your Android app.</p> </google-codelab-step> <google-codelab-step label="Congratulations!" duration="0" step="5"> <h2 class="step-title" id="5" data-text="Congratulations!" tabindex="-1"> 6. Congratulations! </h2> <p>You finished this codelab! If you want to check the final resolution, it is available at <a href="https://github.com/android/identity-samples/tree/main/CredentialManager" target="_blank">https://github.com/android/identity-samples/tree/main/CredentialManager</a></p> <p>If you have any questions, ask them on <a href="https://stackoverflow.com/questions/tagged/passkey" target="_blank">StackOverflow with a <code translate="no" dir="ltr">passkey</code> tag</a>.</p> <h2 is-upgraded id="learn-more" data-text="Learn more" tabindex="-1">Learn more</h2> <ul> <li><a href="https://developer.android.com/training/sign-in/passkeys" target="_blank">Sign in your user with Credential Manager</a></li> <li><a href="https://medium.com/androiddevelopers/bringing-seamless-authentication-to-your-apps-using-credential-manager-api-b3f0d09e0093" target="_blank">Bringing seamless authentication to your apps with passkeys using Credential Manager API</a></li> <li><a href="https://android-developers.googleblog.com/2022/10/bringing-passkeys-to-android-and-chrome.html" target="_blank">Bringing passkeys to Android &amp; Chrome</a></li> </ul> </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="//www.facebook.com/Google-Developers-967415219957038" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Facebook </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//medium.com/google-developers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Medium </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)" > 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="//developers.google.com/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="//developers.google.com/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="//developers.google.com/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="//developers.google.com/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> </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/codelabs/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="//developers.google.com/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="//developers.google.com/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 Developers newsletter</span> <a class="devsite-footer-utility-link gc-analytics-event" href="//services.google.com/fb/forms/googledevelopersnewsletter/?utm_medium=referral&amp;utm_source=google-products&amp;utm_team=googledevs&amp;utm_campaign=201611-newsletter-launch" 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> </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>[]</script> <script type="application/json" tag-management>{&#34;at&#34;: &#34;True&#34;, &#34;ga4&#34;: [], &#34;ga4p&#34;: [], &#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;codelabs&#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="ym6x5LYx8mW90A7a0dKBzPh9Yc3NRC"> (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/codelabs/js/app_loader.js', '[17,"en",null,"/js/devsite_app_module.js","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/codelabs","https://codelabs-dot-devsite-v2-prod.appspot.com",1,null,["/_pwa/codelabs/manifest.json","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/images/video-placeholder.svg","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/codelabs/images/favicon.png","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/codelabs/images/lockup.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","codelabs.developers.google.com","AIzaSyAQk0fBONSGUqCNznf6Krs82Ap1-NV6J4o","AIzaSyCCxcqdrZ_7QMeLCRY20bh_SXdAYqy70KY",null,null,null,["MiscFeatureFlags__enable_variable_operator","TpcFeatures__enable_mirror_tenant_redirects","Profiles__enable_dashboard_curated_recommendations","Cloud__enable_cloud_facet_chat","Cloud__enable_cloudx_experiment_ids","Cloud__enable_free_trial_server_call","Experiments__reqs_query_experiments","Search__enable_page_map","Profiles__enable_completecodelab_endpoint","Profiles__enable_recognition_badges","Cloud__enable_cloud_shell","Profiles__enable_public_developer_profiles","DevPro__enable_developer_subscriptions","Cloud__enable_legacy_calculator_redirect","MiscFeatureFlags__enable_firebase_utm","Profiles__enable_profile_collections","EngEduTelemetry__enable_engedu_telemetry","Analytics__enable_clearcut_logging","Search__enable_suggestions_from_borg","Profiles__enable_awarding_url","Search__enable_dynamic_content_confidential_banner","MiscFeatureFlags__developers_footer_dark_image","Cloud__enable_cloud_shell_fte_user_flow","MiscFeatureFlags__enable_explain_this_code","Concierge__enable_pushui","Cloud__enable_cloud_dlp_service","Profiles__enable_page_saving","TpcFeatures__enable_required_headers","MiscFeatureFlags__enable_project_variables","Profiles__enable_complete_playlist_endpoint","Cloud__enable_llm_concierge_chat","BookNav__enable_tenant_cache_key","MiscFeatureFlags__emergency_css","Profiles__enable_developer_profiles_callout","Profiles__require_profile_eligibility_for_signin","Profiles__enable_release_notes_notifications","Cloud__enable_cloudx_ping","DevPro__enable_cloud_innovators_plus","CloudShell__cloud_shell_button","MiscFeatureFlags__enable_view_transitions","Search__enable_ai_eligibility_checks","MiscFeatureFlags__developers_footer_image","CloudShell__cloud_code_overflow_menu"],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",[17,"codelabs","Google Codelabs","codelabs.developers.google.com",null,"codelabs-dot-devsite-v2-prod.appspot.com",null,null,[null,1,null,null,null,null,null,null,null,null,null,[1],null,null,null,null,null,null,[1],null,null,null,null,[1]],null,[33,null,null,null,null,null,"/images/lockup.svg",null,null,null,null,1,1,null,null,null,null,null,null,null,null,1,null,null,null,null,[]],[],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,[[],[1,1]],[[null,null,null,null,null,null,null,null,null,null,null,null,1]],null,4],null,"pk_live_5170syrHvgGVmSx9sBrnWtA5luvk9BwnVcvIi7HizpwauFG96WedXsuXh790rtij9AmGllqPtMLfhe2RSwD6Pn38V00uBCydV4m"]') </script> <devsite-a11y-announce></devsite-a11y-announce> </body> </html>

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