AppKeySwift #
Any sufficiently advanced technology is indistinguishable from magic. – Arthur C. Clarke
Installation in XCode #
-
In Xcode, select File > Swift Packages > Add Package Dependency.
-
Copy and paste the following into the search/input box, then click Next.
https://github.com/Cosync/AppKeySwift.git
-
Leave the default value of Up to Next Major, then click Next.
-
Select the Package Product; AppKeySwift, then click Finish
Architecture #
The AppKeySwift package offers three core classes/enumerations that together provide full support for Passkey authentication via the AppKey service:
- AppKeyAPIManager
- AKPasskeysManager
- AppKeyError
The AppKeyAPIManager class is a set of asynchronous functions, each directly mirroring a call to the AppKey REST API. It abstracts away the work of properly formatting HTTPS headers and handling network requests on behalf of the client application. Since these functions are asynchronous, they allow the main thread to continue executing other tasks until the network call returns.
The AKPasskeysManager class manages access to the iOS or macOS keychain, where it securely stores and retrieves private keys for user-specific passkeys. It also provides connections to standard iOS UI components for biometric verification, such as Face ID, ensuring that private keys are accessible only after the user’s biometric credentials are verified.
Currently, AppKeySwift supports only synchronized PassKeys using the iOS keychain. Hardware-based passkey support is planned for future releases.
Finally, AppKeyError defines a comprehensive set of error codes and messages that may be raised by AppKeySwift.
Signup Flow #
The client-side user signup flow in AppKeyAPIManager unfolds in three steps:
- signup
- signupConfirm
- signupComplete
The signup() function initiates user registration by sending a unique handle and display name to the AppKey service. If the handle is already taken, the function returns an error. Otherwise, it responds with a userId, username, and a challenge — intended to be signed by a new private key generated on the client. Next, the client calls signUpWith() in AKPasskeysManager to create a public/private key pair for the new passkey, requiring a biometric check (such as Face ID) to ensure the user’s authenticity. This step produces an attestation object that contains the signed challenge returned by signup(). The username is generally derived from the handle, augmented with an app-specific identifier.
Next, signupConfirm() is called to trigger the delivery of a six-digit code to the handle (email or phone number) associated with the passkey. This code confirms that the user controls the handle they’ve registered. This step returns a signup token—a JWT token for signup. After retrieving the code from their email, the client calls signupComplete() with the code to finalize passkey creation on the AppKey service. This function then issues an accessToken along with a JWT token for JWT-based login.
The six-digit code sent to the registered email or phone is the only multi-factor authentication (MFA) step in the passkey setup. This MFA happens only during setup, never during login — saving the user time and sparing them the hassle often associated with password-based logins layered with MFA. It’s also more secure, as the email can’t later be exploited to take over the account, unlike traditional email-based authentication.
Login Flow #
The client-side user login flow in AppKeyAPIManager has two steps:
- login
- loginComplete
The login() function initiates login by sending the user handle to the AppKey service. If no account is associated with the handle, it returns an error. Otherwise, it responds with a challenge, which the client must sign using the private key associated with the user’s passkey. To sign this challenge, the client calls signInWith() in AKPasskeysManager, using the private key (stored securely on the client) and requiring biometric authentication, like Face ID, to confirm the user’s identity. This generates an assertion object that includes the signed challenge from login().
Next, loginComplete() is called to let the AppKey service verify the signed challenge. The AppKey service only has the public key for the user’s passkey, but this is enough to authenticate the signed challenge, which can only be correctly generated by the private key on the client. Upon verification, this function returns a user object, an accessToken, and a JWT token for JWT-based login.
Appplication keys #
In AppKey, each application provides two keys that developers need to integrate into their application code to enable passkey authentication. These keys are:
- Public Key
- App Token
The public key is used by the application to verify that JWT tokens signed by AppKey are valid using the isValidJWT() function. This public key can be presented in two formats: the PEM format, which includes a ‘BEGIN PUBLIC KEY’ header, or as a raw public key - a long string of hexadecimal characters. The format you need depends on your application’s requirements. The isValidJWT() function uses the RAW public key format. It’s important to note that only the public key provided in the AppKey Portal can be used to verify JWT signatures. These signatures are generated with a corresponding private key that is not shared with the developer.
The second key is the App Token. Developers use this token in the AppKey REST API to access services linked to their application. The App Token should remain confidential, as it uniquely identifies an authorized application client with access to the AppKey REST API.