Symfony 8.2 makes it easier to prevent accidental logouts, return custom logout responses and synchronize logout with an OpenID Connect provider.
Logging Out with a POST Form
Logging out with a GET request only takes a link, and browsers, link-preview
bots, security scanners and corporate proxies follow links on their own. The
fix is to only accept POST requests on your logout route:
1 2 3 4
# config/routes.yaml
app_logout:
path: /logout
methods: [POST]
This requires a form instead of a link. To help build it, Symfony 8.2 adds a new
logout_form() Twig function that returns the form action and its hidden
fields (including the CSRF token, if enabled in the firewall):
1 2 3 4 5 6 7 8 9
{% set logout = logout_form() %}
<form method="post" action="{{ logout.action }}">
{% for name, value in logout.fields %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
{% endfor %}
<button>Log out</button>
</form>
Like logout_path(), it accepts the firewall name as an optional argument.
In PHP code, use the new LogoutUrlGenerator::getLogoutForm() method.
Since every application ends up writing that same form, Symfony 8.2 also adds a
logout_button() Twig function that renders it for you. Its CSRF field works
like the one of Symfony forms, including with stateless CSRF protection:
1 2 3 4
{{ logout_button() }}
{# optional: the label, the firewall name, and the attributes of the button and the form #}
{{ logout_button('Sign out', attr: {class: 'btn-link'}, form_attr: {class: 'd-inline'}) }}
The same goes for impersonation when you restrict it to POST requests: the
new impersonation_button() and impersonation_exit_button() functions
render the forms of impersonation_form() and impersonation_exit_form().
The exit button renders nothing when the user is not impersonating anyone:
1 2
{{ impersonation_button('Impersonate', user.userIdentifier, path('homepage')) }}
{{ impersonation_exit_button(target_uri: path('admin_users')) }}
Disabling the Redirect After Logout
When using JSON login, you can customize the login response in a controller,
but customizing the logout response requires an event listener. In Symfony
8.2, set the logout.target option to null to disable the
redirect after logout:
1 2 3 4 5 6 7 8 9
# config/packages/security.yaml
security:
firewalls:
main:
json_login:
check_path: auth_login
logout:
path: auth_logout
target: ~
After logging the user out, the request now reaches the controller of the logout route, so you can create the response there:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/AuthController.php
class AuthController extends AbstractController
{
#[Route('/login', name: 'auth_login')]
public function login(): JsonResponse
{
return $this->json($this->getUser());
}
#[Route('/logout', name: 'auth_logout')]
public function logout(): JsonResponse
{
return $this->json(null);
}
}
OpenID Connect Back-Channel Logout
The OpenID Connect login feature added in Symfony 8.2 now supports the OpenID Connect Back-Channel Logout specification: when users log out from the identity provider, the provider sends a signed logout token to your application and Symfony logs those users out on their next request.
1 2 3 4 5 6 7 8
# config/packages/security.yaml
security:
firewalls:
main:
oidc_login:
# ...
backchannel_logout: true
# or: { path: /oidc/backchannel-logout, cache: cache.security, lifetime: 86400 }
Symfony verifies the signature and claims of the logout token and stores the
ended session in a cache pool. Then, register the logout URL as the
backchannel_logout_uri of your client in the identity provider. Keep in
mind that this only works in stateful firewalls and that the cache pool must be
shared by all your servers and must not evict items before they expire.