PHP Symfony

To see a full example of our PHP Symfony application integrating our web component, please check our sample app on GitHub.


Corbado offers one of the simplest ways to add authentication to your PHP Symfony application. This guide walks you through the necessary steps to install and use a web component in a PHP Symfony application with existing users.
For this we have to:

1. Create and configure a Corbado project

Please follow the instructions of our Getting started page to set up a Corbado account and configure your project in the Corbado developer panel.
To avoid confusion the following things are configured in advance. We will create the respective endpoints etc. later.
  1. 1.
    We add our Application URL here. 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.
  2. 2.
    The Redirect URL is the URL that the user gets sent to, after Corbado has successfully authenticated a user and initialized a session. We implement our Redirect URL at http://localhost:8000/ later, but already enter it here.
  3. 3.
    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 here.
  4. 4.
    In this project we have existing users. Therefore we need to configure the webhook URL. 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. In this project we will later set up our webhook at http://localhost:8000/corbado-webhook, so that's what we would put in here.
Just like that, the project is set up!

2. Add the web component to 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 beginning of the HTML body:
<script src="https://<project ID>"></script>
Afterwards, we embed the web component (using your own project ID of course):
<corbado-auth project-id="<project ID>" conditional="yes">
<input name="username" id="corbado-username" required autocomplete="webauthn"/>
Taking a glance at our login page (which must be the Application URL configured beforehand) in the browser should reveal the Corbado web component in action.

3. Adapt the backend

3.1. Create security adapter and logged-in page

Once a user has successfully authenticated via the Corbado web component in the frontend, a session gets initialized by Corbado and the user gets sent to the Redirect URL. We configured this Redirect URL to be http://localhost:8000/ in step 1.
We implement this endpoint now. First, we initialize the Corbado API client inside the services.yaml file. Here, we need the project ID and API secret.
$projectID: '%app.projectID%'
$apiSecret: '%app.apiSecret%'
- setJwksCachePool: [ '@Symfony\Component\Cache\Adapter\NullAdapter' ]
Next, we create our security adapter. There, we register our UserProvider which manages the current user. This is done for maximum flexibility, so we can extend our own user object with attributes of our choice. Also we register a custom Authenticator which should manage the user verification.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
id: App\Security\CorbadoUserProvider
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
lazy: true
provider: Corbado
- App\Security\CorbadoAuthenticator
However, Corbado takes care of all aspects of the actual authentication. The Authenticator therefore just needs to retrieve the current user from the Corbado PHP SDK whenever it is needed.
$user = $this->corbado->sessions()->getCurrentUser();
if ($user->isAuthenticated() === false) {
throw new CustomUserMessageAuthenticationException('User not authenticated');
Finally, we add a route just for signed-in users (via annotation in our framework).
#[Route(/, name: home, methods:GET)]
Inside our home route, we use our custom Security service to retrieve the current user.
#[Route('/', name: 'home', methods: 'GET')]
public function home(Security $security, string $projectID): Response
$sessionUser = $security->getUser();
if (!$sessionUser instanceof User) {
return $this->redirectToRoute('login');
return $this->render(
'projectID'=> $projectID,
'username'=> $sessionUser->getEmail(),
'userFullName' => $sessionUser->getName(),
We now know who just logged into our application. We can therefore display the logged-in page of our app.

3.2. (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:
# -------- Webhook --------
path: /corbado-webhook
controller: App\Controller\WebhookController::corbado_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.
class WebhookController extends AbstractController
#[Route('/corbado-webhook', name: 'corbado', methods: 'POST')]
public function corbadoWebhook(UserRepository $userRepo, Request $request, string $webhookUsername, string $webhookPassword): Response
try {
// Create new webhook instance with username and password. Both must be
// set in the developer panel ( and are used to secure your
// webhook (this one here) with basic authentication.
$webhook = new Webhook($webhookUsername, $webhookPassword);
// Handle authentication so your webhook is secured (basic authentication). If username
// and/or password are invalid handleAuthentication() will send HTTP status code
// 401 (Unauthorized) and terminate/exit execution here. $webhook->handleAuthentication();
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.
function userStatus(UserRepository $userRepo, string $username): string
$user = $userRepo->findOneBy(['email' => $username]);
//Look up in database if $username exists and if $username is blocked/not permitted to login
if ($user == null) {
return AuthMethodsDataResponse::USER_NOT_EXISTS;
} if ($user->isBlocked()) {
return AuthMethodsDataResponse::USER_BLOCKED;
return AuthMethodsDataResponse::USER_EXISTS;
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.
* Verify given username and password.
* @param UserRepository $userRepo
* @param string $username
* @param string $password
* @return bool */
private function verifyPassword(UserRepository $userRepo, string $username, string $password): bool
$user = $userRepo->findOneBy(['email' => $username]);
if ($user == null || $user->getPassword() == null) {
return false;
return password_verify($password, $user->getPassword());
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?

4. 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.
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. 1.
    The browser requests our login page
  2. 2.
    The AppController sends back the HTML page which contains the web component.
  3. 3.
    After the user has entered the email address, the web component sends it to Corbado.
  4. 4.
    Corbado processes it and sends a request to our webhook to check if the user exists.
  5. 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. 6.
    Our webhook controller sends back the requested information.
  7. 7.
    The message gets forwarded through Corbado CLI again
  8. 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 <project ID> --cliSecret <CLI secret>
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
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.
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). Afterwards, users are asked to create a passkey for future logins.