diff --git a/modules/exampleauth/lib/Auth/Process/RedirectTest.php b/modules/exampleauth/lib/Auth/Process/RedirectTest.php
index 34c93221251e3e3db553ef66a335436129922068..3167fb6866e094ffeef57950a5c18b0232349aa6 100644
--- a/modules/exampleauth/lib/Auth/Process/RedirectTest.php
+++ b/modules/exampleauth/lib/Auth/Process/RedirectTest.php
@@ -13,7 +13,7 @@ use SimpleSAML\Utils;
  * A simple processing filter for testing that redirection works as it should.
  *
  */
-class RedirectTest extends \SimpleSAML\Auth\ProcessingFilter
+class RedirectTest extends Auth\ProcessingFilter
 {
     /**
      * Initialize processing of the redirect test.
@@ -29,7 +29,7 @@ class RedirectTest extends \SimpleSAML\Auth\ProcessingFilter
 
         // Save state and redirect
         $id = Auth\State::saveState($state, 'exampleauth:redirectfilter-test');
-        $url = Module::getModuleURL('exampleauth/redirecttest.php');
+        $url = Module::getModuleURL('exampleauth/redirecttest');
 
         $httpUtils = new Utils\HTTP();
         $httpUtils->redirectTrustedURL($url, ['StateId' => $id]);
diff --git a/modules/exampleauth/lib/Auth/Source/External.php b/modules/exampleauth/lib/Auth/Source/External.php
index 7ce4d78a6f0c0ff59bf39032397dddf9fc63d17d..f44d83aa0e08361a51ed4a3e0e44da39ac6ed484 100644
--- a/modules/exampleauth/lib/Auth/Source/External.php
+++ b/modules/exampleauth/lib/Auth/Source/External.php
@@ -9,6 +9,7 @@ use SimpleSAML\Auth;
 use SimpleSAML\Error;
 use SimpleSAML\Module;
 use SimpleSAML\Utils;
+use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
 
 /**
  * Example external authentication source.
@@ -19,9 +20,8 @@ use SimpleSAML\Utils;
  * To adapt this to your own web site, you should:
  * 1. Create your own module directory.
  * 2. Enable to module in the config by adding '<module-dir>' => true to the $config['module.enable'] array.
- * 3. Copy this file and modules/exampleauth/www/resume.php to their corresponding
- *    location in the new module.
- * 4. Replace all occurrences of "exampleauth" in this file and in resume.php with the name of your module.
+ * 3. Copy this file to its corresponding location in the new module.
+ * 4. Replace all occurrences of "exampleauth" in this file with the name of your module.
  * 5. Adapt the getUser()-function, the authenticate()-function and the logout()-function to your site.
  * 6. Add an entry in config/authsources.php referencing your module. E.g.:
  *        'myauth' => [
@@ -65,13 +65,12 @@ class External extends Auth\Source
          * stored in the users PHP session, but this could be replaced
          * with anything.
          */
-
-        if (!session_id()) {
-            // session_start not called before. Do it here
-            session_start();
+        $session = new SymfonySession();
+        if (!$session->getId()) {
+            $session->start();
         }
 
-        if (!isset($_SESSION['uid'])) {
+        if (!$session->has('uid')) {
             // The user isn't authenticated
             return null;
         }
@@ -81,16 +80,15 @@ class External extends Auth\Source
          * Note that all attributes in SimpleSAMLphp are multivalued, so we need
          * to store them as arrays.
          */
-
         $attributes = [
-            'uid' => [$_SESSION['uid']],
-            'displayName' => [$_SESSION['name']],
-            'mail' => [$_SESSION['mail']],
+            'uid' => [$session->get('uid')],
+            'displayName' => [$session->get('name')],
+            'mail' => [$session->get('mail')],
         ];
 
         // Here we generate a multivalued attribute based on the account type
         $attributes['eduPersonAffiliation'] = [
-            $_SESSION['type'], /* In this example, either 'student' or 'employee'. */
+            $session->get('type'), /* In this example, either 'student' or 'employee'. */
             'member',
         ];
 
@@ -148,7 +146,7 @@ class External extends Auth\Source
          * We assume that whatever authentication page we send the user to has an
          * option to return the user to a specific page afterwards.
          */
-        $returnTo = Module::getModuleURL('exampleauth/resume.php', [
+        $returnTo = Module::getModuleURL('exampleauth/resume', [
             'State' => $stateId,
         ]);
 
@@ -159,7 +157,7 @@ class External extends Auth\Source
          * is also part of this module, but in a real example, this would likely be
          * the absolute URL of the login page for the site.
          */
-        $authPage = Module::getModuleURL('exampleauth/authpage.php');
+        $authPage = Module::getModuleURL('exampleauth/authpage');
 
         /*
          * The redirect to the authentication page.
@@ -185,16 +183,18 @@ class External extends Auth\Source
      * This function resumes the authentication process after the user has
      * entered his or her credentials.
      *
+     * @param \Symfony\Component\HttpFoundation\Request $request
+     *
      * @throws \SimpleSAML\Error\BadRequest
      * @throws \SimpleSAML\Error\Exception
      */
-    public static function resume(): void
+    public static function resume(Request $request): void
     {
         /*
          * First we need to restore the $state-array. We should have the identifier for
          * it in the 'State' request parameter.
          */
-        if (!isset($_REQUEST['State'])) {
+        if (!$request->has('State')) {
             throw new Error\BadRequest('Missing "State" parameter.');
         }
 
@@ -203,7 +203,7 @@ class External extends Auth\Source
          * match the string we used in the saveState-call above.
          */
         /** @var array $state */
-        $state = Auth\State::loadState($_REQUEST['State'], 'exampleauth:External');
+        $state = Auth\State::loadState($request->get('State'), 'exampleauth:External');
 
         /*
          * Now we have the $state-array, and can use it to locate the authentication
@@ -266,15 +266,12 @@ class External extends Auth\Source
      */
     public function logout(array &$state): void
     {
-        if (!session_id()) {
-            // session_start not called before. Do it here
-            session_start();
+        $session = new SymfonySession();
+        if (!$session->getId()) {
+            $session->start();
         }
 
-        /*
-         * In this example we simply remove the 'uid' from the session.
-         */
-        unset($_SESSION['uid']);
+        $session->clear();
 
         /*
          * If we need to do a redirect to a different page, we could do this
diff --git a/modules/exampleauth/lib/Controller/ExampleAuth.php b/modules/exampleauth/lib/Controller/ExampleAuth.php
new file mode 100644
index 0000000000000000000000000000000000000000..0072a1a66268dc64fe6d53089fd6af862ae926dc
--- /dev/null
+++ b/modules/exampleauth/lib/Controller/ExampleAuth.php
@@ -0,0 +1,213 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\exampleauth\Controller;
+
+use SimpleSAML\Auth;
+use SimpleSAML\Configuration;
+use SimpleSAML\Error;
+use SimpleSAML\HTTP\RunnableResponse;
+use SimpleSAML\Module\exampleauth\Auth\Source\External;
+use SimpleSAML\Session;
+use SimpleSAML\Utils;
+use SimpleSAML\XHTML\Template;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
+
+use function array_key_exists;
+use function preg_match;
+use function session_id;
+use function session_start;
+use function urldecode;
+
+/**
+ * Controller class for the exampleauth module.
+ *
+ * This class serves the different views available in the module.
+ *
+ * @package simplesamlphp/simplesamlphp
+ */
+class ExampleAuth
+{
+    /** @var \SimpleSAML\Configuration */
+    protected Configuration $config;
+
+    /** @var \SimpleSAML\Session */
+    protected Session $session;
+
+    /**
+     * @var \SimpleSAML\Auth\State|string
+     * @psalm-var \SimpleSAML\Auth\State|class-string
+     */
+    protected $authState = Auth\State::class;
+
+
+    /**
+     * Controller constructor.
+     *
+     * It initializes the global configuration and session for the controllers implemented here.
+     *
+     * @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
+     * @param \SimpleSAML\Session $session The session to use by the controllers.
+     *
+     * @throws \Exception
+     */
+    public function __construct(
+        Configuration $config,
+        Session $session
+    ) {
+        $this->config = $config;
+        $this->session = $session;
+    }
+
+
+    /**
+     * Inject the \SimpleSAML\Auth\State dependency.
+     *
+     * @param \SimpleSAML\Auth\State $authState
+     */
+    public function setAuthState(Auth\State $authState): void
+    {
+        $this->authState = $authState;
+    }
+
+
+    /**
+     * Auth testpage.
+     *
+     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
+     *
+     * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse
+     */
+    public function authpage(Request $request)
+    {
+        /**
+         * This page serves as a dummy login page.
+         *
+         * Note that we don't actually validate the user in this example. This page
+         * just serves to make the example work out of the box.
+         */
+        $returnTo = $request->get('ReturnTo');
+        if ($returnTo === null) {
+            throw new Error\Exception('Missing ReturnTo parameter.');
+        }
+
+        $httpUtils = new Utils\HTTP();
+        $returnTo = $httpUtils->checkURLAllowed($returnTo);
+
+        /**
+         * The following piece of code would never be found in a real authentication page. Its
+         * purpose in this example is to make this example safer in the case where the
+         * administrator of the IdP leaves the exampleauth-module enabled in a production
+         * environment.
+         *
+         * What we do here is to extract the $state-array identifier, and check that it belongs to
+         * the exampleauth:External process.
+         */
+        if (!preg_match('@State=(.*)@', $returnTo, $matches)) {
+            throw new Error\Exception('Invalid ReturnTo URL for this example.');
+        }
+
+        /**
+         * The loadState-function will not return if the second parameter does not
+         * match the parameter passed to saveState, so by now we know that we arrived here
+         * through the exampleauth:External authentication page.
+         */
+        $this->authState::loadState(urldecode($matches[1]), 'exampleauth:External');
+
+        // our list of users.
+        $users = [
+            'student' => [
+                'password' => 'student',
+                'uid' => 'student',
+                'name' => 'Student Name',
+                'mail' => 'somestudent@example.org',
+                'type' => 'student',
+            ],
+            'admin' => [
+                'password' => 'admin',
+                'uid' => 'admin',
+                'name' => 'Admin Name',
+                'mail' => 'someadmin@example.org',
+                'type' => 'employee',
+            ],
+        ];
+
+        // time to handle login responses; since this is a dummy example, we accept any data
+        $badUserPass = false;
+        if ($request->getMethod() === 'POST') {
+            $username = $request->get('username');
+            $password = $request->get('password');
+
+            if (!isset($users[$username]) || $users[$username]['password'] !== $password) {
+                $badUserPass = true;
+            } else {
+                $user = $users[$username];
+
+                $session = new SymfonySession();
+                if (!$session->getId()) {
+                    $session->start();
+                }
+
+                $session->set('uid', $user['uid']);
+                $session->set('name', $user['name']);
+                $session->set('mail', $user['mail']);
+                $session->set('type', $user['type']);
+
+                return new RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$returnTo]);
+            }
+        }
+
+        // if we get this far, we need to show the login page to the user
+        $t = new Template($this->config, 'exampleauth:authenticate.twig');
+        $t->data['badUserPass'] = $badUserPass;
+        $t->data['returnTo'] = $returnTo;
+
+        return $t;
+    }
+
+
+    /**
+     * Redirect testpage.
+     *
+     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
+     *
+     * @return \SimpleSAML\HTTP\RunnableResponse
+     */
+    public function redirecttest(Request $request): RunnableResponse
+    {
+        /**
+         * Request handler for redirect filter test.
+         */
+        $stateId = $request->get('StateId');
+        if ($stateId === null) {
+            throw new Error\BadRequest('Missing required StateId query parameter.');
+        }
+
+        /** @var array $state */
+        $state = $this->authState::loadState($stateId, 'exampleauth:redirectfilter-test');
+        $state['Attributes']['RedirectTest2'] = ['OK'];
+
+        return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]);
+    }
+
+
+    /**
+     * Resume testpage.
+     *
+     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
+     *
+     * @return \SimpleSAML\HTTP\RunnableResponse
+     */
+    public function resume(Request $request): RunnableResponse
+    {
+        /**
+         * This page serves as the point where the user's authentication
+         * process is resumed after the login page.
+         *
+         * It simply passes control back to the class.
+         */
+        return new RunnableResponse([External::class, 'resume'], [$request]);
+    }
+}
diff --git a/modules/exampleauth/routing/routes/routes.yml b/modules/exampleauth/routing/routes/routes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a4b52108f8f0aca5f229338f388727eec1892835
--- /dev/null
+++ b/modules/exampleauth/routing/routes/routes.yml
@@ -0,0 +1,9 @@
+exampleauth-authpage:
+    path:       /authpage
+    defaults:   { _controller: 'SimpleSAML\Module\exampleauth\Controller\ExampleAuth::authpage' }
+exampleauth-redirecttest:
+    path:       /redirecttest
+    defaults:   { _controller: 'SimpleSAML\Module\exampleauth\Controller\ExampleAuth::redirecttest' }
+exampleauth-resume:
+    path:       /resume
+    defaults:   { _controller: 'SimpleSAML\Module\exampleauth\Controller\ExampleAuth::resume' }
diff --git a/modules/exampleauth/www/authpage.php b/modules/exampleauth/www/authpage.php
deleted file mode 100644
index c1ab81bfc63f259f8c4f6e40d9ec9b7bbfff958f..0000000000000000000000000000000000000000
--- a/modules/exampleauth/www/authpage.php
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-
-/**
- * This page serves as a dummy login page.
- *
- * Note that we don't actually validate the user in this example. This page
- * just serves to make the example work out of the box.
- *
- * @package SimpleSAMLphp
- */
-
-if (!isset($_REQUEST['ReturnTo'])) {
-    die('Missing ReturnTo parameter.');
-}
-
-$httpUtils = new \SimpleSAML\Utils\HTTP();
-$returnTo = $httpUtils->checkURLAllowed($_REQUEST['ReturnTo']);
-
-/**
- * The following piece of code would never be found in a real authentication page. Its
- * purpose in this example is to make this example safer in the case where the
- * administrator of the IdP leaves the exampleauth-module enabled in a production
- * environment.
- *
- * What we do here is to extract the $state-array identifier, and check that it belongs to
- * the exampleauth:External process.
- */
-if (!preg_match('@State=(.*)@', $returnTo, $matches)) {
-    die('Invalid ReturnTo URL for this example.');
-}
-
-/**
- * The loadState-function will not return if the second parameter does not
- * match the parameter passed to saveState, so by now we know that we arrived here
- * through the exampleauth:External authentication page.
- */
-\SimpleSAML\Auth\State::loadState(urldecode($matches[1]), 'exampleauth:External');
-
-// our list of users.
-$users = [
-    'student' => [
-        'password' => 'student',
-        'uid' => 'student',
-        'name' => 'Student Name',
-        'mail' => 'somestudent@example.org',
-        'type' => 'student',
-    ],
-    'admin' => [
-        'password' => 'admin',
-        'uid' => 'admin',
-        'name' => 'Admin Name',
-        'mail' => 'someadmin@example.org',
-        'type' => 'employee',
-    ],
-];
-
-// time to handle login responses; since this is a dummy example, we accept any data
-$badUserPass = false;
-if ($_SERVER['REQUEST_METHOD'] === 'POST') {
-    $username = (string) $_REQUEST['username'];
-    $password = (string) $_REQUEST['password'];
-
-    if (!isset($users[$username]) || $users[$username]['password'] !== $password) {
-        $badUserPass = true;
-    } else {
-        $user = $users[$username];
-
-        if (!session_id()) {
-            // session_start not called before. Do it here.
-            session_start();
-        }
-
-        $_SESSION['uid'] = $user['uid'];
-        $_SESSION['name'] = $user['name'];
-        $_SESSION['mail'] = $user['mail'];
-        $_SESSION['type'] = $user['type'];
-
-        $httpUtils->redirectTrustedURL($returnTo);
-    }
-}
-
-// if we get this far, we need to show the login page to the user
-$config = \SimpleSAML\Configuration::getInstance();
-$t = new \SimpleSAML\XHTML\Template($config, 'exampleauth:authenticate.twig');
-$t->data['badUserPass'] = $badUserPass;
-$t->data['returnTo'] = $returnTo;
-$t->send();
diff --git a/modules/exampleauth/www/redirecttest.php b/modules/exampleauth/www/redirecttest.php
deleted file mode 100644
index 373c8527f400bff23ea506f647f1f5fa8718f554..0000000000000000000000000000000000000000
--- a/modules/exampleauth/www/redirecttest.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-/**
- * Request handler for redirect filter test.
- *
- * @package SimpleSAMLphp
- */
-
-if (!array_key_exists('StateId', $_REQUEST)) {
-    throw new \SimpleSAML\Error\BadRequest('Missing required StateId query parameter.');
-}
-
-/** @var array $state */
-$state = \SimpleSAML\Auth\State::loadState($_REQUEST['StateId'], 'exampleauth:redirectfilter-test');
-
-$state['Attributes']['RedirectTest2'] = ['OK'];
-
-\SimpleSAML\Auth\ProcessingChain::resumeProcessing($state);
diff --git a/modules/exampleauth/www/resume.php b/modules/exampleauth/www/resume.php
deleted file mode 100644
index 192c13a20dceb45230de0044c7cf34a982f0864c..0000000000000000000000000000000000000000
--- a/modules/exampleauth/www/resume.php
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-/**
- * This page serves as the point where the user's authentication
- * process is resumed after the login page.
- *
- * It simply passes control back to the class.
- *
- * @package SimpleSAMLphp
- */
-
-namespace SimpleSAML\Module\exampleauth\Auth\Source;
-
-External::resume();
diff --git a/modules/saml/lib/Auth/Process/SQLPersistentNameID.php b/modules/saml/lib/Auth/Process/SQLPersistentNameID.php
index 60e8a0223335979768ba38a82fe34c908f7887d9..d166f7533229e13105e0316729bd0714a2d8a664 100644
--- a/modules/saml/lib/Auth/Process/SQLPersistentNameID.php
+++ b/modules/saml/lib/Auth/Process/SQLPersistentNameID.php
@@ -62,7 +62,7 @@ class SQLPersistentNameID extends BaseNameIDGenerator
      *
      * @throws \SimpleSAML\Error\Exception If the 'attribute' option is not specified.
      */
-    public function __construct(array $config, $reserved)
+    public function __construct(array &$config, $reserved)
     {
         parent::__construct($config, $reserved);
 
diff --git a/tests/modules/exampleauth/lib/Controller/ExampleAuthTest.php b/tests/modules/exampleauth/lib/Controller/ExampleAuthTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3772184d900b4c32c7498cb93d84db355d283028
--- /dev/null
+++ b/tests/modules/exampleauth/lib/Controller/ExampleAuthTest.php
@@ -0,0 +1,245 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Test\Module\exampleauth\Controller;
+
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Auth;
+use SimpleSAML\Configuration;
+use SimpleSAML\Error;
+use SimpleSAML\HTTP\RunnableResponse;
+use SimpleSAML\Module\exampleauth\Controller;
+use SimpleSAML\Session;
+use SimpleSAML\XHTML\Template;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Set of tests for the controllers in the "exampleauth" module.
+ *
+ * @covers \SimpleSAML\Module\exampleauth\Controller\ExampleAuth
+ */
+class ExampleAuthTest extends TestCase
+{
+    /** @var \SimpleSAML\Configuration */
+    protected Configuration $config;
+
+    /** @var \SimpleSAML\Session */
+    protected Session $session;
+
+
+    /**
+     * Set up for each test.
+     */
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        $this->config = Configuration::loadFromArray(
+            [
+                'module.enable' => ['exampleauth' => true],
+            ],
+            '[ARRAY]',
+            'simplesaml'
+        );
+
+        $this->session = Session::getSessionFromRequest();
+
+        Configuration::setPreLoadedConfig($this->config, 'config.php');
+    }
+
+
+    /**
+     * Test that accessing the authpage-endpoint without ReturnTo parameter throws an exception
+     *
+     * @return void
+     */
+    public function testAuthpageNoReturnTo(): void
+    {
+        $request = Request::create(
+            '/authpage',
+            'GET',
+            ['NoReturnTo' => 'Limbo'],
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+
+        $this->expectException(Error\Exception::class);
+        $this->expectExceptionMessage('Missing ReturnTo parameter.');
+
+        $c->authpage($request);
+    }
+
+
+    /**
+     * Test that accessing the authpage-endpoint without a valid ReturnTo parameter throws an exception
+     *
+     * @return void
+     */
+    public function testAuthpageInvalidReturnTo(): void
+    {
+        $request = Request::create(
+            '/authpage',
+            'GET',
+            ['ReturnTo' => 'SomeBogusValue'],
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+
+        $this->expectException(Error\Exception::class);
+        $this->expectExceptionMessage('Invalid ReturnTo URL for this example.');
+
+        $c->authpage($request);
+    }
+
+
+    /**
+     * Test that accessing the authpage-endpoint using GET-method show a login-screen
+     *
+     * @return void
+     */
+    public function testAuthpageGetMethod(): void
+    {
+        $request = Request::create(
+            '/authpage',
+            'GET',
+            ['ReturnTo' => 'State=/'],
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+        $c->setAuthState(new class () extends Auth\State {
+            public static function loadState(string $id, string $stage, bool $allowMissing = false): ?array
+            {
+                return [];
+            }
+        });
+
+        $response = $c->authpage($request);
+        $this->assertTrue($response->isSuccessful());
+        $this->assertInstanceOf(Template::class, $response);
+    }
+
+
+    /**
+     * Test that accessing the authpage-endpoint using POST-method and using the correct password triggers a redirect
+     *
+     * @return void
+     */
+    public function testAuthpagePostMethodCorrectPassword(): void
+    {
+        $this->markTestSkipped('Needs debugging');
+
+        $request = Request::create(
+            '/authpage',
+            'POST',
+            ['ReturnTo' => 'State=/', 'username' => 'student', 'password' => 'student'],
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+        $c->setAuthState(new class () extends Auth\State {
+            public static function loadState(string $id, string $stage, bool $allowMissing = false): ?array
+            {
+                return [];
+            }
+        });
+
+        $response = $c->authpage($request);
+        $this->assertTrue($response->isSuccessful());
+        $this->assertInstanceOf(RunnableResponse::class, $response);
+    }
+
+
+    /**
+     * Test that accessing the authpage-endpoint using POST-method and an incorrect password shows the login-screen again
+     *
+     * @return void
+     */
+    public function testAuthpagePostMethodIncorrectPassword(): void
+    {
+        $request = Request::create(
+            '/authpage',
+            'POST',
+            ['ReturnTo' => 'State=/', 'username' => 'user', 'password' => 'something stupid'],
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+        $c->setAuthState(new class () extends Auth\State {
+            public static function loadState(string $id, string $stage, bool $allowMissing = false): ?array
+            {
+                return [];
+            }
+        });
+
+        $response = $c->authpage($request);
+        $this->assertTrue($response->isSuccessful());
+        $this->assertInstanceOf(Template::class, $response);
+    }
+
+
+    /**
+     * Test that accessing the resume-endpoint leads to a redirect
+     *
+     * @return void
+     */
+    public function testResume(): void
+    {
+        $request = Request::create(
+            '/resume',
+            'GET',
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+
+        $response = $c->resume($request);
+        $this->assertTrue($response->isSuccessful());
+        $this->assertInstanceOf(RunnableResponse::class, $response);
+    }
+
+
+    /**
+     * Test that accessing the redirecttest-endpoint leads to a redirect
+     *
+     * @return void
+     */
+    public function testRedirect(): void
+    {
+        $request = Request::create(
+            '/redirecttest',
+            'GET',
+            ['StateId' => 'someState']
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+        $c->setAuthState(new class () extends Auth\State {
+            public static function loadState(string $id, string $stage, bool $allowMissing = false): ?array
+            {
+                return [];
+            }
+        });
+
+        $response = $c->redirecttest($request);
+        $this->assertTrue($response->isSuccessful());
+        $this->assertInstanceOf(RunnableResponse::class, $response);
+    }
+
+
+    /**
+     * Test that accessing the redirecttest-endpoint without StateId leads to an exception
+     *
+     * @return void
+     */
+    public function testRedirectMissingStateId(): void
+    {
+        $request = Request::create(
+            '/redirecttest',
+            'GET',
+        );
+
+        $c = new Controller\ExampleAuth($this->config, $this->session);
+
+        $this->expectException(Error\BadRequest::class);
+        $this->expectExceptionMessage('Missing required StateId query parameter.');
+
+        $c->redirecttest($request);
+    }
+}