Home

Awesome

WebAuthnKit (Android)

This library provides you a way to handle W3C Web Authentication API (a.k.a. WebAuthN / FIDO 2.0) easily.

(You can also get iOS version here https://github.com/lyokato/WebAuthnKit-iOS )

android_webauthnkit

Attention

THIS VERSION IS NOT STABLE YET

This library doens't work as expected on Android5 currently.

Installation

In your application's build.gradle

dependencies {
  implementation 'webauthnkit:webauthnkit-core:0.9.3'
}

pom

<dependency>
  <groupId>webauthnkit</groupId>
  <artifactId>webauthnkit-core</artifactId>
  <version>0.9.3</version>
  <type>pom</type>
</dependency>

Getting Started

AutoBackup setting

Make sure to exclude 'webauthnkit.db'

<application
        android:fullBackupContent="@xml/backup_rules">
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <exclude domain="database" path="webauthnkit.db" />
</full-backup-content>

Or you can set allowBackup="false" simply.

<application
        android:allowBackup="false">

Activity

WebAuthnKit uses Kotlin's experimental features. So, add some annotations on your Activity.

FragmentActivity is required to be bound with WebAuthnKit's UI features. Of cource, androidx.appcompat.app.AppCompatActivity is also OK.

@ExperimentalCoroutinesApi
@ExperimentalUnsignedTypes
class AuthenticationActivity : AppCompatActivity() {
  //...
}

Setup your WebAuthnClient

At first, prepare UserConsentUI on your Activity.

import webauthnkit.core.authenticator.internal.ui.UserConsentUI
import webauthnkit.core.authenticator.internal.ui.UserConsentUIFactory

var consentUI: UserConsentUI? = null

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  consentUI = UserConsentUIFactory.create(this)

  // You can configure consent-ui here
  // consentUI.config.registrationDialogTitle = "New Login Key"
  // consentUI.config.selectionDialogTitle = "Select Key"
  // ...
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  consentUI?.onActivityResult(requestCode, resultCode, data)
}

Then, create WebAuthnClient

import webauthnkit.core.client.WebAuthnClient

val client = WebAuthnClient.create(
  activity = this,
  origin   = "https://example.org"
  ui       = consentUI!!
)
// You can configure client here
// client.maxTimeout = 120
// client.defaultTimeout = 60

Registration Flow

With a flow which is described in following documents, WebAuthnClient creates a credential if it succeeded.


private suspend fun executeRegistration() {

    val options = PublicKeyCredentialCreationOptions()

    options.challenge        = ByteArrayUtil.fromHex(challenge)
    options.user.id          = userId
    options.user.name        = username
    options.user.displayName = userDisplayName
    options.user.icon        = userIconURL
    options.rp.id            = "https://example.org"
    options.rp.name          = "your_service_name"
    options.rp.icon          = yourServiceIconURL
    options.attestation      = attestationConveyance

    options.addPubKeyCredParam(
        alg = COSEAlgorithmIdentifier.es256
    )

    options.authenticatorSelection = AuthenticatorSelectionCriteria(
        requireResidentKey = true,
        userVerification   = userVerification
    )

    try {

        val credential = client.create(options)

        // send parameters to your server
        // credential.id
        // credential.rawId
        // credential.response.attestationObject
        // credential.response.clientDataJSON

    } catch (e: Exception) {
        // error handling
    }

}

If you would like to stop while client is in progress, you can call cancel method.

client.cancel()

webauthnkit.core.CancelledException will be thrown in your suspend function.

Authentication Flow

With a flow which is described in following documents, WebAuthnClient finds credentials, let user to select one (if multiple), and signs the response with it.

private suspend fun executeAuthentication() {

    val options = PublicKeyCredentialRequestOptions()

    options.challenge        = ByteArrayUtil.fromHex(challenge)
    options.rpId             = relyingParty
    options.userVerification = userVerification

    if (credId.isNotEmpty()) {
        options.addAllowCredential(
            credentialId = ByteArrayUtil.fromHex(credId),
            transports   = mutableListOf(AuthenticatorTransport.Internal))
    }

    try {

        val assertion = client.get(options)

        // send parameters to your server
        //assertion.id
        //assertion.rawId
        //assertion.response.authenticatorData
        //assertion.response.signature
        //assertion.response.userHandle
        //assertion.response.clientDataJSON

    } catch (e: Exception) {
        // error handling
    }

}

Features

Not Implemented yet

Key Algorithm Support

Resident Key

InternalAuthenticator forces to use resident-key.

Attestation

Currently, this library supports only self-attestation.

See Also

License

MIT-LICENSE

Author

Lyo Kato <lyo.kato at gmail.com>