Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add Android 14 service guide #39

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
39 changes: 20 additions & 19 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@ import starlight from "@astrojs/starlight";
import rehypeMermaid from "rehype-mermaid";
import tailwind from "@astrojs/tailwind";
import starlightOpenAPI, { openAPISidebarGroups } from "starlight-openapi";
import path from 'node:path';
import { fileURLToPath } from 'node:url';
// import starlightTypeDoc, { typeDocSidebarGroup } from "starlight-typedoc";
import react from "@astrojs/react";

import mdx from "@astrojs/mdx";
import basicSsl from '@vitejs/plugin-basic-ssl'
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// https://astro.build/config
export default defineConfig({
vite: {
resolve: {
alias: {
'@/lib': path.resolve(__dirname, './src/lib'),
'@/assets': path.resolve(__dirname, './src/assets'),
'@/components': path.resolve(__dirname, './src/components'),
},
},
plugins: [basicSsl()],
server: {
https: true,
Expand Down Expand Up @@ -120,33 +130,24 @@ export default defineConfig({
},
{
label: "Provider Service",
collapsed: true,
badge: {
text: "^14",
variant: "danger"
},
items: [
{
label: "Introduction",
link: "/clients/android/provider-service/introduction",
},
{
label: "Create Passkey",
link: "/clients/android/provider-service/create-passkey",
badge: {
text: "TODO",
variant: "danger"
},
label: "Registration",
link: "/clients/android/provider-service/registration",
},
{
label: "Get Passkey",
link: "/clients/android/provider-service/get-passkey",
badge: {
text: "TODO",
variant: "danger"
},
label: "Authentication",
link: "/clients/android/provider-service/authentication",
}
],
badge: {
text: "^14",
variant: "danger"
},
]
},
{
label: "Reference",
Expand Down
15 changes: 15 additions & 0 deletions docs/src/components/CodeUri.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
import { Code } from '@astrojs/starlight/components';
import { fetchAndExtract } from '@/lib/fetchAndExtract.js';

const { uri, lines, lang, title, mark, frame } = Astro.props;

// 'lines' can optionally be specified in the following formats:
// - '1-3' (inclusive range)
// - '5' (single line)
// - '1-10,15-20' (multiple ranges)
// - '1-3,5,7-9' (mixed)
const selectedCode = await fetchAndExtract(uri, lines);
---

<Code code={selectedCode} lang={lang} title={title} frame={frame} mark={mark} />

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
title: "Android 14: Authentication Service"
---

import {Steps, TabItem, Tabs} from '@astrojs/starlight/components';
import CodeUri from "@/components/CodeUri.astro";

To handle sign in requests in your credential provider service, complete the steps shown in the following sections.

## Query

User sign-in is handled with the following steps:

<Steps>
1. When a device tries to sign in a user, it prepares a [GetCredentialRequest](https://developer.android.com/reference/androidx/credentials/GetCredentialRequest) instance.
2. The Android framework propagates this request to all applicable credential providers by binding to these services.
3. The provider service then receives a `BeginGetCredentialRequest` that contains a list of `BeginGetCredentialOption`,
each of which contains parameters that can be used to retrieve matching credentials.
</Steps>

To handle this request in your credential provider service,
you can override the `onBeginGetCredentialRequest()` method as shown in the following example:

```kotlin
package foundation.algorand.demo

class CustomProviderService: CredentialProviderService() {
companion object {
const val GET_PASSKEY_INTENT = 1
const val GET_PASSKEY_ACTION = "foundation.algorand.demo.GET_PASSKEY"
}

/**
* Handle Get Credential Requests
*/
@RequiresApi(Build.VERSION_CODES.S)
override fun onBeginGetCredentialRequest(
request: BeginGetCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,
) {
try {
callback.onResult(processGetCredentialRequest(request))
} catch (e: GetCredentialException) {
callback.onError(GetCredentialUnknownException())
}
}

/**
* Get a list of available PublicKeyCredential Entries
*/
private fun processGetCredentialRequest(request: BeginGetCredentialRequest): BeginGetCredentialResponse{
Log.v(TAG, "processing GetCredentialRequest")
val deferredCredentials: Deferred<List<Credential>> = scope.async {
credentialRepository.getDatabase(this@LiquidCredentialProviderService).credentialDao().getAllRegular()
}
val credentials = runBlocking {
deferredCredentials.await()
}
return BeginGetCredentialResponse(credentials.map {
val data = Bundle()
data.putString("credentialId", it.credentialId)
data.putString("userHandle", it.userHandle)
PublicKeyCredentialEntry.Builder(
this@LiquidCredentialProviderService,
it.userHandle,
createNewPendingIntent(GET_PASSKEY_ACTION, GET_PASSKEY_INTENT, data),
request.beginGetCredentialOptions[0] as BeginGetPublicKeyCredentialOption
)
.setIcon(Icon.createWithResource(this@LiquidCredentialProviderService, R.mipmap.ic_launcher))
.build()
})
}

/**
* This method creates a new PendingIntent for the given action and request code.
*/
private fun createNewPendingIntent(action: String, requestCode: Int, extra: Bundle?): PendingIntent {
val intent = Intent(action).setPackage(PACKAGE_NAME)

if (extra != null) {
intent.putExtra("CREDENTIAL_DATA", extra)
}
return PendingIntent.getActivity(
applicationContext, requestCode,
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
)
}
}
```

## Selection

Once you query and populate the credentials,
now you need to handle the selection phase for the credentials being selected by the user.

<Steps>
1. In the `onCreate` method of the corresponding Activity, retrieve the associated intent, and pass to [PendingIntentHandler.retrieveProviderGetCredentialRequest()](https://developer.android.com/reference/androidx/credentials/provider/ProviderGetCredentialRequest).
2. Extract the [GetPublicKeyCredentialOption](https://developer.android.com/reference/androidx/credentials/GetPublicKeyCredentialOption) from the request retrieved above. Subsequently, extract the `requestJson` and `clientDataHash` from this option.
3. Extract the `credentialId` from the intent extra, which was populated by the credential provider when the corresponding `PendingIntent` was set up.
4. Extract the passkey from your local database using the request parameters accessed above.
5. Assert that the passkey is valid with extracted metadata, and user verification.
6. Construct a JSON response based on the W3 Web Authentication Assertion spec.
7. Construct a PublicKeyCredential using the JSON generated above and set it on a final GetCredentialResponse.
</Steps>

The following example illustrates how these steps can be implemented:


<Tabs>
<TabItem label="GetPasskeyActivity.kt">
<CodeUri lang="kotlin" uri="https://raw.githubusercontent.com/algorandfoundation/liquid-auth-android/develop/demo/src/main/java/foundation/algorand/demo/headless/GetPasskeyActivity.kt"/>
</TabItem>
<TabItem label="GetPasskeyViewModel.kt">
<CodeUri lines="1-68,71-101,104-142" lang="kotlin" uri="https://raw.githubusercontent.com/algorandfoundation/liquid-auth-android/develop/demo/src/main/java/foundation/algorand/demo/headless/GetPasskeyViewModel.kt"/>
</TabItem>
</Tabs>
Loading
Loading