Ihre erste Android FIDO2 API

1. Einführung

Was ist die FIDO2 API?

Mit der FIDO2 API können Android-Anwendungen zur Authentifizierung von Nutzern starke, attestierte, auf öffentliche Schlüssel basierende Anmeldedaten erstellen und verwenden. Die API bietet eine Implementierung des WebAuthn-Clients, die die Verwendung von BLE-, NFC- und USB-Roaming-Authentifikatoren (Sicherheitsschlüsseln) sowie eines Plattformauthentifizierungsgeräts unterstützt, mit dem sich der Nutzer mit seinem Fingerabdruck oder seiner Displaysperre authentifizieren kann.

Das werden Sie erstellen...

In diesem Codelab entwickeln Sie eine Android-App mit einer einfachen Funktion zur erneuten Authentifizierung mithilfe des Fingerabdrucksensors. „Erneute Authentifizierung“ Ein Nutzer meldet sich in einer App an und authentifiziert sich noch einmal, wenn er zu deiner App zurückkehrt oder auf einen wichtigen Bereich deiner App zugreift. Der letzte Fall wird auch als „Step-up-Authentifizierung“ bezeichnet.

Lerninhalte

Sie erfahren, wie Sie die Android FIDO2 API und Optionen aufrufen, die Sie für verschiedene Anlässe bereitstellen können. Außerdem lernen Sie Best Practices für die erneute Autorisierung kennen.

Das brauchen Sie...

  • Android-Gerät mit Fingerabdrucksensor (auch ohne Fingerabdrucksensor bietet die Displaysperre eine gleichwertige Funktion zur Nutzerbestätigung)
  • Android 7.0 oder höher mit den neuesten Updates Registrieren Sie einen Fingerabdruck (oder eine Displaysperre).

2. Einrichtung

Repository klonen

GitHub-Repository ansehen

https://github.com/android/codelab-fido2

$ git clone https://github.com/android/codelab-fido2.git

Was werden wir implementieren?

  • Nutzer dürfen einen „Plattform-Authenticator zur Nutzerbestätigung“ registrieren Das Android-Smartphone mit Fingerabdrucksensor fungiert dann als solches.
  • Nutzer dürfen sich mit ihrem Fingerabdruck noch einmal in der App authentifizieren.

Hier können Sie sich eine Vorschau ansehen.

Codelab-Projekt starten

Die fertige Anwendung sendet Anfragen an einen Server unter https://webauthn-codelab.glitch.me. Sie können dort die Webversion derselben App ausprobieren.

c2234c42ba8a6ef1.png

Sie arbeiten an Ihrer eigenen Version der App.

  1. Rufen Sie unter https://glitch.com/edit/#!/webauthn-codelab die Bearbeitungsseite der Website auf.
  2. Suche nach „Remix to Edit“ (Remix zum Bearbeiten) oben rechts. Durch Drücken der Taste kannst du „verzweigen“ und fahren Sie mit Ihrer eigenen Version zusammen mit einer neuen Projekt-URL fort. 9ef108869885e4ce.png
  3. Kopieren Sie den Projektnamen oben links. Sie können ihn beliebig ändern. c91d0d59c61021a4.png
  4. Füge ihn in den Abschnitt HOSTNAME der Datei .env im Glitch ein. 889b55b1cf74b894.png

3. Ihre App und eine Website mit Digital Asset Links verknüpfen

Wenn Sie die FIDO2 API in einer Android-App verwenden möchten, verknüpfen Sie sie mit einer Website und teilen Sie die Anmeldedaten zwischen ihnen. Verwende dazu Digital Asset Links. Sie können Verknüpfungen deklarieren, indem Sie eine Digital Asset Links-JSON-Datei auf Ihrer Website hosten und dem Manifest Ihrer App einen Link zur Digital Asset Link-Datei hinzufügen.

.well-known/assetlinks.json in Ihrer Domain hosten

Sie können eine Verknüpfung zwischen Ihrer App und der Website definieren, indem Sie eine JSON-Datei erstellen und unter .well-known/assetlinks.json ablegen. Zum Glück gibt es einen Servercode, mit dem die assetlinks.json-Datei automatisch angezeigt wird. Dazu müssen Sie lediglich die folgenden Umgebungsparameter in die .env-Datei für eine Störung einfügen:

  • ANDROID_PACKAGENAME: Paketname Ihrer App (com.beispiel.android.fido2)
  • ANDROID_SHA256HASH: SHA256-Hash Ihres Signaturzertifikats

Verwenden Sie den folgenden Befehl, um den SHA256-Hash Ihres Signaturzertifikats für Entwickler abzurufen. Das Standardpasswort des Keystores für die Fehlerbehebung lautet „android“.

$ keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore

Wenn Sie auf https://<your-project-name>.glitch.me/.well-known/assetlinks.json zugreifen , sollte ein JSON-String wie der folgende angezeigt werden:

[{
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "web",
    "site": "https://<your-project-name>.glitch.me"
  }
}, {
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.android.fido2",
    "sha256_cert_fingerprints": ["DE:AD:BE:EF:..."]
  }
}]

Projekt in Android Studio öffnen

Klicken Sie auf "Vorhandenes Android Studio-Projekt öffnen". auf dem Begrüßungsbildschirm von Android Studio.

Wählen Sie die Option „Android“ aus. Ordner im Repository auszuchecken.

1062875cf11ffb95.png

App mit deinem Remix verknüpfen

Datei „gradle.properties“ öffnen. Ändere unten in der Datei die Host-URL in den Glitch-Remix, den du gerade erstellt hast.

// ...

# The URL of the server
host=https://<your-project-name>.glitch.me

Jetzt sollte deine Digital Asset Links-Konfiguration abgeschlossen sein.

4. So funktioniert die App jetzt

Sehen wir uns zunächst an, wie die App jetzt funktioniert. Achten Sie darauf, im Kombinationsfeld für die Ausführungskonfiguration app-start auszuwählen. Klicken Sie auf „Ausführen“. (das grüne Dreieck neben dem Kombinationsfeld) aus, um die App auf Ihrem verbundenen Android-Gerät zu starten.

29351fb97062b43c.png

Wenn du die App startest, erscheint der Bildschirm, auf dem du deinen Nutzernamen eingeben kannst. Das ist UsernameFragment. Zu Demonstrationszwecken akzeptieren die App und der Server einen beliebigen Nutzernamen. Geben Sie einfach etwas ein und klicken Sie auf „Weiter“.

bd9007614a9a3644.png

Der nächste Bildschirm, den du siehst, ist AuthFragment. Dort kann sich der Nutzer mit einem Passwort anmelden. Später werden wir hier eine Funktion zur Anmeldung mit FIDO2 hinzufügen. Zu Demonstrationszwecken akzeptieren die App und der Server ein beliebiges Passwort. Gib einfach einen Text ein und klicke auf „Anmelden“.

d9caba817a0a99bd.png

Dies ist der letzte Bildschirm dieser App, HomeFragment. Aktuell sehen Sie hier nur eine leere Liste mit Anmeldedaten. Drücken Sie auf „Erneute Authentifizierung“. führt Sie zurück zu AuthFragment. Tippe auf „Abmelden“. führt Sie zurück zu UsernameFragment. Die unverankerte Aktionsschaltfläche mit „+“ ändert sich zwar noch nichts, aber es wird die Registrierung eines

sobald Sie den FIDO2-Registrierungsvorgang implementiert haben.

1cfcc6c884020e37.png

Bevor Sie mit dem Programmieren beginnen, sollten Sie Folgendes beachten: Drücke in Android Studio auf "TODO". . Daraufhin wird eine Liste aller TODOs in diesem Codelab angezeigt. Wir beginnen mit dem ersten TODO im nächsten Abschnitt.

e5a811bbc7cd7b30.png

5. Anmeldedaten mit einem Fingerabdruck registrieren

Um die Authentifizierung mithilfe eines Fingerabdrucks zu aktivieren, müssen Sie zuerst einen Berechtigungsnachweis registrieren, der von einem Nutzer generiert wurde, der die Authentifizierung der Plattform bestätigt. Dabei handelt es sich um einen in das Gerät eingebetteten Authenticator, der den Nutzer mithilfe biometrischer Verfahren, z. B. eines Fingerabdrucksensors, verifiziert.

37ce78fdf2759832.png

Wie im vorherigen Abschnitt gesehen, hat die unverankerte Aktionsschaltfläche jetzt keine Funktion. Sehen wir uns an, wie wir einen neuen Berechtigungsnachweis registrieren können.

Server-API aufrufen: /auth/registerRequest

Öffnen Sie AuthRepository.kt und suchen Sie nach TODO(1).

Hier ist registerRequest die Methode, die aufgerufen wird, wenn die UAS gedrückt wird. Diese Methode soll die Server-API /auth/registerRequest aufrufen. Die API gibt ein ApiResult mit allen PublicKeyCredentialCreationOptions zurück, die der Client zum Generieren neuer Anmeldedaten benötigt.

Wir können dann getRegisterPendingIntent mit den Optionen aufrufen. Diese FIDO2-API gibt einen Android PendingIntent zurück, um einen Fingerabdruckdialog zu öffnen und neue Anmeldedaten zu generieren. Dieser PendingIntent kann an den Aufrufer zurückgegeben werden.

Die Methode sieht dann so aus:

suspend fun registerRequest(): PendingIntent? {
  fido2ApiClient?.let { client ->
    try {
      val sessionId = dataStore.read(SESSION_ID)!!
      when (val apiResult = api.registerRequest(sessionId)) {
        ApiResult.SignedOutFromServer -> forceSignOut()
        is ApiResult.Success -> {
          if (apiResult.sessionId != null) {
            dataStore.edit { prefs ->
              prefs[SESSION_ID] = apiResult.sessionId
            }
          }
          val task = client.getRegisterPendingIntent(apiResult.data)
          return task.await()
        }
      }
    } catch (e: Exception) {
      Log.e(TAG, "Cannot call registerRequest", e)
    }
  }
  return null
}

Fingerabdruck-Dialogfeld für die Registrierung öffnen

Öffnen Sie HomeFragment.kt und suchen Sie nach TODO(2).

Hier wird der Intent von der AuthRepository über die UI zurückgegeben. Hier verwenden wir das Mitglied createCredentialIntentLauncher, um den PendingIntent zu starten, den wir als Ergebnis des vorherigen Schritts erhalten haben. Dadurch wird ein Dialogfeld zum Erstellen von Anmeldedaten geöffnet.

binding.add.setOnClickListener {
  lifecycleScope.launch {
    val intent = viewModel.registerRequest()
    if (intent != null) {
      createCredentialIntentLauncher.launch(
        IntentSenderRequest.Builder(intent).build()
      )
    }
  }
}

ActivityResult mit den neuen Anmeldedaten erhalten

Öffnen Sie HomeFragment.kt und suchen Sie nach TODO(3).

Diese handleCreateCredentialResult-Methode wird aufgerufen, nachdem das Fingerabdruck-Dialogfeld geschlossen wurde. Wenn ein Berechtigungsnachweis erfolgreich generiert wurde, enthält das Mitglied data von ActivityResult diese Informationen.

Zuerst müssen wir PublicKeyCredential aus data extrahieren. Der Daten-Intent hat ein zusätzliches Feld aus Byte-Array mit dem Schlüssel Fido.FIDO2_KEY_CREDENTIAL_EXTRA. Sie können die statische Methode deserializeFromBytes in PublicKeyCredential verwenden, um das Byte-Array in ein PublicKeyCredential-Objekt umzuwandeln.

Prüfen Sie als Nächstes, ob das response-Mitglied dieses Anmeldedatenobjekts ein AuthenticationErrorResponse-Element ist. Wenn dies der Fall ist, ist beim Generieren des Berechtigungsnachweises ein Fehler aufgetreten. Andernfalls können wir Anmeldedaten an unser Back-End senden.

Die fertige Methode sieht so aus:

private fun handleCreateCredentialResult(activityResult: ActivityResult) {
  val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
  when {
    activityResult.resultCode != Activity.RESULT_OK ->
      Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_LONG).show()
    bytes == null ->
      Toast.makeText(requireContext(), R.string.credential_error, Toast.LENGTH_LONG)
        .show()
    else -> {
      val credential = PublicKeyCredential.deserializeFromBytes(bytes)
      val response = credential.response
      if (response is AuthenticatorErrorResponse) {
        Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_LONG)
          .show()
      } else {
        viewModel.registerResponse(credential)
      }
    }
  }
}

Server-API aufrufen: /auth/registerResponse

Öffnen Sie AuthRepository.kt und suchen Sie nach TODO(4).

Die Methode registerResponse wird aufgerufen, nachdem die Benutzeroberfläche erfolgreich neue Anmeldedaten generiert hat und diese an den Server zurückgesendet werden sollen.

Das Objekt PublicKeyCredential enthält Informationen zu den neu generierten Anmeldedaten. Wir möchten uns nun die ID unseres lokalen Schlüssels merken, damit wir ihn von anderen Schlüsseln unterscheiden können, die auf dem Server registriert sind. Fügen Sie im PublicKeyCredential-Objekt das Attribut rawId mit toBase64 in eine lokale Stringvariable ein.

Jetzt können die Informationen an den Server gesendet werden. Verwenden Sie api.registerResponse, um die Server-API aufzurufen und die Antwort zurückzusenden. Der zurückgegebene Wert enthält eine Liste aller auf dem Server registrierten Anmeldedaten, einschließlich des neuen.

Schließlich können wir die Ergebnisse in DataStore speichern. Die Liste der Anmeldedaten sollte mit dem Schlüssel CREDENTIALS als StringSet gespeichert werden. Sie können toStringSet verwenden, um die Liste der Anmeldedaten in eine StringSet umzuwandeln.

Außerdem speichern wir die Anmeldedaten-ID mit dem Schlüssel LOCAL_CREDENTIAL_ID.

suspend fun registerResponse(credential: PublicKeyCredential) {
  try {
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = credential.rawId.toBase64()
    when (val result = api.registerResponse(sessionId, credential)) {
      ApiResult.SignedOutFromServer -> forceSignOut()
      is ApiResult.Success -> {
        dataStore.edit { prefs ->
          result.sessionId?.let { prefs[SESSION_ID] = it }
          prefs[CREDENTIALS] = result.data.toStringSet()
          prefs[LOCAL_CREDENTIAL_ID] = credentialId
        }
      }
    }
  } catch (e: ApiException) {
    Log.e(TAG, "Cannot call registerResponse", e)
  }
}

Führen Sie die App aus. Sie können dann auf die UAS klicken und einen neuen Berechtigungsnachweis registrieren.

7d64d9289c5a3cbc.png

6. Nutzer mit einem Fingerabdruck authentifizieren

Jetzt sind in der App und auf dem Server Anmeldedaten registriert. Jetzt können wir es verwenden, damit sich der Nutzer anmelden kann. Wir fügen AuthFragment eine Anmeldefunktion mit Fingerabdruck hinzu. Sobald ein Nutzer darauf landet, wird ein Fingerabdruck-Dialogfeld angezeigt. Wenn die Authentifizierung erfolgreich ist, wird der Nutzer zu HomeFragment weitergeleitet.

Server-API aufrufen: /auth/signinRequest

Öffnen Sie AuthRepository.kt und suchen Sie nach TODO(5).

Diese signinRequest-Methode wird beim Öffnen von AuthFragment aufgerufen. Hier möchten wir den Server anfordern und prüfen, ob sich der Nutzer mit FIDO2 anmelden kann.

Zuerst müssen wir PublicKeyCredentialRequestOptions vom Server abrufen. Verwenden Sie api.signInRequest, um die Server-API aufzurufen. Die zurückgegebene ApiResult enthält PublicKeyCredentialRequestOptions.

Mit der PublicKeyCredentialRequestOptions können wir die getSignIntent der FIDO2 API verwenden, um einen PendingIntent zu erstellen, mit dem das Fingerabdruck-Dialogfeld geöffnet wird.

Schließlich können wir den PendingIntent wieder an die Benutzeroberfläche zurückgeben.

suspend fun signinRequest(): PendingIntent? {
  fido2ApiClient?.let { client ->
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = dataStore.read(LOCAL_CREDENTIAL_ID)
    if (credentialId != null) {
      when (val apiResult = api.signinRequest(sessionId, credentialId)) {
        ApiResult.SignedOutFromServer -> forceSignOut()
        is ApiResult.Success -> {
          val task = client.getSignPendingIntent(apiResult.data)
          return task.await()
        }
      }
    }
  }
  return null
}

Fingerabdruck-Dialogfeld zur Assertion öffnen

Öffnen Sie AuthFragment.kt und suchen Sie nach TODO(6).

Das funktioniert im Grunde genauso wie bei der Registrierung. Wir können das Fingerabdruck-Dialogfeld mit dem signIntentLauncher-Mitglied öffnen.

viewLifecycleOwner.lifecycleScope.launchWhenStarted {
  launch {
    viewModel.signinRequests.collect { intent ->
      signIntentLauncher.launch(
        IntentSenderRequest.Builder(intent).build()
      )
    }
  }
  launch {
    ...
  }
}

ActivityResult verarbeiten

Öffnen Sie AuthFragment.kt und suchen Sie nach TODO(7).

Auch dies ist dasselbe wie bei der Registrierung. Wir können die PublicKeyCredential extrahieren, nach einem Fehler suchen und sie an ViewModel übergeben.

private fun handleSignResult(activityResult: ActivityResult) {
  val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
  when {
    activityResult.resultCode != Activity.RESULT_OK ->
      Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_SHORT).show()
    bytes == null ->
      Toast.makeText(requireContext(), R.string.auth_error, Toast.LENGTH_SHORT)
        .show()
    else -> {
      val credential = PublicKeyCredential.deserializeFromBytes(bytes)
      val response = credential.response
      if (response is AuthenticatorErrorResponse) {
        Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_SHORT)
          .show()
      } else {
        viewModel.signinResponse(credential)
      }
    }
  }
}

Server-API aufrufen: /auth/signinResponse

Öffnen Sie AuthRepository.kt und suchen Sie nach TODO(8).

Das Objekt „PublicKeyCredential“ enthält die Ausweisdokument-ID keyHandle. Genau wie beim Registrierungsvorgang speichern wir dies in einer lokalen Zeichenfolgenvariablen, damit wir es später speichern können.

Jetzt kann die Server-API mit api.signinResponse aufgerufen werden. Der zurückgegebene Wert enthält eine Liste von Anmeldedaten.

Zu diesem Zeitpunkt ist die Anmeldung erfolgreich. Wir müssen alle Ergebnisse in DataStore speichern. Die Liste der Anmeldedaten sollte als StringSet mit dem Schlüssel CREDENTIALS gespeichert werden. Die oben gespeicherte lokale Ausweisdokument-ID sollte als String mit dem Schlüssel LOCAL_CREDENTIAL_ID gespeichert werden.

Schließlich müssen wir den Anmeldestatus aktualisieren, damit die Benutzeroberfläche den Nutzer zum HomeFragment weiterleiten kann. Dazu wird ein SignInState.SignedIn-Objekt mit dem Namen signInStateMutable an den SharedFlow ausgegeben. Außerdem möchten wir refreshCredentials aufrufen, um die Anmeldedaten des Nutzers abzurufen, damit sie in der UI aufgelistet werden.

suspend fun signinResponse(credential: PublicKeyCredential) {
  try {
    val username = dataStore.read(USERNAME)!!
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = credential.rawId.toBase64()
    when (val result = api.signinResponse(sessionId, credential)) {
      ApiResult.SignedOutFromServer -> forceSignOut()
      is ApiResult.Success -> {
        dataStore.edit { prefs ->
          result.sessionId?.let { prefs[SESSION_ID] = it }
          prefs[CREDENTIALS] = result.data.toStringSet()
          prefs[LOCAL_CREDENTIAL_ID] = credentialId
        }
        signInStateMutable.emit(SignInState.SignedIn(username))
        refreshCredentials()
      }
    }
  } catch (e: ApiException) {
    Log.e(TAG, "Cannot call registerResponse", e)
  }
}

Führen Sie die App aus und klicken Sie auf „Erneute Autorisierung“. um AuthFragment zu öffnen. Es sollte nun ein Fingerabdruck-Dialogfeld angezeigt werden, in dem Sie aufgefordert werden, sich mit Ihrem Fingerabdruck anzumelden.

45f81419f84952c8.png

Glückwunsch! Sie wissen jetzt, wie Sie die FIDO2 API unter Android für die Registrierung und Anmeldung verwenden.

7. Glückwunsch!

Sie haben das Codelab Ihre erste Android FIDO2 API erfolgreich abgeschlossen.

Das haben Sie gelernt

  • So registrieren Sie Anmeldedaten mit einem Plattform-Authenticator, der den Nutzer bestätigt.
  • Nutzer mit einem registrierten Authenticator authentifizieren
  • Verfügbare Optionen zum Registrieren einer neuen Authentifizierungs-App.
  • UX-Best Practices für die erneute Authentifizierung mit einem biometrischen Sensor.

Nächster Schritt

  • Hier erfahren Sie, wie Sie auf einer Website ein ähnliches Erlebnis schaffen.

Probieren Sie das Codelab Your first WebAuthn (Ihre erste WebAuthn-Version) aus.

Ressourcen

Vielen Dank an Yuriy Ackermann von der FIDO Alliance für die Hilfe.