Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions identity/credentialmanager/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2025 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
Expand All @@ -19,6 +35,9 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- // [START android_identity_assetlinks_manifest] -->
<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
<!-- // [END android_identity_assetlinks_manifest] -->
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package com.example.identity.credentialmanager

import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import androidx.credentials.GetPasswordOption
import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.PasswordCredential
import androidx.credentials.PublicKeyCredential
import androidx.credentials.exceptions.GetCredentialException
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import org.json.JSONObject

class CredentialManagerFunctions (
context: Context,
) {
// [START android_identity_initialize_credman]
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
// [END android_identity_initialize_credman]

// Placeholder for TAG log value.
val TAG = ""
/**
* Retrieves a passkey from the credential manager.
*
* @param creationResult The result of the passkey creation operation.
* @param context The activity context from the Composable, to be used in Credential Manager APIs
* @return The [GetCredentialResponse] object containing the passkey, or null if an error occurred.
*/
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun signInFlow(
creationResult: JSONObject,
activityContext: Context,
) {
val requestJson = creationResult.toString()
// [START android_identity_get_password_passkey_options]
// Retrieves the user's saved password for your app from their
// password provider.
val getPasswordOption = GetPasswordOption()

// Get passkey from the user's public key credential provider.
val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
requestJson = requestJson
)
// [END android_identity_get_password_passkey_options]
var result: GetCredentialResponse
// [START android_identity_get_credential_request]
val credentialRequest = GetCredentialRequest(
listOf(getPasswordOption, getPublicKeyCredentialOption),
)
// [END android_identity_get_credential_request]
runBlocking {
// getPrepareCredential request
// [START android_identity_prepare_get_credential]
coroutineScope {
val response = credentialManager.prepareGetCredential(
GetCredentialRequest(
listOf(
getPublicKeyCredentialOption,
getPasswordOption
)
)
)
}
// [END android_identity_prepare_get_credential]
// getCredential request without handling exception.
// [START android_identity_launch_sign_in_flow_1]
coroutineScope {
try {
result = credentialManager.getCredential(
// Use an activity-based context to avoid undefined system UI
// launching behavior.
context = activityContext,
request = credentialRequest
)
handleSignIn(result)
} catch (e: GetCredentialException) {
// Handle failure
}
}
// [END android_identity_launch_sign_in_flow_1]
// getCredential request adding some exception handling.
// [START android_identity_handle_exceptions_no_credential]
coroutineScope {
try {
result = credentialManager.getCredential(
context = activityContext,
request = credentialRequest
)
} catch (e: GetCredentialException) {
Log.e("CredentialManager", "No credential available", e)
}
}
// [END android_identity_handle_exceptions_no_credential]
}
}

// [START android_identity_launch_sign_in_flow_2]
fun handleSignIn(result: GetCredentialResponse) {
// Handle the successfully returned credential.
val credential = result.credential

when (credential) {
is PublicKeyCredential -> {
val responseJson = credential.authenticationResponseJson
// Share responseJson i.e. a GetCredentialResponse on your server to
// validate and authenticate
}

is PasswordCredential -> {
val username = credential.id
val password = credential.password
// Use id and password to send to your server to validate
// and authenticate
}

is CustomCredential -> {
// If you are also using any external sign-in libraries, parse them
// here with the utility functions provided.
if (credential.type == ExampleCustomCredential.TYPE) {
try {
val ExampleCustomCredential =
ExampleCustomCredential.createFrom(credential.data)
// Extract the required credentials and complete the authentication as per
// the federated sign in or any external sign in library flow
} catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
// Unlikely to happen. If it does, you likely need to update the dependency
// version of your external sign-in library.
Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
}
} else {
// Catch any unrecognized custom credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
}
// [END android_identity_launch_sign_in_flow_2]
}

sealed class ExampleCustomCredential {
class ExampleCustomCredentialParsingException : Throwable() {}

companion object {
fun createFrom(data: Bundle): PublicKeyCredential {
return PublicKeyCredential("")
}

const val TYPE: String = ""
}
}
66 changes: 66 additions & 0 deletions identity/credentialmanager/src/main/jsonSnippets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

{
"snippets": [
{
"DigitalAssetLinking":
// Digital asset linking
// [START android_identity_assetlinks_json]
[
{
"relation": [
"delegate_permission/common.handle_all_urls",
"delegate_permission/common.get_login_creds"
],
"target": {
"namespace": "android_app",
"package_name": "com.example.android",
"sha256_cert_fingerprints": [
SHA_HEX_VALUE
]
}
}
]
// [END android_identity_assetlinks_json]
},

// JSON request and response formats
// [START android_identity_format_json_request_passkey]
{
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
"allowCredentials": [],
"timeout": 1800000,
"userVerification": "required",
"rpId": "credential-manager-app-test.glitch.me"
},
// [END android_identity_format_json_request_passkey]

// [START android_identity_format_json_response_passkey]
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
}
}
// [END android_identity_format_json_response_passkey]
]
}
28 changes: 28 additions & 0 deletions identity/credentialmanager/src/main/res/layout-v34/xmlsnippets.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2025 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- // [START android_identity_textview_isCredential] -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:isCredential="true" />
<!-- // [END android_identity_textview_isCredential] -->

</LinearLayout>
7 changes: 7 additions & 0 deletions identity/credentialmanager/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
<resources>
<string name="app_name">credentialmanager</string>
// [START android_identity_assetlinks_app_association]
<string name="asset_statements" translatable="false">
[{
\"include\": \"https://signin.example.com/.well-known/assetlinks.json\"
}]
</string>
// [END android_identity_assetlinks_app_association]
</resources>