|
| 1 | +# Credential Manager Sample App |
| 2 | + |
| 3 | +This is the repository for the Credential Manager API code integration app, |
| 4 | +also known as the **"Shrine"** app. |
| 5 | + |
| 6 | +The Shrine app is a fully functional Android app built with Kotlin and Jetpack Compose. |
| 7 | +This sample app is built to share a working sample of Credential Manager APIs in Android |
| 8 | +and help visualize the workflow. This sample code is designed to help you understand the |
| 9 | +workflow better and allow you to estimate the level of effort needed to incorporate |
| 10 | +Credential Manager with your own apps. |
| 11 | + |
| 12 | +## Features |
| 13 | + |
| 14 | +This sample app implements the following use cases: |
| 15 | + |
| 16 | +* Create an account using username and set the session using password. |
| 17 | +* Generate a new passkey for an existing account |
| 18 | +* Store the credentials for created accounts in the user's Google Password Manager account. |
| 19 | +* Sign in flow with passkeys support |
| 20 | +* Sign in flow with restore credentials support |
| 21 | +* Logout from the account. |
| 22 | + |
| 23 | +## Requirements |
| 24 | + |
| 25 | +* Latest release of [Android Studio](https://developer.android.com/studio) |
| 26 | +* Java 11 or higher |
| 27 | +* A web browser with the ability to access [Glitch](https://glitch.com/). |
| 28 | + |
| 29 | +## Typical account creation and login flow |
| 30 | + |
| 31 | +* Launch the app |
| 32 | +* Create an account by sending any username to the server |
| 33 | +* Set a session by sending a password in step 2 |
| 34 | +* Register user credentials using your fingerprint sensor |
| 35 | +* From the list of passkeys options shown in the bottom sheet, select the correct passkey option to login |
| 36 | +* Logout of the application and close the app |
| 37 | +* [Optional] Create multiple accounts and switch accounts to test the implementation |
| 38 | + |
| 39 | + |
| 40 | +## How to setup your own Glitch.me server |
| 41 | + |
| 42 | +The Shrine app sends requests to a Glitch.me server, and out of the box this code example has been |
| 43 | +configured to use a Glitch instance that we've created. To use your own Glitch-hosted backend, |
| 44 | +follow these steps. The backend code uses your Android package and SHA fingerprint, and you will |
| 45 | +update these on the server. |
| 46 | + |
| 47 | +1. Go to the edit page of the website at [https://glitch.com/edit/#!/credential-manager-app-test](https://glitch.com/edit/#!/credential-manager-app-test) |
| 48 | + |
| 49 | +2. Find the ***"Remix to Edit"*** button at the top right corner. By pressing the button, you can fork the code and continue this tutorial with your own version of the project and services. |
| 50 | + |
| 51 | +3. To use the API on an Android app, you need to associate it with a website and share credentials between them. To set this up, you'll use [Digital Asset Links](https://developer.android.com/training/sign-in/passkeys#add-support-dal). Digital Asset Links files are used to declare associations by hosting a JSON file on your website, and adding a link to this file to your app's manifest. Normally, you'll define an association between your app and the website by creating a JSON file and put it at `.well-known/assetlinks.json` on your HTTPS server. **For this demo, we have a server code that creates an `assetlinks.json` file automatically, just by adding the following environment params to the `.env` file in Glitch:** |
| 52 | + |
| 53 | + 1. In the Glitch left nav Files section, click on the `.env` file. This opens up your project's Environment Config. Fill in the following values: |
| 54 | + |
| 55 | + 2. `HOSTNAME`: The name of your newly created Glitch service. The project name is found on top left of your Glitch project screen. It'll be something like `peaceful-banana-fern`. Paste or type in the name of your Glitch project into the `HOSTNAME` section. |
| 56 | + |
| 57 | + 3. `ANDROID_PACKAGENAME`: The package name of your app, such as `com.google.credentialmanager.sample`. You can find the package name in your project's app-level `build.gradle` file as the value of the `applicationId` property within the `android` block. |
| 58 | + |
| 59 | + 4. `ANDROID_SHA256HASH`: SHA-256 hash of your signing certificate. To get the SHA-256 hash of your developer signing certificate, use the following command: `keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore`. The default password of the debug keystore is "android". The SHA256 value appears under Certificate fingerprints. (`75:89:78:74:...`) |
| 60 | + |
| 61 | + |
| 62 | +4. In your `build.gradle`'s `android` block, find the fields for `buildConfigField` and `resValue`, and update the following values. |
| 63 | + |
| 64 | + 1. `buildConfigField / API_BASE_URL`: The URL of your new Glitch server's API. It'll be the full URL + /path appended to the end. For example: `https://peaceful-banana-fern.glitch.me/auth` |
| 65 | + |
| 66 | + 2. `resValue / host`: The root URL for your server. For example: [https://peaceful-banana-fern.glitch.me](https://peaceful-banana-fern.glitch.me) |
| 67 | + |
| 68 | + |
| 69 | +5. Sync your `build.gradle` changes by running **File > Sync Project with Gradle Files**. |
| 70 | + |
| 71 | +6. Test building your app. Run a physical or emulated device that has a valid and passkey-enabled Google account set up, then run your app on it. You should see the Shrine app home screen appear, with Sign In and Sign Up buttons. Don't click anything just yet, you'll do that in the next step. |
| 72 | + |
| 73 | + |
| 74 | +## Integration |
| 75 | + |
| 76 | +Follow these steps to test Credential Manager integration. In the app, look for a toast to appear to indicate a response success or failure on each step. |
| 77 | + |
| 78 | +### Create an account for username on the server |
| 79 | + |
| 80 | +1. When your app runs the first time, you should see a screen with buttons for Sign In and Sign Up. Click the **Sign Up** button. The **Create Account** screen appears. |
| 81 | + |
| 82 | +2. Enter an email address and unique password and click the **Submit** button. |
| 83 | + |
| 84 | +3. You should now see a **Create a passkey** screen. Click **Create a passkey**. |
| 85 | + |
| 86 | +4. You should see a Google Password Manager bottom sheet appear, offering to save your credentials. Click **Continue**. |
| 87 | + |
| 88 | +5. The Shrine app should then show the **Create a passkey** screen. Click the **Create a passkey** button and a Google Password Manager bottom sheet should appear that offers to create a passkey for your app. Click **Continue**. You should now see the Shrine main menu. |
| 89 | + |
| 90 | +6. Click **Step 1: Send Username** after adding a username and email in the 1st field. For demo purposes, the app and the server will accept any username. |
| 91 | + |
| 92 | +7. Check the username, and create a new account if it doesn't exist. |
| 93 | + |
| 94 | +8. Set a `username` in the session. |
| 95 | + |
| 96 | +9. Wait for the toast to appear saying "Username verified successfully". If you don't see toast, check the logs for errors. |
| 97 | + |
| 98 | + |
| 99 | +### **Set a session on server** |
| 100 | + |
| 101 | +This step demonstrates if developers want to do additional authentication (2FA). This step shows how 2FA can be done while using passkeys for authentication. |
| 102 | + |
| 103 | +1. Check **Step 2: Send Password**. Above that field, add any password. Type and send the request. |
| 104 | + |
| 105 | +2. Verify the user credential and let the user sign-in. No preceding registration is required. |
| 106 | + |
| 107 | +3. Wait for the toast to appear that says "Session-id stored successfully, Do register!" |
| 108 | + |
| 109 | +4. If you don't see toast, check the logs for errors. |
| 110 | + |
| 111 | + |
| 112 | +### **Pass required information to a passkey creation prompt** |
| 113 | + |
| 114 | +This section describes how to send a registration request to the server and pass the required information to a passkey creation prompt. |
| 115 | + |
| 116 | +1. Register the passkey credential. Inside `AuthRepository.kt`, find `registerRequest`. |
| 117 | + |
| 118 | +2. Once the request is sent from the client, this method calls the server API `/auth/registerRequest`. The API returns an `ApiResult` with all the `PublicKeyCredentialCreationOptions` that the client needs to generate a new credential. |
| 119 | + |
| 120 | + |
| 121 | +### **Create a passkey** |
| 122 | + |
| 123 | +In this section, you'll create a passkey with the response received from `/registerRequest`. |
| 124 | + |
| 125 | +1. Parse the params as per needed for the create credentials call. Call `createPasskey()` from `Auth.kt` |
| 126 | + |
| 127 | +2. Give users the choice to enroll a passkey and use it for re-authentication by registering a user credential using a `CreatePublicKeyCredentialRequest()` object. |
| 128 | + |
| 129 | +3. This method calls `createCredential()` from *Credential Manager API*, which registers a user credential that can be used to authenticate the user to the app in the future. This method launches framework UI flows for a user to view their registration options, grant consent, etc. |
| 130 | + |
| 131 | +4. Use your fingerprint or other auth. Methods from your device to register. |
| 132 | + |
| 133 | + |
| 134 | +### **Send the registration response and register a user credential** |
| 135 | + |
| 136 | +This section describes how to send a registration response back to the server and register a user credential on a server. |
| 137 | + |
| 138 | +- Call `/registerResponse`. This `registerResponse` method is called after the user interface successfully generates a new credential, and you want to send it back to the server. |
| 139 | + |
| 140 | +- Use the response received from the `createPasskey()` call and pass it back to your server. |
| 141 | + |
| 142 | +- Remember the ID of your local key so you can distinguish it from other keys registered on the server. In the `PublicKeyCredential` object, use the `rawId` property. |
| 143 | + |
| 144 | +- The returned value contains a list of all the credentials registered on the server, including the new one. |
| 145 | + |
| 146 | + |
| 147 | +### **How to retrieve previously stored credentials for user’s account** |
| 148 | + |
| 149 | +You now have a credential registered on the app and the server. You can now use it to let the user sign in. |
| 150 | + |
| 151 | +1. Initiate a server check: |
| 152 | + |
| 153 | +- Open the file `AuthRepository.kt`. |
| 154 | + |
| 155 | +- Examine the `signinRequest` object. |
| 156 | + |
| 157 | +- Send a request to your server to confirm if Credential Manager APIs can be used for user sign-in. |
| 158 | + |
| 159 | +- Provide the `sessionId` and `credentialId` (which were stored locally) as data for this request. |
| 160 | + |
| 161 | + |
| 162 | +2. Prompt the user for stored credentials: |
| 163 | + |
| 164 | +- If the server request is successful, call the `getPasskey()` function from the `Auth.kt` file to display the user's stored credentials. |
| 165 | + |
| 166 | +3. Configure the retrieval request: |
| 167 | + |
| 168 | +- Create a `GetCredentialRequest()`. |
| 169 | + |
| 170 | +- Provide the previously created registration options to this request. |
| 171 | + |
| 172 | +- For the `isAutoSelectAllowed()` flag: |
| 173 | + |
| 174 | + |
| 175 | +- Set to `true` if you want a single stored credential to be automatically selected. |
| 176 | + |
| 177 | +- Set to `false` to require manual selection. |
| 178 | + |
| 179 | + |
| 180 | +4. Retrieve credentials: |
| 181 | + |
| 182 | +- Use `PublicKeyCredentialOptions` with the `GetCredentialRequest` to retrieve all the user's eligible credentials. |
| 183 | + |
| 184 | +- Ensure that the `requestJson` argument is in a valid WebAuthn JSON format. |
| 185 | + |
| 186 | + |
| 187 | +5. Display the credential selection interface: |
| 188 | + |
| 189 | + |
| 190 | +- Call `CredentialManager.getCredential()` to display a bottom sheet interface. |
| 191 | + |
| 192 | +- This UI displays a list of previously saved credentials. The user can then select a credential and provide consent to proceed with authentication. |
| 193 | + |
| 194 | + |
| 195 | +### **Send a sign-in response and authenticate the user** |
| 196 | + |
| 197 | +This section describes how to send a sign-in response to the server and authenticate your user. |
| 198 | + |
| 199 | +1. Call `signinResponse` from `AuthRespository.kt`. Pass the response and credential to the method as parameters. |
| 200 | + |
| 201 | +2. If successful, the user has been signed in and you can redirect them to the home screen. |
| 202 | + |
| 203 | + |
| 204 | +### **Restore credentials of a returning user on a new device** |
| 205 | + |
| 206 | +This section describes how to implement restore credentials |
| 207 | + |
| 208 | +1. On a successful user authentication, create a Restore Key |
| 209 | + |
| 210 | + 1. Call `AuthRepository`'s `registerPasskeyCreationRequest` method |
| 211 | + |
| 212 | + 2. With the PasskeyCreationRequest recieved from the above method, call `CredentialManagerUtils`'s `createRestoreKey` method |
| 213 | + |
| 214 | + 3. Then call `AuthRepository`'s `registerPasskeyCreationResponse` method |
| 215 | + |
| 216 | + |
| 217 | +2. Once on a new device, check if there is any restore key present on the device or not (brought to the new device in the process of Backup and Restore) |
| 218 | + |
| 219 | + 1. Call `AuthRepository`'s `signInWithPasskeyOrPasswordRequest` method |
| 220 | + |
| 221 | + 2. With the PasskeyCreationRequest recieved from the above method, call `CredentialManagerUtils`'s `getRestoreKey` method |
| 222 | + |
| 223 | + 3. If there is a RestoreKey present this will return a `GenericCredentialManagerResponse.GetCredentialSuccess` else this will return a `GenericCredentialManagerResponse.Error` |
| 224 | + |
| 225 | + |
| 226 | +3. Sign in using the found Restore Key |
| 227 | + |
| 228 | + 1. If a restore key is found in the above step, simply use it to sign-in using `AuthRepository`'s `signInWithPasskeysResponse` method |
| 229 | + |
| 230 | + |
| 231 | +4. Delete a Restore Key |
| 232 | + |
| 233 | + 1. If a user logs out of the app, make sure to clear the stored restore key by calling `CredentialManagerUtils`'s `deleteRestoreKey` |
| 234 | + |
| 235 | + |
| 236 | +## **Specific use case handling** |
| 237 | + |
| 238 | +- When there are no passkeys associated with accounts registered, the developer should catch the exception code and let the user know that first he needs to create a passkey before they try to fetch it. |
| 239 | + |
| 240 | +- For Begin Sign In Failure: 16: Caller has been temporarily blocked due to too many canceled sign-in prompt errors: This is a FIDO-specific error. |
| 241 | + |
| 242 | +- For Begin Sign In Failure: 8: Unknown internal error. If the phone isn't set up properly with a Google account, the passkey JSON is being created incorrectly. |
| 243 | + |
| 244 | +- For `publickeycredential.CreatePublicKeyCredentialDomException`: The incoming request cannot be validated: This means your application package ID is not registered with your server. Validate this with your server code. |
| 245 | + |
| 246 | + |
| 247 | +### **How to build a debug signed version** |
| 248 | + |
| 249 | +To build a debug signed version of this sample app, you need to update the `API_BASE_URL` to `https://credential-manager-app-test.glitch.me/auth` and `host` field to `https://credman-glitch-sample.glitch.me` under `buildConfigField` in your `build.gradle` file. |
| 250 | + |
| 251 | +## **License** |
| 252 | + |
| 253 | +Shrine is distributed under the terms of the Apache License (Version 2.0). See the license for more information. |
| 254 | + |
| 255 | +** |
0 commit comments