flutter-institute / webauthn

A plugin to handle webauthn login
BSD 3-Clause "New" or "Revised" License
15 stars 7 forks source link

How do I implement passkey with webauthn #16

Closed varunlohade closed 1 month ago

varunlohade commented 8 months ago

I tried using this package, but it just shows the biometric(face id or fingerprint), I want to implement passkey in my application, Any way I can do it with webauthn package

killermonk commented 8 months ago

Please take a look at the example app for how to integrate the authenticator in your app: https://github.com/flutter-institute/webauthn/blob/master/example/lib/main.dart

This is designed to work as the authenticator portion of Webauthn, so it's expecting to receive information from a Relying Party (your back-end app). That can then be passed through the WebApi helper methods to interact with the authenticator.

majdslmt commented 5 months ago

Hi!

How can I obtain the public key when creating the credential? I have tried the following code:

var attestationBytes = attestation.asCBOR(); var attestationJson = attestation.asJSON(); However, I only received authData without the key.

killermonk commented 5 months ago

@majdslmt For the currently used key type (ES256), the public key bytes are the last 77 bytes of the attestation's authData. So you can retrieve it by using attestation.authData.sublist(attestation.authData.length - 77)

See https://github.com/flutter-institute/webauthn/blob/master/lib/src/models/attestation.dart#L32 where the structure of authData is documented.

However, as the authenticator you shouldn't be worrying about extracting the public key from the attestation. That's something the Relying Party will do when they validate the response. What's your use case here?

majdslmt commented 5 months ago

I want to save the public key in the backend, but I can't extract the public key from authData. Also, when using this code, attestation.authData.sublist(attestation.authData.length - 77) didn't work to get the public key as string data. @killermonk

killermonk commented 5 months ago

@majdslmt that's because authData is a Uint8List, so the sublist is the list of bytes that make up the public key data. If you want to save that data as a string, you'll need to do some encoding on it. Which format are you trying to store it in?

killermonk commented 5 months ago

To be much more verbose.

The public key that is stored in the authData is encoded in COSE_Key format per https://www.w3.org/TR/webauthn-2/#sctn-attested-credential-data and https://datatracker.ietf.org/doc/html/rfc8152#section-7

The byte data you get from the authData is that encoded format, so you can parse it an re-encode it however you want. I had a script that encoded this into an RSA style format. I'll see if I can find it today.

You can see where it is encoded here: https://github.com/flutter-institute/webauthn/blob/master/lib/src/util/credential_safe.dart#L147 and you'll be able to use the cbor library to decode it so you don't have to.

majdslmt commented 5 months ago

I want to get the response as this payload to be able to use @simplewebauthn/server in server side {"id":"ASBmohREs1OhTB6s_EJY-hIwyiQuGbklL-mXK-tmVnmR5N4rlg_TRlrOf-olrhRQlasoGZOabDM4oppcnj0fn0Y","user":"test","rawId":"ASBmohREs1OhTB6s_EJY-hIwyiQuGbklL-mXK-tmVnmR5N4rlg_TRlrOf-olrhRQlasoGZOabDM4oppcnj0fn0Y","response":{"attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQEgZqIURLNToUwerPxCWPoSMMokLhm5JS_plyvrZlZ5keTeK5YP00Zazn_qJa4UUJWrKBmTmmwzOKKaXJ49H59GpQECAyYgASFYIEjOcsLFskQtV98RJaSq5QzeJ6s-JqDUZLCpGP495Ew1Ilgg07dunytQNNIHKKF5q-OwC1lbMC2b5JdBdtwlK9InYx0","clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiakg3MjZvV1gtdkFzLWIyUm9XZE9SZlZyQWR1aEZFWDNfbjhUeTdOUlpzZyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0","transports":["hybrid","internal"],"publicKeyAlgorithm":-7,"publicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESM5ywsWyRC1X3xElpKrlDN4nqz4moNRksKkY_j3kTDXTt26fK1A00gcooXmr47ALWVswLZvkl0F23CUr0idjHQ","authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQEgZqIURLNToUwerPxCWPoSMMokLhm5JS_plyvrZlZ5keTeK5YP00Zazn_qJa4UUJWrKBmTmmwzOKKaXJ49H59GpQECAyYgASFYIEjOcsLFskQtV98RJaSq5QzeJ6s-JqDUZLCpGP495Ew1Ilgg07dunytQNNIHKKF5q-OwC1lbMC2b5JdBdtwlK9InYx0"},"type":"public-key","clientExtensionResults":{"credProps":{"rk":false}},"authenticatorAttachment":"cross-platform"} @killermonk

killermonk commented 5 months ago

You'll want to check out WebAPI.createAttestationResponse here https://github.com/flutter-institute/webauthn/blob/master/lib/src/web_api.dart#L132

That will return you most of what you are looking for. The server should theoretically be able to handle this response. The WebAPI stuff are helpers I quickly threw in to test against one use case, though, so they might be incomplete.

I was looking over https://www.w3.org/TR/webauthn-2/#authenticatorattestationresponse and the response object the WebAPI helper creates has all the "attributes" available, but does not have any of the "operations" implemented.

Also, I haven't built in support for extensions, yet.

majdslmt commented 5 months ago

@killermonk thanks! now I am trying the WebApi but still getting this error ""type 'Null' is not a subtype of type 'Map<dynamic, dynamic>' in type cast" " when create the CreateCredentialOptions with fake payload to test this { "authenticatorExtensions": "", "clientDataHash": "LTCT/hWLtJenIgi0oUhkJz7dE8ng+pej+i6YI1QQu60=", "credTypesAndPubKeyAlgs": [ ["public-key", -7] ], "excludeCredentials": [{ "type": "public-key", "id": "lVGyXHwz6vdYignKyctbkIkJto/ADbYbHhE7+ss/87o=" }], "pubKeyCredParams": [ { "alg": -8, "type": "public-key" }, { "alg": -7, "type": "public-key" }, { "alg": -257, "type": "public-key" } ], "requireResidentKey": true, "requireUserPresence": true, "requireUserVerification": false, "rp": { "name": "webauthn.io", "id": "webauthn.io" }, "user": { "name": "testuser", "displayName": "Test User", "id": "/QIAAAAAAAAAAA==" } } Do you have an example of optionsPayload to Create a New Credential and Make an Assertion

majdslmt commented 5 months ago

@killermonk I think the problem is in fromjson function but my solution is build the payload not from json

// Convert 'rp', 'user', and 'authenticatorSelection' directly from jsonMap. final rp = RpEntity.fromJson(jsonMap['rp']); final user = UserEntity.fromJson(jsonMap['user']); final authenticatorSelection = AuthenticatorSelectionCriteria.fromJson(jsonMap['authenticatorSelection']);

// Initialize the list with a capacity for better performance since its length is known. var keys = List.filled(2, null, growable: false); for (var i = 0; i < keys.length; i++) { keys[i] = PublicKeyCredentialParameters.fromJson(jsonMap['pubKeyCredParams'][i]); }

// Construct the PublicKeyCredentialCreationOptions object. final p = PublicKeyCredentialCreationOptions( rpEntity: rp, userEntity: user, challenge: utf8.encode(jsonMap['challenge']), authenticatorSelection: authenticatorSelection, pubKeyCredParams: keys, );

// Finally, create the credential options object. final rpOptions = CreateCredentialOptions(publicKey: p);

killermonk commented 5 months ago

@majdslmt sorry for my delay here, I've been swamped with work so I haven't had time to look into this yet. Thanks for the details, though. I'm hoping to be able to dig in over the next few days.

killermonk commented 1 month ago

@majdslmt I need to apologize for my delay here. My personal PC's SSD died and by the time I'd gotten things all fixed up I forgot I had this pending issue that we were discussing.

I went through your payload, and you're getting the current error because it is malformed. If you take a look at the CreateCredentialOptions model at lib/src/models/create_credential_options.dart you can see the properties that are required and how the object should be structured.

First, the only key that should exist on the top level is "publicKey", and all the other options need to be moved inside that as a nested object. That's where the Null error is coming from: it can't find that key. Not the greatest error message of all time from the generated code.

Next, your options need to be restructured slightly.

None of these options are supported in this manner.

  "requireResidentKey": true,
  "requireUserPresence": true,
  "requireUserVerification": false,

They need to be moved into the "authenticatorSelection" object as follows:

"authenticatorSelection": {
  "requireResidentKey": true,
  "userVerification": "required"
}

Also, you are missing the "challenge" data. With those modifications you should be able to use the fromJson options now. End result is options that look like this:

{
  "publicKey": {
    "rp": {
      "name": "webauthn.io",
      "id": "webauthn.io"
    },
    "user": {
      "name": "testuser",
      "displayName": "Test User",
      "id": "/QIAAAAAAAAAAA=="
    },
    "clientDataHash": "LTCT/hWLtJenIgi0oUhkJz7dE8ng+pej+i6YI1QQu60=",
    "credTypesAndPubKeyAlgs": [
      ["public-key", -7]
    ],
    "excludeCredentials": [{
      "type": "public-key",
      "id": "lVGyXHwz6vdYignKyctbkIkJto/ADbYbHhE7+ss/87o="
    }],
    "pubKeyCredParams": [
      { "alg": -8, "type": "public-key" },
      { "alg": -7, "type": "public-key" },
      { "alg": -257, "type": "public-key" }
    ],
    "challenge": "AQIDBA==",
    "authenticatorSelection": {
      "requireResidentKey": true,
      "userVerification": "required"
    }
  }
}