davidearl / webauthn

An implementation of webauthn in PHP on the server side (e.g Yubico 2 and Google Titan keys)
https://webauthn.davidearl.uk
MIT License
129 stars 24 forks source link

Multiple Keys per user #60

Open lukd06 opened 1 year ago

lukd06 commented 1 year ago

Is there any way to store multiple keys for one user?

davidearl commented 1 year ago

Yes, that's the default behaviour.

When someone registers a key, the WebAuthn::register function returns a string which you should store with your user record in your user database. If it's the first time, pass in an empty string as the second parameter of register ($userwebauthn), but if you already have a registration, pass in the existing string from the user record, and once complete, replace it with the new string from the register function. The easiest way to do this is when you first add the user record, set the registration string as an empty string, always pass it in to register, and always save the result back to the user record afterwards.

This works because on return from register() the string actually contains a JSON array of keys and if passed an array to start with it adds to it. The authentication challenge checks against all of them.

If you try to do this in the example, it says 'user already exists', but that's because there's no other authentication in the example, so people could deliberately or inadvertently to each others keys (in real world, your user would usually have to be already authenticated to be able add an additional registration). If you run your own copy of the example, with the check for duplicate user replaced with retrieval of an existing user record, you should find it will allow you to add additional keys for that user, i.e. in the example index.php instead of

      if (file_exists(userpath($username))) {
        oops("user '{$username}' already exists");
      }

      /* Create ... /
      $user = (object)['name'=> $username,
                       'id'=> $userid,
                       'webauthnkeys' => $webauthn->cancel()];
      saveuser($user);

put something like

     if (file_exists(userpath($username))) {
        $user = getuser($username);
      } else {
        $user = (object)['name'=> $username,
                           'id'=> $userid,
                           'webauthnkeys' => $webauthn->cancel()];
        saveuser($user); 
     }
lukd06 commented 1 year ago

Thank you for the fast response, but how can I send the user multiple challenges to solve?

davidearl commented 1 year ago

The way I do this in an app that uses the library is the user account gets created with a password initially, then once they are logged in there's a button in the user interface which lets them add a key. They can just use that button on different platforms as often as necessary, for example with FaceID on their iPhone, fingerprint on their iPad and Windows Hello on Windows.

If you want to use it for 2FA rather than the primary authentication method, you'd do much the same - you'd have a button to add 2FA once they are logged in, which they can use as many times as they want on different platforms (perhaps alongside one to add a TOTP 2FA app, or text message as alternatives to a webauthn key).

If you ONLY want to have logins using a key, you're going to have to find a way for them to authenticate on another platform to add a key to their account. You could do it like a password reset - so they can send themselves a temporary, time-limited token to their email, which they then open on the second platform, which logs them in, and they can then add another token by pressing a button.

I hope it is obvious that you have to know for sure who they are before you can add a second or more keys to their account: that's not a limitation of the code, it's fundamental, or other people could add keys to someone's account.