From b7c34fba8dfaa456bf32fa71a25b787d207624b0 Mon Sep 17 00:00:00 2001 From: Kay Joosten Date: Mon, 28 Sep 2026 16:11:41 +0200 Subject: [PATCH] Preserve deeplink target after SAML login # If applied, this commit will... Make the profile app redirect users back to the page they originally requested after logging in via SAML, instead of always sending them to the homepage. # Why is this change needed? Prior to this change, browsing to a specific page (e.g. /attribute-support) while unauthenticated, logging in via WAYF/the IdP, and returning through the SAML ACS endpoint always landed the user on "/" instead of the page they originally asked for. The SamlAuthenticator is registered in security.yaml as a Symfony "custom_authenticator" rather than through one of Symfony's built-in authenticator factories (e.g. form_login). Symfony only calls AuthenticationSuccessHandler::setFirewallName() automatically for its own built-in factories, so the firewall name was never set on our SuccessHandler. Without a firewall name, DefaultAuthenticationSuccessHandler is unable to look up the deeplink that was stored in the session before authentication started, and always falls back to the default target path. # How does it address the issue? This change explicitly configures the SuccessHandler service with the "saml_based" firewall name, so the URL that was stored in the session before login is used again as the post-login redirect target. Tests covering the wiring and the redirect behavior, with and without a stored deeplink, are included. # Provide links to any relevant tickets, articles or other resources https://github.com/OpenConext/OpenConext-profile/issues/325 --- CHANGELOG.md | 3 +- config/services.yaml | 8 ++ .../SamlAuthenticationSuccessHandlerTest.php | 89 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/OpenConext/ProfileBundle/Tests/Security/SamlAuthenticationSuccessHandlerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f1ff8db92..3cde3a9ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ -# Next release +# UNRELEASED * Upgrade the runtime and build toolchain to PHP 8.5 and Node.js 24. * Refresh the CI Docker base image plus Composer and npm dependencies for the PHP 8.5 build. +* Preserve the originally requested deeplink after SAML login instead of redirecting to the homepage #325 # 4.1.4 * Update saml2 library. diff --git a/config/services.yaml b/config/services.yaml index 37aa313e3..55da17f50 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -32,6 +32,14 @@ services: surfnet_saml.saml_provider: class: OpenConext\ProfileBundle\Security\Authentication\Provider\SamlProvider + Surfnet\SamlBundle\Security\Authentication\Handler\SuccessHandler: + arguments: + - '@security.http_utils' + - [] + - '@logger' + calls: + - setFirewallName: [ 'saml_based' ] + Psr\Log\LoggerInterface: alias: 'monolog.logger' public: true diff --git a/src/OpenConext/ProfileBundle/Tests/Security/SamlAuthenticationSuccessHandlerTest.php b/src/OpenConext/ProfileBundle/Tests/Security/SamlAuthenticationSuccessHandlerTest.php new file mode 100644 index 000000000..f1e6861f7 --- /dev/null +++ b/src/OpenConext/ProfileBundle/Tests/Security/SamlAuthenticationSuccessHandlerTest.php @@ -0,0 +1,89 @@ +get(SuccessHandler::class); + + $this->assertSame('saml_based', $successHandler->getFirewallName()); + } + + public function test_a_deeplink_stored_before_login_is_used_as_the_post_login_redirect_target(): void + { + self::bootKernel(); + + $deepLink = 'https://profile.surfconext.nl/attribute-support'; + + $session = new Session(new MockArraySessionStorage()); + $session->set('_security.saml_based.target_path', $deepLink); + + $request = Request::create('https://profile.surfconext.nl/saml/acs', 'POST'); + $request->setSession($session); + + /** @var SuccessHandler $successHandler */ + $successHandler = self::getContainer()->get(SuccessHandler::class); + + $token = $this->createMock(TokenInterface::class); + + $response = $successHandler->onAuthenticationSuccess($request, $token); + + $this->assertNotNull($response); + $this->assertTrue($response->isRedirect($deepLink)); + } + + public function test_without_a_stored_deeplink_the_default_target_path_is_used(): void + { + self::bootKernel(); + + $session = new Session(new MockArraySessionStorage()); + + $request = Request::create('https://profile.surfconext.nl/saml/acs', 'POST'); + $request->setSession($session); + + /** @var SuccessHandler $successHandler */ + $successHandler = self::getContainer()->get(SuccessHandler::class); + + $token = $this->createMock(TokenInterface::class); + + $response = $successHandler->onAuthenticationSuccess($request, $token); + + $this->assertNotNull($response); + $this->assertTrue($response->isRedirect('https://profile.surfconext.nl/')); + } +}