How to add passkey authentication to your PHP Symfony app with Corbado

How to add passkey authentication to your PHP Symfony app with Corbado

Passkeys are the new standard login method for web authentication but so far there are very few developer tutorials that show how to add them on a code level. In the following, we will change the password-based login of our PHP Symfony app to a passwordless, passkey-first authentication with Corbado's web component. The web component is an easy to integrate, lightweight authentication layer with great out-of-the-box passkey UX.


GitHub repository link:

Let's get the whole picture first! We use a dockerized setup with three containers:

  1. The first container runs our PHP Symfony application
  2. The second container hosts the database with our existing users.
  3. The third container runs phpMyAdmin to provide easy access to the database
Architecture diagram of existing application

Our folder structure looks like this

Start the system with docker compose up.
Our frontend runs on http://localhost:8000 while our backend is available at http://localhost:8000/api.

Our login page looks like this:

Existing login screen

To integrate the Corbado web component, we follow these steps:

  1. Create project in the Corbado developer panel
  2. Integrate web component in the frontend
  3. Create backend endpoint to generate a session
  4. (Optional) Enable password-based login as fallback
  5. (Optional) Make your local application reachable for Corbado via Corbado CLI

1. Create project in the Corbado developer panel

We start by heading over to the Corbado developer panel and create an account. We are welcomed by this screen:

Getting started

We click on 'Integration guide' to do everything step by step.

Integration guide

We want to use the 'Web component', so that's what we select.

Integration guide existing users

Also, we have a system with an existing user base, so we click 'Yes'. Afterwards, we find ourselves at the overview of the developer panel (you need to confirm your account via email if it's your first time).

Head over to the 'Integration guide'. This guides us through all the steps necessary for the integration to work.

Getting started for the integration guide of web component

In step 1, we create an API secret. We need the project ID and the generated API secret later in order to communicate with Corbado's API. Also, we need to configure our authorized origin. The authorized origin is the browser URL from the site where the web component is integrated (with protocol and port but without path). We need it for CORS (cross-origin request sharing) and binding passkeys to it. In our case, we send it to http://localhost:8000.

Configure authentication to Corbado

In the second, optional step, we configure the webhook. This is needed, so Corbado can communicate with our backend e.g. for checking if a username and password of an existing user match. More details on that later.

Configuration of webhooks

We will later set up our webhook at http://localhost:8000/corbado-webhook with ‘p36qLbyRvHoZDT’ and ‘z9aSidcu2xLyoU’ as credentials, so we can already enter that here.

In step 3, we add our Application URL, Redirect URL and Relying Party ID. The Application URL is the URL in the frontend where the web component runs. For example, it’s used to forward users to the web component again after they clicked on an email magic link.

The Redirect URL is the URL that receives Corbado’s session token as GET parameter, after Corbado has successfully authenticated a user, so that a session can be started. We implement our Redirect URL at http://localhost:8000/redirect later, but we can enter it here already.

The Relying Party ID is the domain where we bind our passkeys to. The domain in the browser where a passkey is used must be a matching domain to the Relying Party ID. As we test locally, we set the Relying Party ID to localhost.

Define Application URL, Redirect URL and Relying Party ID

Just like that, the project is set up!

2. Integrate the Corbado web component in the frontend

Now that we are done with the Corbado settings, let’s get to coding! In the frontend, we integrate the Corbado web component into our login page. Add the following, code in the HTML head, so it loads the JavaScript in advance:

Afterwards, we replace our current login form with the one from Corbado (using your own project id of course):

Taking a glance at our login page in the browser reveals the Corbado web component in action:

Corbado login screen

Hint: If you now enter your email address and tried to sign up or login, you will get an error message. That's because we haven't configured the backend yet.

3. Create backend endpoint to generate a session

Once a user has successfully authenticated via the Corbado web component in the frontend, he gets sent to the Redirect URL with a corbadoSessionToken GET parameter. We configured this Redirect URL to be http://localhost:8000/redirect, remember? We implement this endpoint now.

We’ll initialize the Corbado API client inside the services.yaml file. For this we need the project id and API secret from above.

We add a route for the required endpoint in our routeconfig (via annotation in our framework).

In the redirect endpoint, we read the value of the corbadoSessionToken query parameter. Using the Corbado PHP SDK, we then call Corbado’s sessionVerify API endpoint which will give us the email and name of the user which the token belongs to.

We now know who just logged into our application. We create a session for the user and redirect him to a logged-in page of the app:


If you don't have an existing user base, that's all it takes to integrate passwordless, passkey-first authentication with Corbado!

4. (Optional) Enable password-based login as fallback

We don’t want to lock out our existing users, so we enable password-based authentication as a fallback. Therefore, Corbado needs to communicate with our backend. It does so via a webhook that is set up in our backend. In the steps before, we set the webhook URL to http://localhost:8000/corbado-webhook as well as the webhook username and password.

Corbado provides some PHP code that helps to add the webhooks:

composer require corbado/webhook-php

Then, we add the username and password of the webhook to our PHP Symfony environment variables, so that they are available when we want to authenticate an incoming webhook call:

Now we create a route for the webhook:

as well as a controller to make the webhook available.

The webhook controller uses the previously installed Corbado webhook library and the route we protected via HTTP basic auth.

If a user enters their email in the web component, the webhook we just installed gets a call. The corbado_webhook function will handle it resulting in a call of the userStatus function. If the user exists, meaning he already has a password, Corbado gives him the option to login via password.

Once the user has entered his password, another webhook call is issued resulting in a call to the verifyPassword function. There, we check our database if the given credentials match and send the response back.

Done! All our users can now use passkeys, while existing ones have their password as a fallback option.

But wait, how can Corbado call endpoints if our backend is only running locally, especially during development?

5. Make your local application reachable for Corbado via Corbado CLI

Using the Corbado CLI, we create a tunnel of our local application to the outside world, so that the webhook URL can be called. We install it by following the docs

Architecture diagram with Corbado CLI

Once started, the webhook running on our machine forwards requests from Corbado to our local instance. Analog to the image above, the process is then as follows:

  1. The browser requests our login page
  2. The AppController sends back the HTML page which contains the web component.
  3. After the user has entered the email address, the web component sends it to Corbado.
  4. Corbado processes it and sends a request to our webhook to check if the user exists.
  5. The message gets forwarded through Corbado CLI in order to reach our local instance. (If your solution is in production, Corbado can directly call the Webhook controller without the need for the Corbado CLI tunnel because you then have a public address).
  6. Our webhook controller sends back the requested information.
  7. The message gets forwarded through Corbado CLI again
  8. Corbado tells the web component how to react (e.g., ask the user for a password).

Afterwards, we head to the Corbado developer panel to copy our CLI secret.

We login using the project ID and CLI secret:

corbado login --projectID pro-1987097639455463555 --cliSecret 4uKn...

Once logged in, we can start our tunnel using the subscribe command (with port 8000 as our Symfony application is running here).

corbado subscribe http://localhost:8000 --projectID pro-1987097639455463555 --cliSecret 4uKn...

Now, we can test if our webhook works by going to, filling out the stub data and running the tests. If everything went right, all tests will pass.

Corbado webhook testing

If we now go to http://localhost:8000/login and enter the email of an already existing account, the web component should ask you for a password (if no passkey for the account has been created yet).

Password prompt

Afterwards, users are asked to create a passkey for future logins.

Enjoyed this read?

Stay up to date with the latest news, strategies and insights about passkeys sent straight to your inbox!