diff --git a/modules/admin/default-enable b/modules/admin/default-enable
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/modules/admin/lib/ConfigController.php b/modules/admin/lib/ConfigController.php
new file mode 100644
index 0000000000000000000000000000000000000000..0a06413b2c0282a135c2420e8b468bdafdce6986
--- /dev/null
+++ b/modules/admin/lib/ConfigController.php
@@ -0,0 +1,388 @@
+<?php
+
+namespace SimpleSAML\Module\admin;
+
+use SimpleSAML\HTTP\RunnableResponse;
+use SimpleSAML\Locale\Translate;
+use SimpleSAML\Utils\HTTP;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller class for the admin module.
+ *
+ * This class serves the configuration views available in the module.
+ *
+ * @package SimpleSAML\Module\admin
+ */
+class ConfigController
+{
+
+    const LATEST_VERSION_STATE_KEY = 'core:latest_simplesamlphp_version';
+    const RELEASES_API = 'https://api.github.com/repos/simplesamlphp/simplesamlphp/releases/latest';
+
+    /** @var \SimpleSAML\Configuration */
+    protected $config;
+
+    /** @var \SimpleSAML\Session */
+    protected $session;
+
+
+    /**
+     * ConfigController constructor.
+     *
+     * @param \SimpleSAML\Configuration $config The configuration to use.
+     * @param \SimpleSAML\Session $session The current user session.
+     */
+    public function __construct(\SimpleSAML\Configuration $config, \SimpleSAML\Session $session)
+    {
+        $this->config = $config;
+        $this->session = $session;
+    }
+
+
+    /**
+     * Display basic diagnostic information on hostname, port and protocol.
+     *
+     * @param Request $request The current request.
+     *
+     * @return \SimpleSAML\XHTML\Template
+     */
+    public function diagnostics(Request $request)
+    {
+        \SimpleSAML\Utils\Auth::requireAdmin();
+
+        $t = new \SimpleSAML\XHTML\Template($this->config, 'admin:diagnostics.twig');
+        $t->data = [
+            'remaining' => $this->session->getAuthData('admin', 'Expire') - time(),
+            'logouturl' => \SimpleSAML\Utils\Auth::getAdminLogoutURL(),
+            'items' => [
+                'HTTP_HOST' => [$request->getHost()],
+                'HTTPS' => $request->isSecure() ? ['on'] : [],
+                'SERVER_PROTOCOL' => [$request->getProtocolVersion()],
+                'getBaseURL()' => [HTTP::getBaseURL()],
+                'getSelfHost()' => [HTTP::getSelfHost()],
+                'getSelfHostWithNonStandardPort()' => [HTTP::getSelfHostWithNonStandardPort()],
+                'getSelfURLHost()' => [HTTP::getSelfURLHost()],
+                'getSelfURLNoQuery()' => [HTTP::getSelfURLNoQuery()],
+                'getSelfHostWithPath()' => [HTTP::getSelfHostWithPath()],
+                'getFirstPathElement()' => [HTTP::getFirstPathElement()],
+                'getSelfURL()' => [HTTP::getSelfURL()],
+            ],
+        ];
+        return $t;
+    }
+
+
+    /**
+     * Display the main admin page.
+     *
+     * @return \SimpleSAML\XHTML\Template
+     */
+    public function main()
+    {
+        \SimpleSAML\Utils\Auth::requireAdmin();
+
+        $t = new \SimpleSAML\XHTML\Template($this->config, 'admin:config.twig');
+        $t->data = [
+            'warnings' => $this->getWarnings(),
+            'directory' => $this->config->getBaseDir(),
+            'version' => $this->config->getVersion(),
+            'links' => [
+                [
+                    'href' => \SimpleSAML\Module::getModuleURL('admin/diagnostics'),
+                    'text' => Translate::noop('Diagnostics on hostname, port and protocol')
+                ],
+                [
+                    'href' => \SimpleSAML\Module::getModuleURL('admin/phpinfo'),
+                    'text' => Translate::noop('Information on your PHP installation')
+                ]
+            ],
+            'enablematrix' => [
+                'saml20idp' => $this->config->getBoolean('enable.saml20-idp', false),
+                'shib13idp' => $this->config->getBoolean('enable.shib13-idp', false),
+            ],
+            'funcmatrix' => $this->getPrerequisiteChecks(),
+            'logouturl' => \SimpleSAML\Utils\Auth::getAdminLogoutURL(),
+        ];
+
+        \SimpleSAML\Module::callHooks('configpage', $t);
+
+        return $t;
+    }
+
+
+    /**
+     * Display the output of phpinfo().
+     *
+     * @return RunnableResponse
+     */
+    public function phpinfo()
+    {
+        return new RunnableResponse('phpinfo');
+    }
+
+
+    /**
+     * @return array
+     */
+    protected function getPrerequisiteChecks()
+    {
+        $matrix = [
+            [
+                'required' => 'required',
+                'descr' => 'PHP version >= 5.5. You run: '.phpversion(),
+                'enabled' => version_compare(phpversion(), '5.5', '>=')
+            ]
+        ];
+        $store = $this->config->getString('store.type', '');
+
+        // check dependencies used via normal functions
+        $functions = [
+            'time' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('Date/Time Extension'),
+                ]
+            ],
+            'hash' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('Hashing function'),
+                ]
+            ],
+            'gzinflate' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('ZLib'),
+                ]
+            ],
+            'openssl_sign' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('OpenSSL'),
+                ]
+            ],
+            'dom_import_simplexml' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('XML DOM'),
+                ]
+            ],
+            'preg_match' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('Regular expression support'),
+                ]
+            ],
+            'json_decode' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('JSON support'),
+                ]
+            ],
+            'class_implements' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('Standard PHP library (SPL)'),
+                ]
+            ],
+            'mb_strlen' => [
+                'required' => 'required',
+                'descr' => [
+                    'required' => Translate::noop('Multibyte String extension'),
+                ]
+            ],
+            'curl_init' => [
+                'required' => $this->config->getBoolean('admin.checkforupdates', true) ? 'required' : 'optional',
+                'descr' => [
+                    'optional' => Translate::noop(
+                        'cURL (might be required by some modules)'
+                    ),
+                    'required' => Translate::noop(
+                        'cURL (required if automatic version checks are used, also by some modules)'
+                    ),
+                ]
+            ],
+            'session_start' => [
+                'required' => $store === 'phpsession' ? 'required' : 'optional',
+                'descr' => [
+                    'optional' => Translate::noop('Session extension (required if PHP extensions are used)'),
+                    'required' => Translate::noop('Session extension'),
+                ]
+            ],
+            'pdo_drivers' => [
+                'required' => $store === 'sql' ? 'required' : 'optional',
+                'descr' => [
+                    'optional' => Translate::noop('Session extension (required if PHP extensions are used)'),
+                    'required' => Translate::noop('Session extension'),
+                ]
+            ],
+            'ldap_bind' => [
+                'required' => \SimpleSAML\Module::isModuleEnabled('ldap') ? 'required' : 'optional',
+                'descr' => [
+                    'optional' => Translate::noop('LDAP extension (required if an LDAP backend is used)'),
+                    'required' => Translate::noop('LDAP extension'),
+                ]
+            ],
+            'radius_auth_open' => [
+                'required' => \SimpleSAML\Module::isModuleEnabled('radius') ? 'required' : 'optional',
+                'descr' => [
+                    'optional' => Translate::noop('Radius extension (required if a radius backend is used)'),
+                    'required' => Translate::noop('Radius extension'),
+                ]
+            ],
+        ];
+
+        foreach ($functions as $function => $description) {
+            $matrix[] = [
+                'required' => $description['required'],
+                'descr' => $description['descr'][$description['required']],
+                'enabled' => function_exists($function),
+            ];
+        }
+
+        // check object-oriented external libraries and extensions
+        $libs = [
+            [
+                'classes' => ['\Predis\Predis'],
+                'required' => $store === 'redis' ? 'required' : 'optional',
+                'descr' => [
+                    'optional' => Translate::noop('predis/predis (required if the redis data store is used'),
+                    'required' => Translate::noop('predis/predis library'),
+                ]
+            ],
+            [
+                'classes' => ['\Memcache', '\Memcached'],
+                'required' => $store === 'memcache' ? 'required' : 'optional',
+                'descr' => [
+                    'optional' => Translate::noop(
+                        'Memcache or Memcached extension (required if the memcache backend is used'
+                    ),
+                    'required' => Translate::noop('Memcache or Memcached extension'),
+                ]
+            ]
+        ];
+
+        foreach ($libs as $lib) {
+            $enabled = false;
+            foreach ($lib['classes'] as $class) {
+                $enabled |= class_exists($class);
+            }
+            $matrix[] = [
+                'required' => $lib['required'],
+                'descr' => $lib['descr'][$lib['required']],
+                'enabled' => $enabled,
+            ];
+        }
+
+        // perform some basic configuration checks
+        $matrix[] = [
+            'required' => 'optional',
+            'descr' => Translate::noop('The <code>technicalcontact_email</code> configuration option should be set'),
+            'enabled' => $this->config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org',
+        ];
+
+        $matrix[] = [
+            'required' => 'required',
+            'descr' => Translate::noop('The auth.adminpassword configuration option must be set'),
+            'enabled' => $this->config->getString('auth.adminpassword', '123') !== '123',
+        ];
+
+        return $matrix;
+    }
+
+
+    /**
+     * Compile a list of warnings about the current deployment.
+     *
+     * The returned array can contain either strings that can be translated directly, or arrays. If an element is an
+     * array, the first value in that array is a string that can be translated, and the second value will be a hashed
+     * array that contains the substitutions that must be applied to the translation, with its corresponding value. This
+     * can be used in twig like this, assuming an element called "e":
+     *
+     *     {{ e[0]|trans(e[1])|raw }}
+     *
+     * @return array
+     */
+    protected function getWarnings()
+    {
+        $warnings = [];
+
+        // make sure we're using HTTPS
+        if (!\SimpleSAML\Utils\HTTP::isHTTPS()) {
+            $warnings[] = Translate::noop(
+                '<strong>You are not using HTTPS</strong> to protect communications with your users. HTTP works fine '.
+                'for testing purposes, but in a production environment you should use HTTPS. <a '.
+                'href="https://simplesamlphp.org/docs/stable/simplesamlphp-maintenance">Read more about the '.
+                'maintenance of SimpleSAMLphp</a>.'
+            );
+        }
+
+        // make sure we have a secret salt set
+        if ($this->config->getValue('secretsalt') === 'defaultsecretsalt') {
+            $warnings[] = Translate::noop(
+                '<strong>The configuration uses the default secret salt</strong>. Make sure to modify the <code>'.
+                'secretsalt</code> option in the SimpleSAMLphp configuration in production environments. <a '.
+                'href="https://simplesamlphp.org/docs/stable/simplesamphp-install">Read more about the '.
+                'maintenance of SimpleSAMLphp</a>.'
+            );
+        }
+
+        // check for URL limitations
+        if (extension_loaded('suhosin')) {
+            $len = ini_get('suhosin.get.max_value_length');
+            if (empty($len) || $len < 2048) {
+                $warnings[] = Translate::noop(
+                    'The length of query parameters is limited by the PHP Suhosin extension. Please increase the '.
+                    '<code>suhosin.get.max_value_length</code> option in your php.ini to at least 2048 bytes.'
+                );
+            }
+        }
+        
+        /*
+         * Check for updates. Store the remote result in the session so we don't need to fetch it on every access to
+         * this page.
+         */
+        if ($this->config->getBoolean('admin.checkforupdates', true) && $this->config->getVersion() !== 'master') {
+            if (!function_exists('curl_init')) {
+                $warnings[] = Translate::noop(
+                    'The cURL PHP extension is missing. Cannot check for SimpleSAMLphp updates.'
+                );
+            } else {
+                $latest = $this->session->getData(self::LATEST_VERSION_STATE_KEY, "version");
+
+                if (!$latest) {
+                    $ch = curl_init(self::RELEASES_API);
+                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+                    curl_setopt($ch, CURLOPT_USERAGENT, 'SimpleSAMLphp');
+                    curl_setopt($ch, CURLOPT_TIMEOUT, 2);
+                    curl_setopt($ch, CURLOPT_PROXY, $this->config->getString('proxy', null));
+                    curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config->getValue('proxy.auth', null));
+                    $response = curl_exec($ch);
+
+                    if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) {
+                        $latest = json_decode($response, true);
+                        $this->session->setData(self::LATEST_VERSION_STATE_KEY, 'version', $latest);
+                    }
+                    curl_close($ch);
+                }
+
+
+                if ($latest && version_compare($this->config->getVersion(), ltrim($latest['tag_name'], 'v'), 'lt')) {
+                    $warnings[] = [
+                        Translate::noop(
+                            'You are running an outdated version of SimpleSAMLphp. Please update to <a href="'.
+                            '%latest%">the latest version</a> as soon as possible.'
+                        ),
+                            [
+                                '%latest%' => $latest['html_url']
+                            ]
+                    ];
+                }
+            }
+        }
+
+        return $warnings;
+    }
+}
diff --git a/modules/admin/routes.yaml b/modules/admin/routes.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e19374da6b73a94b139884daf5b1f801b9139d3d
--- /dev/null
+++ b/modules/admin/routes.yaml
@@ -0,0 +1,15 @@
+admin-main:
+    path:       /
+    defaults:   { _controller: 'SimpleSAML\Module\admin\ConfigController::main' }
+admin-diagnostics:
+    path:       /diagnostics
+    defaults:   { _controller: 'SimpleSAML\Module\admin\ConfigController::diagnostics' }
+admin-phpinfo:
+    path:       /phpinfo
+    defaults:   { _controller: 'SimpleSAML\Module\admin\ConfigController::phpinfo' }
+admin-test:
+    path:       /test/{as}
+    defaults:   { _controller: 'SimpleSAML\Module\admin\TestController::main', as: null }
+admin-fed:
+    path:       /federation
+    defaults:   { _controller: 'SimpleSAML\Module\admin\FederationController::main' }
diff --git a/modules/admin/templates/config.twig b/modules/admin/templates/config.twig
new file mode 100644
index 0000000000000000000000000000000000000000..58bbbbbbef5eba4df1f137c19e9536b4fddf4b27
--- /dev/null
+++ b/modules/admin/templates/config.twig
@@ -0,0 +1,60 @@
+{% set pagetitle = 'SimpleSAMLphp installation page'|trans %}
+{% set frontpage_section = 'main' %}
+{% extends "base.twig" %}
+
+{% block content %}
+    {%- include "@admin/includes/menu.twig" %}
+
+    {%- for key, warning in warnings %}
+      {%- if warning is iterable %}
+
+    <div class="message-box warning">{{ warning[0]|trans(warning[1])|raw }}</div>
+      {%- else %}
+
+    <div class="message-box warning">{{ warning|trans|raw }}</div>
+      {%- endif %}
+    {%- endfor %}
+
+    <br/>
+    <div><code class="simplesaml_version">{{ directory }} ({{ version }})</code></div>
+    <div class="enablebox mini">
+      <table>
+	      <tr class="{%- if enablematrix.saml20idp %}enabled{% else %}disabled{% endif -%}">
+          <td>SAML 2.0 IdP</td>
+          <td><i class="fa fa-{%- if enablematrix.saml20idp %}check{% else %}ban{% endif %}"></i></td>
+        </tr>
+	      <tr class="{%- if enablematrix.shib13idp %}enabled{% else %}disabled{% endif -%}">
+          <td>Shib 1.3 IdP</td>
+          <td><i class="fa fa-{%- if enablematrix.shib13idp %}check{% else %}ban{% endif %}"></i></td>
+        </tr>
+      </table>
+    </div>
+    <h2>{% trans %}Configuration{% endtrans %}</h2>
+    <ul>
+      {%- for key, link in links %}
+
+      <li><a href="{{ link.href }}">{{ link.text }}</a></li>
+      {%- endfor %}
+
+    </ul>
+    <h2>{% trans %}Checking your PHP installation{% endtrans %}</h2>
+    <div class="enablebox">
+        <table>
+        {%- for key, func in funcmatrix %}
+
+          <tr class="{%- if func.enabled %}enabled{% else %}disabled{% endif -%}">
+            <td><i class="fa fa-{%- if func.enabled %}check{% else %}ban{% endif -%}"></i></td>
+            <td>
+            {%- if func.required == 'required' %}
+              {%- trans %}required{% endtrans %}
+            {%- else %}
+              {%- trans %}optional{% endtrans %}
+            {%- endif -%}
+            </td>
+            <td>{{ func.descr|trans|raw }}</td>
+          </tr>
+        {%- endfor %}
+
+        </table>
+    </div>
+{% endblock %}
diff --git a/modules/admin/templates/includes/menu.twig b/modules/admin/templates/includes/menu.twig
new file mode 100644
index 0000000000000000000000000000000000000000..7400477dffae0928fc2cc3f65d90634d541d5d74
--- /dev/null
+++ b/modules/admin/templates/includes/menu.twig
@@ -0,0 +1,20 @@
+<div class="pure-g frontpage-menu">
+    <div class="pure-u-2-3">
+        <div class="pure-menu pure-menu-horizontal">
+            <ul class="pure-menu-list">
+                <li class="pure-menu-item{% if frontpage_section == "main" %} pure-menu-selected{% endif %}">
+                    <a href="/{{ baseurlpath }}module.php/admin/" class="pure-menu-link">Admin</a>
+                </li>
+                <li class="pure-menu-item{% if frontpage_section == "test" %} pure-menu-selected{% endif %}">
+                    <a href="/{{ baseurlpath }}module.php/admin/test" class="pure-menu-link">Test</a>
+                </li>
+                <li class="pure-menu-item{% if frontpage_section == "federation" %} pure-menu-selected{% endif %}">
+                    <a href="/{{ baseurlpath }}module.php/admin/federation" class="pure-menu-link">Federation</a>
+                </li>
+                <li class="pure-menu-item">
+                    <a href="{{ logouturl }}" class="pure-menu-link">Logout</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
diff --git a/www/admin/index.php b/www/admin/index.php
index 41e9c200af4a8218ad2d143ccd4dfe9f0ce14e5c..2541aa1d0198460667613b7bcc18fff9392f2fb0 100644
--- a/www/admin/index.php
+++ b/www/admin/index.php
@@ -2,6 +2,8 @@
 
 require_once('../_include.php');
 
+\SimpleSAML\Utils\HTTP::redirectTrustedURL(\SimpleSAML\Module::getModuleURL('admin/'));
+
 // Load SimpleSAMLphp configuration
 $config = \SimpleSAML\Configuration::getInstance();
 $session = \SimpleSAML\Session::getSessionFromRequest();