PHP Symfony & Passkeys: This tutorial shows how to integrate passkeys into PHP Symfony apps by implementing passkey-first authentication & session management.
Lukas
Created: April 17, 2023
Updated: February 17, 2025
We aim to make the Internet a safer place using passkeys. That's why we want to support developers with tutorials on how to implement passkeys.
1. Introduction: PHP Symfony Passkeys
2. PHP Symfony Project Prerequisites
3. Repository Structure for PHP Symfony Passkey Project
4. Set Up Your PHP Symfony Project
5. Set Up Corbado Passkey Authentication
5.1 Set Up Your Corbado Account and Project
5.3 Create the Homepage to Handle Sign-ups and Logins
5.4 Create a Profile Route using the Corbado PHP SDK
In this blog post, we’ll explore how to implement passkeys for secure authentication using Corbado. We'll cover the setup of the Corbado web-js package for the frontend part as well as the Corbado PHP SDK for the PHP backend part.
If you want to see the finished code, please have a look at our PHP Symfony passkeys example application GitHub repository.
The result looks as follows:
This tutorial assumes basic familiarity with PHP Symfony, JavaScript and HTML. Also make sure to have PHP as well as composer and the Symfony CLI installed and accessible from your shell. Let's dive in!
Recent Articles
⚙️
Vue.js Passkeys: How to Implement Passkeys in Vue.js Apps
⚙️
Flask Passkeys: How to Implement Passkeys with Python Flask
⚙️
Tutorial: How to Add Passkeys to Node.js (Express) App
⚙️
The 34 Best Passkey SDKs and Libraries for Your Framework
📖
WebAuthn Conditional UI (Passkeys Autofill) Technical Explanation
A Symfony project contains many files, but the important ones for us in this tutorial are the following:
. ├── .env # Contains all environment variables ├── templates | ├── home | | └── index.html.twig # Homepage template | ├── profile | | └── index.html.twig # Profile page template | └── base.html.twig # Layout for our pages └── src └── Crontroller ├── ProfileController.php # Responsible to retrieve profile information in backend └── HomeController.php # Render's our homepage
Let's start by creating our PHP Symfony app:
symfony new example_passkeys_php_symfony --version="7.0.*" --webapp
That's it for now.
To set up Corbado passkey authentication in your PHP Symfony project, follow the next steps.
Visit the Corbado developer panel to sign up and create your account (you'll see the passkey sign-up in action here!).
After sign-up, a project wizard will guide you through the necessary steps to get everything up and running:
Application URL
and Relying Party ID
. The Application URL
is the URL where you embed the Corbado UI component. In this example, we set it to http://localhost:8000
. The Relying Party ID
is the domain (no protocol, no port and no path) where passkeys should be bound to. Here, it's localhost
(you can define both values als in the Settings > General > URLs of the Corbado developer panel).Afterwards, you'll see the relevant HTML / JavaScript code snippets you need to integrate into the project. The subsequent sections of this article will explain them in detail.
As another step, we create an API secret
which will be needed to request user data from the
Corbado backend. Please create an API secret
in Settings > Credentials > API secrets.
Afterwards, we need to also get the Frontend API
and Backend API
URLs which we can find
in Settings > General.
Next, you need to go back to your terminal and should navigate to the root directory of the project:
cd example_passkeys_php_symfony
Open the .env
file at the root of your project.
Copy and paste the Project ID
, API secret
, Frontend API URL
and Backend API URL
(you can obtain it from the Corbado developer panel from Settings > General > Project info above into it. The .env
file should look like this now:
CORBADO_PROJECT_ID=<your-project-id> CORBADO_API_SECRET=<your-api-secret> CORBADO_FRONTEND_API=<your-frontend-api-url> CORBADO_BACKEND_API=<your-frontend-api-url>
First, install the Corbado PHP SDK:
composer require corbado/php-sdk
To utilize caching for JWKS, you need a PSR-6 compatible cache implementation, which we'll install like this:
composer require symfony/cache
That's it for the setup.
Our homepage will use the Corbado web-js package to handle passkey authentication.
Set the HomeController
up like this:
symfony console make:controller HomeController
Under the templates
folder, our project should already contain a base.html.twig
file.
We'll create a surrounding layout for our pages here.
It will load the Corbado web-js assets in the head of the document.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %}Welcome!{% endblock %}</title> <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>"> <link rel="stylesheet" href="https://unpkg.com/@corbado/web-js@2.9.0/dist/bundle/index.css" /> <script src="https://unpkg.com/@corbado/web-js@2.9.0/dist/bundle/index.js"></script> {% block stylesheets %} {% endblock %} {% block javascripts %} {% block importmap %}{{ importmap('app') }}{% endblock %} {% endblock %} </head> <body> {% block body %}{% endblock %} </body> </html>
The templates
folder should also contain a home folder with an index.html.twig
file in it.
We'll extend our base file, and add a div with the id of corbado-auth
.
In a separate script tag, we'll be loading our Corbado project with our Project ID
and set the setShortSessionCookie
option to true, so a short_term_session
cookie (represented as JWT) is created on successful login.
Lastly, we'll inject the authentication UI in our div.
{% extends 'base.html.twig' %} {% block title %}Homepage{% endblock %} {% block body %} <div id="corbado-auth"></div> <script type="module"> await Corbado.load({ projectId: "{{ projectId }}", setShortSessionCookie: true, darkMode: "off", }); async function onLoggedIn() { window.location.href = "/profile" } const authElement = document.getElementById('corbado-auth'); //Element where you want to render CorbadoAuth UI Corbado.mountAuthUI(authElement, { onLoggedIn: onLoggedIn, }); </script> {% endblock %}
Now, head into the src/Controller/Controller.php
file.
We'll configure the homepage route here and provide our Corbado project ID to our template.
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; class HomeController extends AbstractController { #[Route('/', name: 'homepage')] public function index(): Response { return $this->render('home/index.html.twig', [ 'controller_name' => 'HomeController', 'projectId' => $_ENV['CORBADO_PROJECT_ID'], ]); } }
To try it out, run your application with the following command:
symfony server:start
Note: The component will only work properly when the page is visited on localhost
.
Create a ProfileController
with the following command:
symfony console make:controller ProfileController
Our controller will make user information available to our template by using the Corbado PHP SDK.
Now, head into the created Controller file under
src/Controller/ProfileController.php
.
In the controller's constructor, we'll instantiate the PHP SDK:
<?php namespace App\Controller; use Corbado\Exceptions\AssertException; use Corbado\Exceptions\ConfigException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Corbado\Config; use Corbado\SDK; class ProfileController extends AbstractController { private SDK $corbadoSDK; /** * @throws ConfigException * @throws AssertException */ public function __construct() { $jwksCache = new FilesystemAdapter(); $config = new Config($_ENV['CORBADO_PROJECT_ID'], $_ENV['CORBADO_API_SECRET'], $_ENV['CORBADO_FRONTEND_API'], $_ENV['CORBADO_BACKEND_API']); $config->setJwksCachePool($jwksCache); $this->corbadoSDK = new SDK($config); } }
Now, add a showProfile
method, which will make user information available to our template:
class ProfileController extends Controller { // .... #[Route('/profile', name: 'profile')] public function showProfile(Request $request): Response { // Retrieve the short-term session value from the Cookie or // the Authorization header. $shortTermSessionValue = '<Your short-term session value>'; if ($shortTermSessionValue == '') { // If the short-term session value is empty (e.g. the cookie is not set or // expired), the user is not authenticated. Redirect to the login page. return $this->redirectToRoute('homepage'); } try { $user = $this->corbadoSDK->sessions()->validateToken($shortTermSessionValue); } catch (\Exception $e) { $this->addFlash('error', 'Unable to retrieve user information.'); return $this->redirectToRoute('homepage'); } return $this->render('profile/index.html.twig', [ 'user' => $user, 'projectId' => $_ENV['CORBADO_PROJECT_ID'], ]); } }
Next, we'll create the template under templates/profile/index.html.twig
.
It will render some user information and use the Corbado web-js package again to handle logout button functionality.
We add an event listener to the button that uses the Corbado.logout
utility.
{% extends 'base.html.twig' %} {% block title %}User Profile{% endblock %} {% block body %} <h1>User Profile</h1> {% if user %} <p><strong>Id:</strong> {{ user.getId() }}</p> <p><strong>Email:</strong> {{ user.getEmail() }}</p> <button id="logoutButton"> Logout </button> <!-- Add other user information as needed --> {% else %} <p>No user information available.</p> <a href="/">Go back home</a> {% endif %} <script type="module"> await Corbado.load({ projectId: "{{ projectId }}", setShortSessionCookie: true }); document.getElementById("logoutButton").addEventListener("click", async () => { await Corbado.logout() window.location.href = "/" }) </script> {% endblock %}
That's it. We now have a fully functioning profile page using information obtained from our PHP server and Corbado web-js for frontend functionality like a logout.
Finally, take a look at the result by running:
symfony server:start
Visit http://localhost:8000 to see the following screen:
After signup, you'll be redirected to the profile page. Here, you can log out and return to the homepage again.
After a successful sign-up / login, you'll see the profile page at http://localhost:8000/profile
:
This tutorial showed how easy it is to add passwordless authentication with passkeys to a PHP Symfony application using Corbado. Besides the passkey-first authentication, Corbado provides simple session management, that we used for a retrieval of basic user data. If you want to read more about how Corbado's session management please check the docs here.
Table of Contents
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.