Lucky Symfony applications!
An official bundle is provided in the package web-auth/webauthn-symfony-bundle
.
Starting at v3.2.4, the bundle can be installed on Symfony 4.4 or 5.0+.
If you use Laravel, you may be interested in this project: https://github.com/asbiin/laravel-webauthn
Before installing it, please make sure you installed and configured:
The package symfony/psr-http-message-bridge
,
The package nyholm/psr7
or any other PSR-7 package,
The SensioFrameworkExtraBundle and enabled the PSR-7 support.
If you are using Symfony Flex then the bundle will automatically be installed and the default configuration will be set. Otherwise you need to add it in your AppKernel.php
file:
And add the Webauthn Route Loader:
The first step is to create your credential and user entity repositories.
Only Doctrine ORM based repositories are provided. Other storage systems like filesystem or Doctrine ODM may be added in the future but, at the moment, you have to create these from scratch.
With Flex, you have a minimal configuration file installed through a Flex Recipe. You must set the repositories you have just created. You also have to modify the environment variables Relying_PARTY_ID
and Relying_PARTY_NAME
.
You may also need to adjust other parameters.
If you don’t use Flex, hereafter an example of configuration file:
The credential_repository and user_repository parameters correspond to the services we created above.
Please refer to this page. You should let the default value as it is.
If you don't create the creation_profiles
section, a default
profile is set.
The realying Party corresponds to your application. Please refer to this page for more information.
The parameter id
is optional but highly recommended.
By default, the length of the challenge is 32 bytes. You may need to select a smaller or higher length. This length can be configured for each profile:
The default timeout is set to 60 seconds (60 000 milliseconds). You can change this value as follows:
For v4.0+, the timeout will be set to null
. The values recommended by the specification are as follow:
If the user verification is discouraged
, timeout should be between 30 and 180 seconds
If the user verification is preferred
or required
, the range is 300 to 600 seconds (5 to 10 minutes)
This set of options allows you to select authenticators depending on their capabilities. The values are described in the advanced concepts of the protocol.
This option indicates the algorithms allowed for your application. By default, a large list of algorithms is defined, but you can add custom algorithms or reduce the list.
The order is important. Preferred algorithms go first.
It is not recommended changing the default list unless you exactly know what you are doing.
If you need the attestation of the authenticator, you can specify the preference regarding attestation conveyance during credential generation.
Please note that the metadata service is mandatory when you use this option.
The use of Attestation Statements is generally not recommended unless you REALLY need this information.
You can set as many extensions as you want in the profile. Please also refer to this page for more information.
The example below is totally fictive. Some extensions are defined in the specification but the support depends on the authenticators, on the browsers and on the relying parties (your applications).
If you don't create the creation_profiles
section, a default
profile is set.
The parameters for the request profiles (i.e. the authentication) are very similar to the creation profiles. The only difference is that you don’t need all the detail of the Relying Party, but only its ID (i.e. its domain).
Please note that all parameters are optional. The following configuration is perfectly valid. However, and as mentioned above, the parameter id
is highly recommended.
Now you have a fully configured bundle, you can protect your routes and manage the user registration and authenticatin through the Symfony Firewall.
How to register and authenticate my users?
To authenticate or register your users with Symfony, the best and easiest way is to use the Security Bundle. First, install that bundle.
Next, you have to create a custom user provider that will retrieve users on login requests. The following example uses the user repository showed on this page.
Now you can tell Symfony to use your user provider and you enable the dedicated firewall
In some case, you may have several user providers that are used by other parts of your application. This Webauthn bundle allow you to override the default user provider.
Users can logout as usual. You just have to add the logout configuration under your firewall and add the route.
As you have noticed, there is nothing to configure to have a fully functional firewall. The firewall routes are automatically created for you. They are namely:
/login/options
: to create the request options (POST only)
/login
: to submit the assertion response (POST only)
You should also ensure to allow anonymous users to contact those endpoints.
Prior to the authentication of the user, you must create a PublicKey Credential Request Options object. To do so, send a POST request to /login/options
.
The body of this request is a JSON object that must contain a username
field with the name of the user being authenticated.
No need to reinvent the wheel, you can use the webauthn-helper package.
It is mandatory to set the Content-Type header to application/json
.
In case of success, you receive a valid PublicKeyCredentialRequestOptions
object and your user will be asked to interact with one of its registered security devices.
The default path is /login/options
. You can change it if needed:
When the user touched the security device, you will receive a response from it. You just have to send a POST request to /login
.
The body of this request is the response of the security device.
It is mandatory to set the Content-Type header to application/json
.
The default path is /login
. You can change that path if needed:
Your user can now be authenticated and retrieved as usual.
By default, the default
profile is used. You may have created a request profile in the bundle configuration. You can use this profile instead of the default one.
The user registration is also managed by the firewall. It is disabled by default. If you want that feature, please enable it:
The firewall routes are automatically created for you. They are namely:
/register/options
: to create the creation options (POST only)
/register
: to submit the attestation response (POST only)
You should also ensure to allow anonymous users to contact those endpoints.
Prior to the registration of a user and its authenticator, you must create a PublicKey Credential Creation Options object. To do so, send a POST request to /register/options
.
The body of this request is a JSON object that must contain username
and displayName
fields with the username of the user being registered and the name displayed in the application.
It is mandatory to set the Content-Type header to application/json
.
In case of success, you receive a valid PublicKeyCredentialCreationOptions
object and your user will be asked to interact with its security device.
The default path is /register/options
. You can change it if needed:
When the user touched the security device, you will receive a response from it. You just have to send a POST request to /register
.
The body of this request is the response of the security device.
It is mandatory to set the Content-Type header to application/json
.
The default path is /register
. You can change that path is needed:
In case of success, the user and the authenticator are correctly registered and automatically logged in.
By default, the default
profile is used. You may have created a creation profile in the bundle configuration. You can use this profile instead of the default one.
The security token returned by the firewall sets some attributes depending on the assertion and the capabilities of the authenticator. The attributes are:
IS_USER_PRESENT
: the user was present during the authentication ceremony. This attribute is usually set to true
by authenticators,
IS_USER_VERIFIED
: the user was verified by the authenticator. Verification may be performed by several means including biometrics ones (fingerprint, iris, facial recognition…).
You can then set constraints to the access controls. In the example below, the /admin path can be reached by users with the role ROLE_ADMIN
and that have been verified during the ceremony.
Webauthn authentication and registration are 2 steps round trip processes:
Options issuance
Authenticator response verification
It is required to store the options and the user entity associated to it to verify the authenticator responses.
By default, the firewall uses Webauthn\Bundle\Security\Storage\SessionStorage
. This storage system stores the data in a session.
If this behaviour does not fit on your needs (e.g. you want to use a database, Redis…), you can implement a custom data storage for that purpose. Your custom storage system has to implement Webauthn\Bundle\Security\Storage\RequestOptionsStorage
and be declared as a container service.
When done, you can set your new service in the firewall configuration:
You can customize the responses returned by the firewall by using a custom handler. This could be useful when you want to return additional information to your application.
There are 4 types of responses and handlers:
Request options,
Creation options,
Authentication Success,
Authentication Failure,
This handler is called when a client sends a valid POST request to the options_path
during the authentication process. The default Request Options Handler is Webauthn\Bundle\Security\Handler\DefaultRequestOptionsHandler
. It returns a JSON Response with the Public Key Credential Request Options objects in its body.
Your custom handler has to implement the interface Webauthn\Bundle\Security\Handler\RequestOptionsHandler
and be declared as a service.
When done, you can set your new service in the firewall configuration:
This handler is very similar to the previous one, except that it is called during the registration of a new user and has to implement the interface Webauthn\Bundle\Security\Handler\CreationOptionsHandler
.
This handler is called when a client sends a valid assertion from the authenticator. The default handler is Webauthn\Bundle\Security\Handler\DefaultSuccessHandler
.
Your custom handler has to implement the interface Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface
and be declared as a container service.
When done, you can set your new service in the firewall configuration:
This handler is called when an error occurred during the authentication process. The default handler is Webauthn\Bundle\Security\Handler\DefaultFailureHandler
.
Your custom handler has to implement the interface Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface
and be declared as a container service.
When done, you can set your new service in the firewall configuration:
With Doctrine, you have to indicate how to store the Credential Source objects. Hereafter an example of an entity. In this example we add an entity id
and a custom field created_at
. We also indicate the repository as we will have a custom one.
As the ID must have a fixed length and because the credentialId
field of Webauthn\PublicKeyCredentialSource
hasn’t such a requirement and is a binary string, thus we need to declare our own id
field.
Do not forget to update your database schema!
To ease the integration into your application, the bundle provides a concrete class that you can extend.
In this following example, we extend that class and add a method to get all credentials for a specific user handle. Feel free to add your own methods.
We must override the method saveCredentialSource
because we may receive Webauthn\PublicKeyCredentialSource
objects instead of App\Entity\PublicKeyCredentialSource
.
This repository should be declared as a Symfony service.
With Symfony Flex, this is usually done automatically
In a Symfony application context, you usually have to manage several user entities. Thus, in the following example, the user entity class will extend the required calls and implement the interface provided by the Symfony Security component.
Feel free to add the necessary setters as well as other fields you need (creation date, last update at…).
Please note that the ID of the user IS NOT generated by Doctrine and must be a string. We highly recommend you to use UUIDs.
The following example uses Doctrine to create, persist or perform queries using the User objects created above.
This repository should be declared as a Symfony service.
With Symfony 4, this is usually done automatically