Add WebAuthn Support to XWiki
Description
About:
The idea is to allow browsers to automatically authenticate on an XWiki instance using the new WebAuthn standard.
This project has the following use-cases:
- Ability to register webauthn credentials for an existing standard XWiki user.
- An existing XWiki user can then use these credentials to authenticate themselves on XWiki.
XWiki supports several different authentication mechanisms for authenticating users. WebAuthn is meant to work as an independent authenticator, but the user can fall back to the default form login if the browser on which he is accessing XWiki does not support WebAuthn and many other exceptional circumstances.
- For the integration of the Web Authentication API(WebAuthn) with XWiki, java-webauthn-server will be used as it provides better support and many useful methods along with the implementation of the Relying Party operations.
- With WebAuthn(also known as FIDO2), public-key cryptography is used to authenticate end users on an XWiki instance. When a user registers for an account using WebAuthn, a credential key-pair: a public and a private key is generated on XWiki and the platform-authenticator respectively. The public key will be sent to the server for storage but the private key will never leave the authenticator. When the user makes a request to login, the WebAuthn authenticator sends an assertion that proves the user possesses the private key. XWiki WebAuthn authenticator will then use the public key to validate the assertion before allowing the user in.
The WebAuthn API will have two methods that will correspond to register and login:
- navigator.credentials.create(): when supplied with a public key creates new credentials in DB, either for registration of a new account or associating an asymmetric key-pair credential with an existing account.
- navigator.credentials.get(): when used with a public key uses an existing set of credentials to authenticate to XWiki.
Features:
- A user will be able to register a credential key pair and use it to authenticate themselves on an XWiki instance without the hassle of remembering the password.
- The current plan is to add register, login, and recover/delete credential facilities for the users.
- A clean minimal looking user interface that will complement the existing XWiki user interface.
Architecture:
The front end interacts with the server via a REST API, managing translation between JSON request/response payloads and domain objects, and most methods simply call into analogous methods in the server layer. The REST API then delegates to the server layer, this layer manages the general architecture of the application and is where most business logic and integration code would go. The application implements the "persistent" storage of users and credential registrations which simply keeps them stored in memory for a limited time. The transient storage of pending challenges is also kept in memory, but for a shorter duration. The logic for authorizing the registration of additional credentials, and deregistration of credentials, will also be in this layer. In general, anything that would be specific to an XWiki user would go in this layer.
The server layer in turn calls the library layer, which is where the webauthn-server-core library gets involved. The entry point into the library is the RelyingParty class. This layer implements the Web Authentication Relying Party Operations and takes care of all RP-agnostic parts of the Web Authentication logic: generating challenges and verifying all aspects of the responses. It is mostly stateless and exposes integration points for the storage of challenges and credentials. Some notable integration points are:
- Providing an implementation of the CredentialRepository interface to use for looking up stored public keys, user handles, and signature counters.
- Providing an instance of the MetadataService interface to enable the identification and validation of authenticator models. This instance is then used to look up trusted attestation root certificates.
Implementation details:
The Yubico java-webauthn-server library requires an implementation of the CredentialRepository interface. It uses this implementation to read the credentials from the database and to store new credentials into the database. The library also needs an instance of RelyingParty.
The two important properties we have to set are RelyingPartyIdentity.id and RelyingParty.origins. The id must be set to the origin of your web application URL. If we test this with "https://www.example.com", the id must be set to the value "www.example.com". The id is checked on the client and if this value does not match with the URL of our application, 'the navigator.credentials.create()' and 'navigator.credentials.get()' methods fail with an error.
The second important property origins must contain the full URL of our application. With the URL above, it has to be set to "https://www.example.com". The origins property is checked on the server, and the java-webauthn-server library throws an exception if the URL of the application is not listed in the origins collection.
Registration:
1,2: First, the user enters his username and the client sends a POST request to /registration/start.
3,4,5: The server checks if the user name is already taken and sends back an error message if that is the case. Otherwise, the application inserts the user into the database.
6,7: Next, the handler calls the startRegistration() method from the RelyingParty singleton. This call requires a UserIdentity instance which we build with the user name and user id. The method creates a PublicKeyCredentialCreationOptions instance with a random challenge. Our endpoint sends this object back to the client. We must store the PublicKeyCredentialCreationOptions instance in a cache because later we need to pass this object to the finishRegistration() method as an argument.
8: The client receives the PublicKeyCredentialCreationOptions object as response and passes it to the navigator.credentials.create() method.
9: The browser interacts with the security key and gets back a PublicKeyCredential object. This object contains an AuthenticatorAttestationResponse object with the public key and the signed challenge.
10: The application sends this object with a POST request to the /registration/finish endpoint.
11: The finish endpoint retrieves the response object of the first request from the cache, calls the finishRegistration() method and passes the PublicKeyCredential response from the client and the PublicKeyCredentialCreationOptions as the arguments.
12,13,14: The finishRegistration() method validates the signature and public key and throws a RegistrationFailedException if that fails. If the call succeeds the application stores the new credential into a 'credentials' table (addCredential()) and sends back a success message to the server.
Login:
1,2,3: The user enters his username, the application sends the user name in a POST request to /assertion/start. The server calls the startAssertion() method of the RelyingParty object to create an instance of PublicKeyCredentialRequestOptions. To create this object the startAssertion() method fetches the credentials of the given user from the database and creates a random challenge. Like in the registration process, we have to store this object in a cache because we need to pass it to the finishAssertion() method.
4,5: The server sends back the object, and the client passes it to the navigator.credentials.get() method.
6: The message goes to the authenticator, which signs the random challenge from the server with it's private key and sends back a Credential object. Unlike the PublicKeyCredential object we get back from the create() method, Credential only contains the challenge and signature without the public key.
7,8,9,10,11: The application sends this object with a POST request to the /assertion/finish endpoint. The server then validates and verifies the signature by reading the corresponding public key from the credentials table. If the validation succeeds the application updates the signature counter (credentials count) in the database. The counter serves as a protection against "replay attacks".
Remaining:
- It is yet to be decided whether the webauthn authenticator will act as an independent authenticator or as an authenticator on top of the login form(user-pass).
- An extra feature to delete the credentials or recover them back will also be implemented after the registration and login functionalities are implemented.
- There will be a way for the user to prevent losing access to his account if he sets up multiple authenticators. There will be a limit to it.
All of these "remaining" will be discussed among the mentors and the community on the forum post of the project and will be updated here afterwards.