In order to use a PassKey, the client needs to create the key, and coordinate with the server.
We should note that this is a VERY BASIC EXAMPLE. It’s quite possible to get a lot more involved, and a lot more secure.
Multi-Step Process
There are a few steps that we take, in creating an account on the server, and a PassKey for that account.
Before We Start: Relying Party
This is a shared agreement on who the server is. It consists of a name, and an “RP ID,” which is a string (usually a URL). It will be part of the communications between the client and the server, and constitutes a basic “sanity check.”
Challenge Step
Client Requests Challenge From Server
The first thing that happens, is that the client requests a “create challenge” from the server. In our implementation the client also sends a “display name” (the name used for the PassKey), and a user ID (which is used by the server, but we create because it’s easier for us. It’s a simple UUID.).
Server Responds With Challenge And Public Key Credential Data
The server will temporarily save the display name and user ID. It will then generate a “challenge,” which is really just a random string. It also temporarily saves this string, and sends it back to the client, along with the user ID, display name, and local relying party information. This exchange is not encrypted (except by TLS).
NOTE: In reality, only the challenge really needs to be saved on the server, but we also save the display name and user ID, for convenience.
Client Creates the PassKey, and Sends It to the Server
The client then uses a local instance of ASAuthorizationController to create a signed data package to return to the server.
At this point, the key pair will be created, and the private key will be used to sign the package.
The package will include:
- The challenge sent from the server.
- A Credential ID, created by the Auth Controller. This is a simple, unique byte string.
- The display name for the key (from the create request).
- The user ID (from the create request).
- The client’s understanding of the Relying Party.
These are all signed with the private key.
The client then sends the signed data package and the public key back to the server.
Creation Step
The server then checks the signed package, using the public key that was provided with it. If the challenge from the package matches the one temporarily stored on the server, then the server creates the PassKey record (the public key is stored, along with the Credential ID), and responds with a success indication.
In our case, a “success” indication includes an automatic login, so we return a new bearer token to the client. In our app, we track logins via a bearer token (which is not actually part of the PassKey. The PassKey is used to authorize the creation of the token).
Now that we know what the workflow is, let’s look at the code that we use to implement it.