diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php
index 09daaa9a122c755b80cb127f0162fb3f184da24b..22310d180bda124b45a2025ad13b7af221275295 100644
--- a/lib/SimpleSAML/Auth/State.php
+++ b/lib/SimpleSAML/Auth/State.php
@@ -378,6 +378,7 @@ class State
              */
             throw $exception;
         }
+        throw new \Exception(); // This should never happen
     }
 
 
diff --git a/modules/admin/lib/ConfigController.php b/modules/admin/lib/ConfigController.php
index 97786c4ba0bf168af1dfea3a1d3783670de180a7..8d2c0758f40ee604a80be7a294f6bea2c4af2675 100644
--- a/modules/admin/lib/ConfigController.php
+++ b/modules/admin/lib/ConfigController.php
@@ -363,7 +363,7 @@ class ConfigController
                 );
             }
         }
-        
+
         /*
          * Check for updates. Store the remote result in the session so we don't need to fetch it on every access to
          * this page.
@@ -385,7 +385,8 @@ class ConfigController
                     curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config->getValue('proxy.auth', null));
                     $response = curl_exec($ch);
 
-                    if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) {
+                    if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) === 200) {
+                        /** @psalm-suppress InvalidScalarArgument */
                         $latest = json_decode($response, true);
                         $this->session->setData(self::LATEST_VERSION_STATE_KEY, 'version', $latest);
                     }
diff --git a/modules/admin/lib/FederationController.php b/modules/admin/lib/FederationController.php
index 1929504c332f9acc1396a70dd151d5f45c9f9797..17d5744c0d13decdd5456f33eb81b95f12217076 100644
--- a/modules/admin/lib/FederationController.php
+++ b/modules/admin/lib/FederationController.php
@@ -227,7 +227,7 @@ class FederationController
                             $this->mdHandler->getMetaDataCurrentEntityID('shib13-idp-hosted')
                         );
                 }
-                
+
                 foreach ($shib13entities as $index => $entity) {
                     $builder = new SAMLBuilder($entity['entityid']);
                     $builder->addMetadataIdP11($entity['metadata_array']);
diff --git a/modules/admin/lib/TestController.php b/modules/admin/lib/TestController.php
index 957aca6c648d5cdb927fe3ecdb4eb25d8044b21a..ee81541aacb850b9b8a05cab5c739c8a7877a47f 100644
--- a/modules/admin/lib/TestController.php
+++ b/modules/admin/lib/TestController.php
@@ -69,6 +69,7 @@ class TestController
                 $authsource->logout($this->config->getBasePath().'logout.php');
             } elseif (!is_null($request->query->get(Auth\State::EXCEPTION_PARAM))) {
                 // This is just a simple example of an error
+                /** @var array $state */
                 $state = Auth\State::loadExceptionState();
                 assert(array_key_exists(Auth\State::EXCEPTION_DATA, $state));
                 throw $state[Auth\State::EXCEPTION_DATA];
@@ -114,16 +115,21 @@ class TestController
      */
     private function getNameIDHTML(Template $t, NameID $nameId)
     {
+        $translator = $t->getTranslator();
         $result = '';
         if ($nameId->getValue() === null) {
-            $list = ["NameID" => [$t->t('{status:subject_notset}')]];
-            $result .= "<p>NameID: <span class=\"notset\">".$t->t('{status:subject_notset}')."</span></p>";
+            $list = ["NameID" => [$translator->t('{status:subject_notset}')]];
+            /** @var string $notset */
+            $notset = $translator->t('{status:subject_notset}');
+            $result .= "<p>NameID: <span class=\"notset\">".$notset."</span></p>";
         } else {
             $list = [
                 "NameId" => [$nameId->getValue()],
             ];
             if ($nameId->getFormat() !== null) {
-                $list[$t->t('{status:subject_format}')] = [$nameId->getFormat()];
+                /** @var string $format */
+                $format = $translator->t('{status:subject_format}');
+                $list[$format] = [$nameId->getFormat()];
             }
             if ($nameId->getNameQualifier() !== null) {
                 $list['NameQualifier'] = [$nameId->getNameQualifier()];
@@ -226,7 +232,7 @@ class TestController
         if (is_array($attr) && count($attr) > 1) {
             $str = '<ul>';
             foreach ($attr as $value) {
-                $str .= '<li>'.htmlspecialchars($attr).'</li>';
+                $str .= '<li>'.htmlspecialchars(strval($attr)).'</li>';
             }
             $str .= '</ul>';
             return $str;
@@ -266,7 +272,9 @@ class TestController
             'NameID' => [$nameID->getValue()],
         ];
         if ($nameID->getFormat() !== null) {
-            $eptid[$t->t('{status:subject_format}')] = [$nameID->getFormat()];
+            /** @var string $format */
+            $format = $t->t('{status:subject_format}');
+            $eptid[$format] = [$nameID->getFormat()];
         }
         if ($nameID->getNameQualifier() !== null) {
             $eptid['NameQualifier'] = [$nameID->getNameQualifier()];
diff --git a/modules/core/lib/Auth/Process/AttributeAdd.php b/modules/core/lib/Auth/Process/AttributeAdd.php
index b568998866cb160270989d141257ab1ac077fd18..06fdb41bdd2af46e5305f80d4112918ff6700327 100644
--- a/modules/core/lib/Auth/Process/AttributeAdd.php
+++ b/modules/core/lib/Auth/Process/AttributeAdd.php
@@ -14,16 +14,19 @@ class AttributeAdd extends \SimpleSAML\Auth\ProcessingFilter
 {
     /**
      * Flag which indicates wheter this filter should append new values or replace old values.
+     * @var bool
      */
     private $replace = false;
 
     /**
      * Attributes which should be added/appended.
      *
-     * Assiciative array of arrays.
+     * Associative array of arrays.
+     * @var array
      */
     private $attributes = [];
 
+
     /**
      * Initialize this filter.
      *
@@ -59,6 +62,7 @@ class AttributeAdd extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Apply filter to add or replace attributes.
      *
diff --git a/modules/core/lib/Auth/Process/AttributeAlter.php b/modules/core/lib/Auth/Process/AttributeAlter.php
index a522facfc65c86fe194d7561b863e1f7d46e510e..5212e6938728332dde270c49524095216474d087 100644
--- a/modules/core/lib/Auth/Process/AttributeAlter.php
+++ b/modules/core/lib/Auth/Process/AttributeAlter.php
@@ -16,34 +16,41 @@ class AttributeAlter extends \SimpleSAML\Auth\ProcessingFilter
 {
     /**
      * Should the pattern found be replaced?
+     * @var bool
      */
     private $replace = false;
 
     /**
      * Should the value found be removed?
+     * @var bool
      */
     private $remove = false;
 
     /**
      * Pattern to search for.
+     * @var string
      */
     private $pattern = '';
 
     /**
      * String to replace the pattern found with.
+     * @var string|false
      */
     private $replacement = false;
 
     /**
      * Attribute to search in
+     * @var string
      */
     private $subject = '';
 
     /**
      * Attribute to place the result in.
+     * @var string
      */
     private $target = '';
 
+
     /**
      * Initialize this filter.
      *
@@ -85,6 +92,7 @@ class AttributeAlter extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Apply the filter to modify attributes.
      *
@@ -175,6 +183,7 @@ class AttributeAlter extends \SimpleSAML\Auth\ProcessingFilter
                     $attributes[$this->subject]
                 );
             } else {
+                /** @psalm-suppress InvalidArgument */
                 $attributes[$this->target] = array_diff(
                     preg_replace(
                         $this->pattern,
diff --git a/modules/core/lib/Auth/Process/AttributeCopy.php b/modules/core/lib/Auth/Process/AttributeCopy.php
index e9fab68d73790c93ca371a394787b247638f39fc..0dd53c8fade1080e5f87b5d5e8a357b858760973 100644
--- a/modules/core/lib/Auth/Process/AttributeCopy.php
+++ b/modules/core/lib/Auth/Process/AttributeCopy.php
@@ -21,6 +21,7 @@ class AttributeCopy extends \SimpleSAML\Auth\ProcessingFilter
 {
     /**
      * Assosiative array with the mappings of attribute names.
+     * @var array
      */
     private $map = [];
 
@@ -50,6 +51,7 @@ class AttributeCopy extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Apply filter to rename attributes.
      *
diff --git a/modules/core/lib/Auth/Process/AttributeLimit.php b/modules/core/lib/Auth/Process/AttributeLimit.php
index 8559db4d68f261c791d48cb8f0a4acc4f27821e9..864324b268c0140d2673d6a63e86b45c53242b0e 100644
--- a/modules/core/lib/Auth/Process/AttributeLimit.php
+++ b/modules/core/lib/Auth/Process/AttributeLimit.php
@@ -15,6 +15,7 @@ class AttributeLimit extends \SimpleSAML\Auth\ProcessingFilter
 {
     /**
      * List of attributes which this filter will allow through.
+     * @var array
      */
     private $allowedAttributes = [];
 
@@ -60,6 +61,7 @@ class AttributeLimit extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Get list of allowed from the SP/IdP config.
      *
@@ -79,6 +81,7 @@ class AttributeLimit extends \SimpleSAML\Auth\ProcessingFilter
         return null;
     }
 
+
     /**
      * Apply filter to remove attributes.
      *
@@ -129,6 +132,7 @@ class AttributeLimit extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Perform the filtering of attributes
      * @param array $values The current values for a given attribute
diff --git a/modules/core/lib/Auth/Process/AttributeMap.php b/modules/core/lib/Auth/Process/AttributeMap.php
index db5bdbc4f54023d94afc8d59e984a9e2c299fc37..f4b79d2494a38de517a7a30c59e7d8ad35ff7efa 100644
--- a/modules/core/lib/Auth/Process/AttributeMap.php
+++ b/modules/core/lib/Auth/Process/AttributeMap.php
@@ -15,11 +15,13 @@ class AttributeMap extends \SimpleSAML\Auth\ProcessingFilter
 {
     /**
      * Associative array with the mappings of attribute names.
+     * @var array
      */
     private $map = [];
 
     /**
      * Should attributes be duplicated or renamed.
+     * @var bool
      */
     private $duplicate = false;
 
@@ -89,7 +91,8 @@ class AttributeMap extends \SimpleSAML\Auth\ProcessingFilter
             }
             $filePath = Module::getModuleDir($m[0]).'/attributemap/'.$m[1].'.php';
         } else {
-            $filePath = $config->getPathValue('attributenamemapdir', 'attributemap/').$fileName.'.php';
+            $attributenamemapdir = $config->getPathValue('attributenamemapdir', 'attributemap/') ?: 'attributemap/';
+            $filePath = $attributenamemapdir.$fileName.'.php';
         }
 
         if (!file_exists($filePath)) {
diff --git a/modules/core/lib/Auth/Process/AttributeRealm.php b/modules/core/lib/Auth/Process/AttributeRealm.php
index c708ece37927747c46f44aff2e1637aa2ecf0692..58533a7cd64d3a216fe01223f3fe79b0683a9237 100644
--- a/modules/core/lib/Auth/Process/AttributeRealm.php
+++ b/modules/core/lib/Auth/Process/AttributeRealm.php
@@ -16,6 +16,7 @@ class AttributeRealm extends \SimpleSAML\Auth\ProcessingFilter
     /** @var string */
     private $attributename = 'realm';
 
+
     /**
      * Initialize this filter.
      *
@@ -32,6 +33,7 @@ class AttributeRealm extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Apply filter to add or replace attributes.
      *
diff --git a/modules/core/lib/Auth/Process/AttributeValueMap.php b/modules/core/lib/Auth/Process/AttributeValueMap.php
index bf747b634baa89de8794891066ad7165aae653fc..82ed34a3df667b11f5b0d4abdd396f029272b611 100644
--- a/modules/core/lib/Auth/Process/AttributeValueMap.php
+++ b/modules/core/lib/Auth/Process/AttributeValueMap.php
@@ -30,7 +30,7 @@ class AttributeValueMap extends \SimpleSAML\Auth\ProcessingFilter
      * @var array
      */
     private $values = [];
-    
+
     /**
      * Whether $sourceattribute should be kept or not.
      * @var bool
@@ -42,7 +42,7 @@ class AttributeValueMap extends \SimpleSAML\Auth\ProcessingFilter
      * @var bool
      */
     private $replace = false;
-    
+
 
     /**
      * Initialize the filter.
@@ -83,7 +83,7 @@ class AttributeValueMap extends \SimpleSAML\Auth\ProcessingFilter
             if ($name === 'sourceattribute') {
                 $this->sourceattribute = $value;
             }
-        
+
             // set the values
             if ($name === 'values') {
                 $this->values = $value;
diff --git a/modules/core/lib/Auth/Process/Cardinality.php b/modules/core/lib/Auth/Process/Cardinality.php
index b60fc389a816b7406b479baf522aa94f03222fc2..023f4c57ab60cb862d7597c79a36fb3a4e77a4c1 100644
--- a/modules/core/lib/Auth/Process/Cardinality.php
+++ b/modules/core/lib/Auth/Process/Cardinality.php
@@ -25,6 +25,7 @@ class Cardinality extends \SimpleSAML\Auth\ProcessingFilter
     /** @var \SimpleSAML\Utils\HttpAdapter */
     private $http;
 
+
     /**
      * Initialize this filter, parse configuration.
      *
@@ -97,6 +98,7 @@ class Cardinality extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Process this filter
      *
diff --git a/modules/core/lib/Auth/Process/CardinalitySingle.php b/modules/core/lib/Auth/Process/CardinalitySingle.php
index 13dad416bed83a371da51df9a9d2bc4819452f50..1d1e39baea212223c20406ebafe910d9f24881c0 100644
--- a/modules/core/lib/Auth/Process/CardinalitySingle.php
+++ b/modules/core/lib/Auth/Process/CardinalitySingle.php
@@ -36,6 +36,7 @@ class CardinalitySingle extends \SimpleSAML\Auth\ProcessingFilter
     /** @var \SimpleSAML\Utils\HttpAdapter */
     private $http;
 
+
     /**
      * Initialize this filter, parse configuration.
      *
diff --git a/modules/core/lib/Auth/Process/GenerateGroups.php b/modules/core/lib/Auth/Process/GenerateGroups.php
index b5d738e1df64a6c45d467a6ea10f881bd2f309e7..206433cd5ec322adcdfecfe96bb5b0ec1e41bd85 100644
--- a/modules/core/lib/Auth/Process/GenerateGroups.php
+++ b/modules/core/lib/Auth/Process/GenerateGroups.php
@@ -14,9 +14,11 @@ class GenerateGroups extends \SimpleSAML\Auth\ProcessingFilter
 {
     /**
      * The attributes we should generate groups from.
+     * @var array
      */
     private $generateGroupsFrom;
 
+
     /**
      * Initialize this filter.
      *
@@ -48,6 +50,7 @@ class GenerateGroups extends \SimpleSAML\Auth\ProcessingFilter
         }
     }
 
+
     /**
      * Apply filter to add groups attribute.
      *
diff --git a/modules/core/lib/Auth/Process/LanguageAdaptor.php b/modules/core/lib/Auth/Process/LanguageAdaptor.php
index 818b866a96c0d04215ddb5f1e48cdda2f1b6d355..64cef588417175d160f661cb6d6c1226d98c8481 100644
--- a/modules/core/lib/Auth/Process/LanguageAdaptor.php
+++ b/modules/core/lib/Auth/Process/LanguageAdaptor.php
@@ -16,6 +16,7 @@ class LanguageAdaptor extends \SimpleSAML\Auth\ProcessingFilter
     /** @var string */
     private $langattr = 'preferredLanguage';
 
+
     /**
      * Initialize this filter.
      *
diff --git a/modules/core/lib/Auth/Process/PHP.php b/modules/core/lib/Auth/Process/PHP.php
index 96eb1773b8b35a46e50247f92d09e7eb84fe2555..77d8266affc46a9998cfb74d9278d660013e04a8 100644
--- a/modules/core/lib/Auth/Process/PHP.php
+++ b/modules/core/lib/Auth/Process/PHP.php
@@ -55,11 +55,12 @@ class PHP extends \SimpleSAML\Auth\ProcessingFilter
         /**
          * @param array &$attributes
          * @param array &$state
-         * @return void
          */
-        $function = function (
-            /** @scrutinizer ignore-unused */ &$attributes,
-            /** @scrutinizer ignore-unused */ &$state
+        $function = /** @return void */ function (
+            /** @scrutinizer ignore-unused */
+            array &$attributes,
+            /** @scrutinizer ignore-unused */
+            array &$state
         ) {
             eval($this->code);
         };
diff --git a/modules/core/lib/Auth/Process/StatisticsWithAttribute.php b/modules/core/lib/Auth/Process/StatisticsWithAttribute.php
index bf920128d881b4245a27c7d1d61c37a87b8bf4ba..862e0c0d03782b8e589ab076e73026bc02e02336 100644
--- a/modules/core/lib/Auth/Process/StatisticsWithAttribute.php
+++ b/modules/core/lib/Auth/Process/StatisticsWithAttribute.php
@@ -83,7 +83,7 @@ class StatisticsWithAttribute extends \SimpleSAML\Auth\ProcessingFilter
             $isPassive = 'passive-';
         }
 
-        if (array_key_exists($this->attribute, $state['Attributes'])) {
+        if (!is_null($this->attribute) && array_key_exists($this->attribute, $state['Attributes'])) {
             $logAttribute = $state['Attributes'][$this->attribute][0];
         }
 
diff --git a/modules/core/lib/Auth/Process/TargetedID.php b/modules/core/lib/Auth/Process/TargetedID.php
index 6c720d6ba530808692263cd4e28e0dc369a2b031..1ad7d20280ff346d69b1727f7ddbc1b692102eb0 100644
--- a/modules/core/lib/Auth/Process/TargetedID.php
+++ b/modules/core/lib/Auth/Process/TargetedID.php
@@ -39,6 +39,8 @@ class TargetedID extends \SimpleSAML\Auth\ProcessingFilter
     /**
      * The attribute we should generate the targeted id from, or NULL if we should use the
      * UserID.
+     *
+     * @var string|null
      */
     private $attribute = null;
 
diff --git a/modules/core/lib/Auth/Source/AdminPassword.php b/modules/core/lib/Auth/Source/AdminPassword.php
index 931707dbbc3b4985be1b8a23203dfd13a9482025..ea03893e75cb9f0a94c49f644ad0834355b1ed14 100644
--- a/modules/core/lib/Auth/Source/AdminPassword.php
+++ b/modules/core/lib/Auth/Source/AdminPassword.php
@@ -31,6 +31,7 @@ class AdminPassword extends \SimpleSAML\Module\core\Auth\UserPassBase
         $this->setForcedUsername("admin");
     }
 
+
     /**
      * Attempt to log in using the given username and password.
      *
diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php
index 2eb860bf3a98873180fb8f7fa294ce4c2f98783f..b3477aafe3acec9ee5da338f233f8a33b493577b 100644
--- a/modules/core/lib/Auth/UserPassBase.php
+++ b/modules/core/lib/Auth/UserPassBase.php
@@ -36,12 +36,16 @@ abstract class UserPassBase extends \SimpleSAML\Auth\Source
      *
      * A forced username cannot be changed by the user.
      * If this is NULL, we won't force any username.
+     *
+     * @var string|null
      */
     private $forcedUsername;
 
     /**
      * Links to pages from login page.
      * From configuration
+     *
+     * @var array
      */
     protected $loginLinks;
 
@@ -49,6 +53,7 @@ abstract class UserPassBase extends \SimpleSAML\Auth\Source
      * Storage for authsource config option remember.username.enabled
      * loginuserpass.php and loginuserpassorg.php pages/templates use this option to
      * present users with a checkbox to save their username for the next login request.
+     *
      * @var bool
      */
     protected $rememberUsernameEnabled = false;
@@ -57,6 +62,7 @@ abstract class UserPassBase extends \SimpleSAML\Auth\Source
      * Storage for authsource config option remember.username.checked
      * loginuserpass.php and loginuserpassorg.php pages/templates use this option
      * to default the remember username checkbox to checked or not.
+     *
      * @var bool
      */
     protected $rememberUsernameChecked = false;
@@ -67,6 +73,7 @@ abstract class UserPassBase extends \SimpleSAML\Auth\Source
      * users with a checkbox to keep their session alive across
      * different browser sessions (that is, closing and opening the
      * browser again).
+     *
      * @var bool
      */
     protected $rememberMeEnabled = false;
@@ -75,6 +82,7 @@ abstract class UserPassBase extends \SimpleSAML\Auth\Source
      * Storage for general config option session.rememberme.checked.
      * loginuserpass.php page/template uses this option to default
      * the "remember me" checkbox to checked or not.
+     *
      * @var bool
      */
     protected $rememberMeChecked = false;
@@ -286,10 +294,13 @@ abstract class UserPassBase extends \SimpleSAML\Auth\Source
         assert(is_string($password));
 
         // Here we retrieve the state array we saved in the authenticate-function.
+        /** @var array $state */
         $state = Auth\State::loadState($authStateId, self::STAGEID);
 
         // Retrieve the authentication source we are executing.
         assert(array_key_exists(self::AUTHID, $state));
+
+        /** @var \SimpleSAML\Module\core\Auth\UserPassBase|null $source */
         $source = Auth\Source::getById($state[self::AUTHID]);
         if ($source === null) {
             throw new \Exception('Could not find authentication source with id '.$state[self::AUTHID]);
diff --git a/modules/core/lib/Auth/UserPassOrgBase.php b/modules/core/lib/Auth/UserPassOrgBase.php
index a911097559f7507a325bc9615899ca67552b0cd5..552e9165023713a4b267dbab88546fcd8120350d 100644
--- a/modules/core/lib/Auth/UserPassOrgBase.php
+++ b/modules/core/lib/Auth/UserPassOrgBase.php
@@ -44,6 +44,8 @@ abstract class UserPassOrgBase extends \SimpleSAML\Auth\Source
      *  'none': Force the user to select the correct organization from the dropdown box.
      *  'allow': Allow the user to enter the organization as part of the username.
      *  'force': Remove the dropdown box.
+     *
+     * @var string
      */
     private $usernameOrgMethod;
 
@@ -51,6 +53,7 @@ abstract class UserPassOrgBase extends \SimpleSAML\Auth\Source
      * Storage for authsource config option remember.username.enabled
      * loginuserpass.php and loginuserpassorg.php pages/templates use this option to
      * present users with a checkbox to save their username for the next login request.
+     *
      * @var bool
      */
     protected $rememberUsernameEnabled = false;
@@ -59,6 +62,7 @@ abstract class UserPassOrgBase extends \SimpleSAML\Auth\Source
      * Storage for authsource config option remember.username.checked
      * loginuserpass.php and loginuserpassorg.php pages/templates use this option
      * to default the remember username checkbox to checked or not.
+     *
      * @var bool
      */
     protected $rememberUsernameChecked = false;
@@ -67,6 +71,7 @@ abstract class UserPassOrgBase extends \SimpleSAML\Auth\Source
      * Storage for authsource config option remember.organization.enabled
      * loginuserpassorg.php page/template use this option to present users
      * with a checkbox to save their organization choice for the next login request.
+     *
      * @var bool
      */
     protected $rememberOrganizationEnabled = false;
@@ -75,6 +80,7 @@ abstract class UserPassOrgBase extends \SimpleSAML\Auth\Source
      * Storage for authsource config option remember.organization.checked
      * loginuserpassorg.php page/template use this option to
      * default the remember organization checkbox to checked or not.
+     *
      * @var bool
      */
     protected $rememberOrganizationChecked = false;
@@ -272,10 +278,13 @@ abstract class UserPassOrgBase extends \SimpleSAML\Auth\Source
         assert(is_string($organization));
 
         /* Retrieve the authentication state. */
+        /** @var array $state */
         $state = Auth\State::loadState($authStateId, self::STAGEID);
 
         /* Find authentication source. */
         assert(array_key_exists(self::AUTHID, $state));
+
+        /** @var \SimpleSAML\Module\core\Auth\UserPassOrgBase|null $source */
         $source = Auth\Source::getById($state[self::AUTHID]);
         if ($source === null) {
             throw new \Exception('Could not find authentication source with id '.$state[self::AUTHID]);
@@ -328,10 +337,13 @@ abstract class UserPassOrgBase extends \SimpleSAML\Auth\Source
         assert(is_string($authStateId));
 
         /* Retrieve the authentication state. */
+        /** @var array $state */
         $state = Auth\State::loadState($authStateId, self::STAGEID);
 
         /* Find authentication source. */
         assert(array_key_exists(self::AUTHID, $state));
+
+        /** @var \SimpleSAML\Module\core\Auth\UserPassOrgBase|null $source */
         $source = Auth\Source::getById($state[self::AUTHID]);
         if ($source === null) {
             throw new \Exception('Could not find authentication source with id '.$state[self::AUTHID]);
diff --git a/modules/core/lib/Controller.php b/modules/core/lib/Controller.php
index 1d88454b0c1f791ea27aad4a13a0a3ecb0dd5ca6..44f3ead040c86972221acc5082d1aa32658c6684 100644
--- a/modules/core/lib/Controller.php
+++ b/modules/core/lib/Controller.php
@@ -141,7 +141,9 @@ class Controller
         if ($request->get(Auth\State::EXCEPTION_PARAM, false) !== false) {
             // This is just a simple example of an error
 
+            /** @var array $state */
             $state = Auth\State::loadExceptionState();
+
             assert(array_key_exists(Auth\State::EXCEPTION_DATA, $state));
             $e = $state[Auth\State::EXCEPTION_DATA];
 
diff --git a/modules/core/lib/Stats/Output/File.php b/modules/core/lib/Stats/Output/File.php
index d3875afd13bd1f29c88aec391544784d7f9b409a..242372ab04155e9d301ca2cadb46ed548b7ef8ac 100644
--- a/modules/core/lib/Stats/Output/File.php
+++ b/modules/core/lib/Stats/Output/File.php
@@ -38,13 +38,14 @@ class File extends \SimpleSAML\Stats\Output
      */
     public function __construct(Configuration $config)
     {
-        $this->logDir = $config->getPathValue('directory');
-        if ($this->logDir === null) {
+        $logDir = $config->getPathValue('directory');
+        if ($logDir === null) {
             throw new \Exception('Missing "directory" option for core:File');
         }
-        if (!is_dir($this->logDir)) {
-            throw new \Exception('Could not find log directory: '.var_export($this->logDir, true));
+        if (!is_dir($logDir)) {
+            throw new \Exception('Could not find log directory: '.var_export($logDir, true));
         }
+        $this->logDir = $logDir;
     }
 
 
@@ -86,6 +87,10 @@ class File extends \SimpleSAML\Stats\Output
     {
         assert(isset($data['time']));
 
+        if ($this->file === false || $this->file === null) {
+            throw new Error\Exception('Error opening log file:  invalid handle');
+        }
+
         $time = $data['time'];
         $milliseconds = (int) (($time - (int) $time) * 1000);
 
diff --git a/modules/core/lib/Storage/SQLPermanentStorage.php b/modules/core/lib/Storage/SQLPermanentStorage.php
index ea201bbb432dbc030129d11549468b6e6d4c8127..6bce0baf22f484f389f5dc38a7343063e85284b1 100644
--- a/modules/core/lib/Storage/SQLPermanentStorage.php
+++ b/modules/core/lib/Storage/SQLPermanentStorage.php
@@ -32,7 +32,7 @@ class SQLPermanentStorage
             $config = Configuration::getInstance();
         }
 
-        $datadir = $config->getPathValue('datadir', 'data/');
+        $datadir = $config->getPathValue('datadir', 'data/') ?: 'data/';
 
         if (!is_dir($datadir)) {
             throw new \Exception('Data directory ['.$datadir.'] does not exist');
@@ -51,7 +51,7 @@ class SQLPermanentStorage
             if ($q === false) {
                 $this->db->exec('
 		    CREATE TABLE data (
-                        key1 text, 
+                        key1 text,
                         key2 text,
                         type text,
                         value text,
diff --git a/modules/core/templates/frontpage_config.tpl.php b/modules/core/templates/frontpage_config.tpl.php
index 10f7463cf305b6ceff462533f85f4c5140db68db..8227e8cd0ce23e0a572b21388d5544cf57fc614f 100644
--- a/modules/core/templates/frontpage_config.tpl.php
+++ b/modules/core/templates/frontpage_config.tpl.php
@@ -5,7 +5,7 @@ $this->includeAtTemplateBase('includes/header.php');
 
 ?>
 
-<!-- 
+<!--
 <div id="tabdiv">
 <ul>
     <li><a href="#welcome"><?php echo $this->t('{core:frontpage:welcome}'); ?></a></li>
diff --git a/modules/core/templates/loginuserpass.php b/modules/core/templates/loginuserpass.tpl.php
similarity index 100%
rename from modules/core/templates/loginuserpass.php
rename to modules/core/templates/loginuserpass.tpl.php
diff --git a/modules/core/templates/logout-iframe-wrapper.php b/modules/core/templates/logout-iframe-wrapper.tpl.php
similarity index 100%
rename from modules/core/templates/logout-iframe-wrapper.php
rename to modules/core/templates/logout-iframe-wrapper.tpl.php
diff --git a/modules/core/templates/logout-iframe.php b/modules/core/templates/logout-iframe.tpl.php
similarity index 100%
rename from modules/core/templates/logout-iframe.php
rename to modules/core/templates/logout-iframe.tpl.php
diff --git a/modules/core/templates/short_sso_interval.php b/modules/core/templates/short_sso_interval.tpl.php
similarity index 100%
rename from modules/core/templates/short_sso_interval.php
rename to modules/core/templates/short_sso_interval.tpl.php
diff --git a/modules/core/www/authenticate.php b/modules/core/www/authenticate.php
index e1484e1fed26af74e45614a8650e3c7ff56b032c..03b461fcdd384622347666b2d2aa91c3523477e6 100644
--- a/modules/core/www/authenticate.php
+++ b/modules/core/www/authenticate.php
@@ -20,7 +20,9 @@ if (array_key_exists('logout', $_REQUEST)) {
 if (array_key_exists(\SimpleSAML\Auth\State::EXCEPTION_PARAM, $_REQUEST)) {
     // This is just a simple example of an error
 
+    /** @var array $state */
     $state = \SimpleSAML\Auth\State::loadExceptionState();
+
     assert(array_key_exists(\SimpleSAML\Auth\State::EXCEPTION_DATA, $state));
     $e = $state[\SimpleSAML\Auth\State::EXCEPTION_DATA];
 
diff --git a/modules/core/www/cardinality_error.php b/modules/core/www/cardinality_error.php
index b3a3e5b99c17fb4b5a446319cd719b570ac18265..7ce8cce96fa7d28d356f1fbba523e781447e471b 100644
--- a/modules/core/www/cardinality_error.php
+++ b/modules/core/www/cardinality_error.php
@@ -10,6 +10,7 @@ if (!array_key_exists('StateId', $_REQUEST)) {
     throw new \SimpleSAML\Error\BadRequest('Missing required StateId query parameter.');
 }
 $id = $_REQUEST['StateId'];
+/** @var array $state */
 $state = \SimpleSAML\Auth\State::loadState($id, 'core:cardinality');
 $session = \SimpleSAML\Session::getSessionFromRequest();
 
diff --git a/modules/core/www/frontpage_config.php b/modules/core/www/frontpage_config.php
index bb6e6ae5a347977b3bd6b35de51fa5ad7dc877b8..3ed2fffce938bdd2935a31f66584f3fac84ef6fa 100644
--- a/modules/core/www/frontpage_config.php
+++ b/modules/core/www/frontpage_config.php
@@ -75,8 +75,9 @@ if ($config->getBoolean('admin.checkforupdates', true) && $current !== 'master')
             curl_setopt($ch, CURLOPT_PROXYUSERPWD, $config->getValue('proxy.auth', null));
             $response = curl_exec($ch);
 
-            if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) {
-                $latest = json_decode($response, true);
+            if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) === 200) {
+                /** @psalm-suppress InvalidScalarArgument */
+                $latest = json_decode(strval($response), true);
                 $session->setData("core:latest_simplesamlphp_version", "version", $latest);
             }
             curl_close($ch);
diff --git a/modules/core/www/idp/logout-iframe-post.php b/modules/core/www/idp/logout-iframe-post.php
index 2081dfe26d69fb87b55b54c814d3bd1d8fe7911d..6dc9c8d35ddf9dbff909f717d1d2d052d0b1d4ac 100644
--- a/modules/core/www/idp/logout-iframe-post.php
+++ b/modules/core/www/idp/logout-iframe-post.php
@@ -51,6 +51,7 @@ if ($encryptNameId) {
 
 $bindings = [\SAML2\Constants::BINDING_HTTP_POST];
 
+/** @var array $dst */
 $dst = $spMetadata->getDefaultEndpoint('SingleLogoutService', $bindings);
 $binding = \SAML2\Binding::getBinding($dst['Binding']);
 $lr->setDestination($dst['Location']);
diff --git a/modules/core/www/idp/logout-iframe.php b/modules/core/www/idp/logout-iframe.php
index 27195af6b83b40fc2d7e6be4baee1cf8bbfa64af..5e58666aedfdd2c12a3b5bc1011c89fe7656c582 100644
--- a/modules/core/www/idp/logout-iframe.php
+++ b/modules/core/www/idp/logout-iframe.php
@@ -115,9 +115,9 @@ foreach ($state['core:Logout-IFrame:Associations'] as $association) {
 
 $globalConfig = \SimpleSAML\Configuration::getInstance();
 if ($type === 'nojs') {
-    $t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:logout-iframe-wrapper.php');
+    $t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:logout-iframe-wrapper.tpl.php');
 } else {
-    $t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:logout-iframe.php');
+    $t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:logout-iframe.tpl.php');
 }
 
 /**
diff --git a/modules/core/www/loginuserpass.php b/modules/core/www/loginuserpass.php
index 747506f898d1e969212cd17a2c5acca85e6ffcd9..ce14bf1ed0dcfc6968966e86fc37d6ca507fe2c0 100644
--- a/modules/core/www/loginuserpass.php
+++ b/modules/core/www/loginuserpass.php
@@ -14,8 +14,10 @@ if (!array_key_exists('AuthState', $_REQUEST)) {
     throw new \SimpleSAML\Error\BadRequest('Missing AuthState parameter.');
 }
 $authStateId = $_REQUEST['AuthState'];
+/** @var array $state */
 $state = \SimpleSAML\Auth\State::loadState($authStateId, \SimpleSAML\Module\core\Auth\UserPassBase::STAGEID);
 
+/** @var \SimpleSAML\Module\core\Auth\UserPassBase|null $source */
 $source = \SimpleSAML\Auth\Source::getById($state[\SimpleSAML\Module\core\Auth\UserPassBase::AUTHID]);
 if ($source === null) {
     throw new \Exception(
@@ -23,7 +25,6 @@ if ($source === null) {
     );
 }
 
-
 if (array_key_exists('username', $_REQUEST)) {
     $username = $_REQUEST['username'];
 } elseif ($source->getRememberUsernameEnabled() && array_key_exists($source->getAuthId().'-username', $_COOKIE)) {
@@ -60,7 +61,7 @@ if (!empty($_REQUEST['username']) || !empty($password)) {
     if ($source->getRememberUsernameEnabled()) {
         $sessionHandler = \SimpleSAML\SessionHandler::getSessionHandler();
         $params = $sessionHandler->getCookieParams();
-        
+
         if (isset($_REQUEST['remember_username']) && $_REQUEST['remember_username'] == 'Yes') {
             $params['expire'] = time() + 31536000;
         } else {
@@ -98,7 +99,7 @@ if (!empty($_REQUEST['username']) || !empty($password)) {
 }
 
 $globalConfig = \SimpleSAML\Configuration::getInstance();
-$t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:loginuserpass.php');
+$t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:loginuserpass.tpl.php');
 $t->data['stateparams'] = ['AuthState' => $authStateId];
 if (array_key_exists('forcedUsername', $state)) {
     $t->data['username'] = $state['forcedUsername'];
diff --git a/modules/core/www/loginuserpassorg.php b/modules/core/www/loginuserpassorg.php
index 2b9ec4dba32a09252629180f31d82f4ce349c30d..e9423c71c9e0c6184a7c844350d2d3cec1cbf704 100644
--- a/modules/core/www/loginuserpassorg.php
+++ b/modules/core/www/loginuserpassorg.php
@@ -14,8 +14,11 @@ if (!array_key_exists('AuthState', $_REQUEST)) {
     throw new \SimpleSAML\Error\BadRequest('Missing AuthState parameter.');
 }
 $authStateId = $_REQUEST['AuthState'];
+
+/** @var array $state */
 $state = \SimpleSAML\Auth\State::loadState($authStateId, \SimpleSAML\Module\core\Auth\UserPassOrgBase::STAGEID);
 
+/** @var \SimpleSAML\Module\core\Auth\UserPassOrgBase $source */
 $source = \SimpleSAML\Auth\Source::getById($state[\SimpleSAML\Module\core\Auth\UserPassOrgBase::AUTHID]);
 if ($source === null) {
     throw new \Exception(
@@ -124,7 +127,7 @@ if ($organizations === null || !empty($organization)) {
 }
 
 $globalConfig = \SimpleSAML\Configuration::getInstance();
-$t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:loginuserpass.php');
+$t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:loginuserpass.tpl.php');
 $t->data['stateparams'] = ['AuthState' => $authStateId];
 $t->data['username'] = $username;
 $t->data['forceUsername'] = false;
diff --git a/modules/core/www/no_cookie.php b/modules/core/www/no_cookie.php
index 3575d85f6b4b3cc3e623baa4c457c7dff2cd7de7..7ae3889d6c613c5c7df9b2f896354529d0cdffda 100644
--- a/modules/core/www/no_cookie.php
+++ b/modules/core/www/no_cookie.php
@@ -11,8 +11,15 @@ $globalConfig = \SimpleSAML\Configuration::getInstance();
 $t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:no_cookie.tpl.php');
 $translator = $t->getTranslator();
 
-$t->data['header'] = htmlspecialchars($translator->t('{core:no_cookie:header}'));
-$t->data['description'] = htmlspecialchars($translator->t('{core:no_cookie:description}'));
-$t->data['retry'] = htmlspecialchars($translator->t('{core:no_cookie:retry}'));
+/** @var string $header */
+$header = $translator->t('{core:no_cookie:header}');
+/** @var string $desc */
+$desc = $translator->t('{core:no_cookie:description}');
+/** @var string $retry */
+$retry = $translator->t('{core:no_cookie:retry}');
+
+$t->data['header'] = htmlspecialchars($header);
+$t->data['description'] = htmlspecialchars($desc);
+$t->data['retry'] = htmlspecialchars($retry);
 $t->data['retryURL'] = $retryURL;
 $t->show();
diff --git a/modules/core/www/short_sso_interval.php b/modules/core/www/short_sso_interval.php
index a9da1cae10bf25318b38849856b54d1da9a5edfb..919210f535aefdbfac9766ae22a1b1236c824f82 100644
--- a/modules/core/www/short_sso_interval.php
+++ b/modules/core/www/short_sso_interval.php
@@ -11,7 +11,10 @@ if (!array_key_exists('StateId', $_REQUEST)) {
     throw new \SimpleSAML\Error\BadRequest('Missing required StateId query parameter.');
 }
 $id = $_REQUEST['StateId'];
+
+/** @var array $state */
 $state = \SimpleSAML\Auth\State::loadState($id, 'core:short_sso_interval');
+
 $session = \SimpleSAML\Session::getSessionFromRequest();
 
 if (array_key_exists('continue', $_REQUEST)) {
@@ -20,7 +23,7 @@ if (array_key_exists('continue', $_REQUEST)) {
 }
 
 $globalConfig = \SimpleSAML\Configuration::getInstance();
-$t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:short_sso_interval.php');
+$t = new \SimpleSAML\XHTML\Template($globalConfig, 'core:short_sso_interval.tpl.php');
 $translator = $t->getTranslator();
 $t->data['target'] = \SimpleSAML\Module::getModuleURL('core/short_sso_interval.php');
 $t->data['params'] = ['StateId' => $id];
diff --git a/modules/cron/lib/Cron.php b/modules/cron/lib/Cron.php
index 9956a87d42880436e36d8c8d7ac52ba3b69321b2..bbaa4e7c34300c1763c51a9d395cd6683e3abfb6 100644
--- a/modules/cron/lib/Cron.php
+++ b/modules/cron/lib/Cron.php
@@ -17,6 +17,7 @@ class Cron
      */
     private $cronconfig;
 
+
     /*
      * @param \SimpleSAML\Configuration $cronconfig The cron configuration to use. If not specified defaults
      * to `config/module_cron.php`
diff --git a/modules/cron/templates/croninfo-result.php b/modules/cron/templates/croninfo-result.tpl.php
similarity index 100%
rename from modules/cron/templates/croninfo-result.php
rename to modules/cron/templates/croninfo-result.tpl.php
diff --git a/modules/cron/www/cron.php b/modules/cron/www/cron.php
index 5dd7439d0a701953913229d8c10f0c64ba82a7b7..2f0dae8b5113a3e3b41da929e1046bcde3c5fb70 100644
--- a/modules/cron/www/cron.php
+++ b/modules/cron/www/cron.php
@@ -29,7 +29,7 @@ if ($cronconfig->getValue('sendemail', true) && count($summary) > 0) {
 }
 
 if (isset($_REQUEST['output']) && $_REQUEST['output'] == "xhtml") {
-    $t = new \SimpleSAML\XHTML\Template($config, 'cron:croninfo-result.php', 'cron:cron');
+    $t = new \SimpleSAML\XHTML\Template($config, 'cron:croninfo-result.tpl.php', 'cron:cron');
     $t->data['tag'] = $croninfo['tag'];
     $t->data['time'] = $time;
     $t->data['url'] = $url;
diff --git a/modules/exampleauth/lib/Auth/Source/External.php b/modules/exampleauth/lib/Auth/Source/External.php
index 1b37ad7a86a99c5df7accf94a1ec84ab1d51dac5..3d3d5cf7b2f918b60302d0af081ba736cad77373 100644
--- a/modules/exampleauth/lib/Auth/Source/External.php
+++ b/modules/exampleauth/lib/Auth/Source/External.php
@@ -34,6 +34,7 @@ class External extends \SimpleSAML\Auth\Source
      */
     const AUTHID = 'SimpleSAML\Module\exampleautth\Auth\Sourc\External.AuthId';
 
+
     /**
      * Constructor for this authentication source.
      *
@@ -186,7 +187,6 @@ class External extends \SimpleSAML\Auth\Source
      * This function resumes the authentication process after the user has
      * entered his or her credentials.
      *
-     * @param array &$state  The authentication state.
      * @return void
      * @throws \SimpleSAML\Error\BadRequest
      * @throws \SimpleSAML\Error\Exception
@@ -205,6 +205,7 @@ class External extends \SimpleSAML\Auth\Source
          * Once again, note the second parameter to the loadState function. This must
          * match the string we used in the saveState-call above.
          */
+        /** @var array $state */
         $state = Auth\State::loadState($_REQUEST['State'], 'exampleauth:External');
 
         /*
diff --git a/modules/exampleauth/lib/Auth/Source/StaticSource.php b/modules/exampleauth/lib/Auth/Source/StaticSource.php
index 7d196b62d92407021317ee53d022594477d1d28c..1a29fcd2fb46b807aa1618a4ba7f8c832b1bfc2e 100644
--- a/modules/exampleauth/lib/Auth/Source/StaticSource.php
+++ b/modules/exampleauth/lib/Auth/Source/StaticSource.php
@@ -17,9 +17,11 @@ class StaticSource extends \SimpleSAML\Auth\Source
 {
     /**
      * The attributes we return.
+     * @var array
      */
     private $attributes;
 
+
     /**
      * Constructor for this authentication source.
      *
diff --git a/modules/exampleauth/lib/Auth/Source/UserPass.php b/modules/exampleauth/lib/Auth/Source/UserPass.php
index 76adc8324f346f1594e9a631861b25fd3d324780..4761e65da870cae7cac1eaf33e9817176aee5878 100644
--- a/modules/exampleauth/lib/Auth/Source/UserPass.php
+++ b/modules/exampleauth/lib/Auth/Source/UserPass.php
@@ -20,9 +20,12 @@ class UserPass extends \SimpleSAML\Module\core\Auth\UserPassBase
     /**
      * Our users, stored in an associative array. The key of the array is "<username>:<password>",
      * while the value of each element is a new array with the attributes for each user.
+     *
+     * @var array
      */
     private $users;
 
+
     /**
      * Constructor for this authentication source.
      *
@@ -66,6 +69,7 @@ class UserPass extends \SimpleSAML\Module\core\Auth\UserPassBase
         }
     }
 
+
     /**
      * Attempt to log in using the given username and password.
      *
diff --git a/modules/exampleauth/www/redirecttest.php b/modules/exampleauth/www/redirecttest.php
index 9d605277d31a7f2c05508d1d01aa5e4bbf596d67..ff06102cd2e27b573898b6fdd287d3c60cee8327 100644
--- a/modules/exampleauth/www/redirecttest.php
+++ b/modules/exampleauth/www/redirecttest.php
@@ -10,6 +10,8 @@
 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'];
diff --git a/modules/multiauth/lib/Auth/Source/MultiAuth.php b/modules/multiauth/lib/Auth/Source/MultiAuth.php
index 31e76b2022383afedc6208afc758221d9ac5c8bb..02a017cfcaefa66dfb3e2c8b22e8818f4393c655 100644
--- a/modules/multiauth/lib/Auth/Source/MultiAuth.php
+++ b/modules/multiauth/lib/Auth/Source/MultiAuth.php
@@ -40,6 +40,7 @@ class MultiAuth extends \SimpleSAML\Auth\Source
 
     /**
      * Array of sources we let the user chooses among.
+     * @var array
      */
     private $sources;
 
@@ -237,7 +238,7 @@ class MultiAuth extends \SimpleSAML\Auth\Source
 
         $source = Auth\Source::getById($authId);
         if ($source === null) {
-            throw new \Exception('Invalid authentication source during logout: '.$source);
+            throw new \Exception('Invalid authentication source during logout: '.$authId);
         }
         // Then, do the logout on it
         $source->logout($state);
diff --git a/modules/multiauth/templates/selectsource.php b/modules/multiauth/templates/selectsource.tpl.php
similarity index 100%
rename from modules/multiauth/templates/selectsource.php
rename to modules/multiauth/templates/selectsource.tpl.php
diff --git a/modules/multiauth/www/selectsource.php b/modules/multiauth/www/selectsource.php
index f18d60fbebacd67392106f0e92251246bb060ecd..2038e30874279a5092d08ec918baa8c5102703e5 100644
--- a/modules/multiauth/www/selectsource.php
+++ b/modules/multiauth/www/selectsource.php
@@ -15,6 +15,8 @@ if (!array_key_exists('AuthState', $_REQUEST)) {
     throw new \SimpleSAML\Error\BadRequest('Missing AuthState parameter.');
 }
 $authStateId = $_REQUEST['AuthState'];
+
+/** @var array $state */
 $state = \SimpleSAML\Auth\State::loadState($authStateId, \SimpleSAML\Module\multiauth\Auth\Source\MultiAuth::STAGEID);
 
 if (array_key_exists("\SimpleSAML\Auth\Source.id", $state)) {
@@ -49,7 +51,7 @@ if (array_key_exists('multiauth:preselect', $state)) {
 }
 
 $globalConfig = \SimpleSAML\Configuration::getInstance();
-$t = new \SimpleSAML\XHTML\Template($globalConfig, 'multiauth:selectsource.php');
+$t = new \SimpleSAML\XHTML\Template($globalConfig, 'multiauth:selectsource.tpl.php');
 
 $defaultLanguage = $globalConfig->getString('language.default', 'en');
 $language = $t->getTranslator()->getLanguage()->getLanguage();
diff --git a/modules/portal/hooks/hook_htmlinject.php b/modules/portal/hooks/hook_htmlinject.php
index 72235d7335907ae61bf0aee342e10729f768cb70..e23e0d1e1fdd2d4fdb911580c2b14ff873b23df5 100644
--- a/modules/portal/hooks/hook_htmlinject.php
+++ b/modules/portal/hooks/hook_htmlinject.php
@@ -16,6 +16,8 @@ function portal_hook_htmlinject(&$hookinfo)
     $links = ['links' => []];
     \SimpleSAML\Module::callHooks('frontpage', $links);
 
+    assert(is_array($links));
+
     $portalConfig = \SimpleSAML\Configuration::getOptionalConfig('module_portal.php');
 
     $allLinks = [];
diff --git a/modules/portal/lib/Portal.php b/modules/portal/lib/Portal.php
index 69bcf63e221d390f22ee34a8062ef075d8854c06..0bbf78a7f4a6a330db2107973d38f99d0ba4ba7a 100644
--- a/modules/portal/lib/Portal.php
+++ b/modules/portal/lib/Portal.php
@@ -50,6 +50,9 @@ class Portal
      */
     public function isPortalized($thispage)
     {
+        if (!isset($this->config)) {
+            return false;
+        }
         foreach ($this->config as $set) {
             if (in_array($thispage, $set, true)) {
                 return true;
@@ -95,15 +98,19 @@ class Portal
             if (isset($page['shorttext'])) {
                 $name = $page['shorttext'];
             }
+
+            /** @var string $name */
+            $name = $t->t($name);
+
             if (!isset($page['href'])) {
                 $text .= '<li class="ui-state-default ui-corner-top ui-tabs-selected ui-state-active"><a href="#">'.
-                    $t->t($name).'</a></li>';
+                    $name.'</a></li>';
             } elseif ($pageid === $thispage) {
                 $text .= '<li class="ui-state-default ui-corner-top ui-tabs-selected ui-state-active"><a href="#">'.
-                    $t->t($name).'</a></li>';
+                    $name.'</a></li>';
             } else {
                 $text .= '<li class="ui-state-default ui-corner-top"><a href="'.$page['href'].'">'.
-                    $t->t($name).'</a></li>';
+                    $name.'</a></li>';
             }
         }
         $text .= '</ul>';
diff --git a/modules/saml/lib/Auth/Process/ExpectedAuthnContextClassRef.php b/modules/saml/lib/Auth/Process/ExpectedAuthnContextClassRef.php
index 7fde9ab41c0b0d04d4ebbe47127b41988fffe138..535ae1f0b8cc8bf3a7d5bba98046041743729516 100644
--- a/modules/saml/lib/Auth/Process/ExpectedAuthnContextClassRef.php
+++ b/modules/saml/lib/Auth/Process/ExpectedAuthnContextClassRef.php
@@ -73,7 +73,7 @@ class ExpectedAuthnContextClassRef extends \SimpleSAML\Auth\ProcessingFilter
     public function process(&$request)
     {
         assert(is_array($request));
-        assert(array_key_exists('Attributes', $request));
+        assert(array_key_exists('saml:sp:State', $request));
 
         $this->AuthnContextClassRef = $request['saml:sp:State']['saml:sp:AuthnContext'];
 
@@ -99,7 +99,7 @@ class ExpectedAuthnContextClassRef extends \SimpleSAML\Auth\ProcessingFilter
     protected function unauthorized(&$request)
     {
         Logger::error(
-            'ExpectedAuthnContextClassRef: Invalid authentication context: '.$this->AuthnContextClassRef.
+            'ExpectedAuthnContextClassRef: Invalid authentication context: '.strval($this->AuthnContextClassRef).
             '. Accepted values are: '.var_export($this->accepted, true)
         );
 
diff --git a/modules/saml/lib/Auth/Process/FilterScopes.php b/modules/saml/lib/Auth/Process/FilterScopes.php
index 7427be412bb5e2b12cd2be4dbce5c313a8da58c7..fd4089b925fba011e24b3de0010848d2b2129ba5 100644
--- a/modules/saml/lib/Auth/Process/FilterScopes.php
+++ b/modules/saml/lib/Auth/Process/FilterScopes.php
@@ -60,41 +60,43 @@ class FilterScopes extends \SimpleSAML\Auth\ProcessingFilter
             $validScopes = $src['scope'];
         }
 
-        foreach ($this->scopedAttributes as $attribute) {
-            if (!isset($request['Attributes'][$attribute])) {
-                continue;
-            }
-
-            $values = $request['Attributes'][$attribute];
-            $newValues = [];
-            foreach ($values as $value) {
-                $ep = Utils\Config\Metadata::getDefaultEndpoint($request['Source']['SingleSignOnService']);
-                $loc = $ep['Location'];
-                $host = parse_url($loc, PHP_URL_HOST);
-                if ($host === null) {
-                    $host = '';
+        $ep = Utils\Config\Metadata::getDefaultEndpoint($request['Source']['SingleSignOnService']);
+        if ($ep !== null) {
+            foreach ($this->scopedAttributes as $attribute) {
+                if (!isset($request['Attributes'][$attribute])) {
+                    continue;
                 }
-                $value_a = explode('@', $value, 2);
-                if (count($value_a) < 2) {
-                    $newValues[] = $value;
-                    continue; // there's no scope
+
+                $values = $request['Attributes'][$attribute];
+                $newValues = [];
+                foreach ($values as $value) {
+                    $loc = $ep['Location'];
+                    $host = parse_url($loc, PHP_URL_HOST);
+                    if ($host === null) {
+                        $host = '';
+                    }
+                    $value_a = explode('@', $value, 2);
+                    if (count($value_a) < 2) {
+                        $newValues[] = $value;
+                        continue; // there's no scope
+                    }
+                    $scope = $value_a[1];
+                    if (in_array($scope, $validScopes, true)) {
+                        $newValues[] = $value;
+                    } elseif (strpos($host, $scope) === strlen($host) - strlen($scope)) {
+                        $newValues[] = $value;
+                    } else {
+                        Logger::warning("Removing value '$value' for attribute '$attribute'. Undeclared scope.");
+                    }
                 }
-                $scope = $value_a[1];
-                if (in_array($scope, $validScopes, true)) {
-                    $newValues[] = $value;
-                } elseif (strpos($host, $scope) === strlen($host) - strlen($scope)) {
-                    $newValues[] = $value;
+
+                if (empty($newValues)) {
+                    Logger::warning("No suitable values for attribute '$attribute', removing it.");
+                    unset($request['Attributes'][$attribute]); // remove empty attributes
                 } else {
-                    Logger::warning("Removing value '$value' for attribute '$attribute'. Undeclared scope.");
+                    $request['Attributes'][$attribute] = $newValues;
                 }
             }
-
-            if (empty($newValues)) {
-                Logger::warning("No suitable values for attribute '$attribute', removing it.");
-                unset($request['Attributes'][$attribute]); // remove empty attributes
-            } else {
-                $request['Attributes'][$attribute] = $newValues;
-            }
         }
     }
 }
diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php
index d65b72c521446c1fc6c55e105aba79e4f32b25b1..f749bc63e08edf8ec433393e4e8875e051fc95df 100644
--- a/modules/saml/lib/Auth/Source/SP.php
+++ b/modules/saml/lib/Auth/Source/SP.php
@@ -52,7 +52,7 @@ class SP extends \SimpleSAML\Auth\Source
     /**
      * Flag to indicate whether to disable sending the Scoping element.
      *
-     * @var boolean|FALSE
+     * @var bool
      */
     private $disable_scoping;
 
@@ -409,6 +409,8 @@ class SP extends \SimpleSAML\Auth\Source
                         $this->protocols[] = Constants::NS_SAMLP;
                     }
                     break;
+                default:
+                    $acs = [];
             }
             $acs['index'] = $index;
             $endpoints[] = $acs;
@@ -554,23 +556,29 @@ class SP extends \SimpleSAML\Auth\Source
             }
 
             $nameId = $state['saml:NameID'];
-            $nid = new NameID();
-            if (!array_key_exists('Value', $nameId)) {
-                throw new \InvalidArgumentException('Missing "Value" in array, cannot create NameID from it.');
-            }
+            if (is_array($nameId)) {
+                // Must be an array > convert to object
 
-            $nid->setValue($nameId['Value']);
-            if (array_key_exists('NameQualifier', $nameId) && $nameId['NameQualifier'] !== null) {
-                $nid->setNameQualifier($nameId['NameQualifier']);
-            }
-            if (array_key_exists('SPNameQualifier', $nameId) && $nameId['SPNameQualifier'] !== null) {
-                $nid->setSPNameQualifier($nameId['SPNameQualifier']);
-            }
-            if (array_key_exists('SPProvidedID', $nameId) && $nameId['SPProvidedId'] !== null) {
-                $nid->setSPProvidedID($nameId['SPProvidedID']);
-            }
-            if (array_key_exists('Format', $nameId) && $nameId['Format'] !== null) {
-                $nid->setFormat($nameId['Format']);
+                $nid = new NameID();
+                if (!array_key_exists('Value', $nameId)) {
+                    throw new \InvalidArgumentException('Missing "Value" in array, cannot create NameID from it.');
+                }
+
+                $nid->setValue($nameId['Value']);
+                if (array_key_exists('NameQualifier', $nameId) && $nameId['NameQualifier'] !== null) {
+                    $nid->setNameQualifier($nameId['NameQualifier']);
+                }
+                if (array_key_exists('SPNameQualifier', $nameId) && $nameId['SPNameQualifier'] !== null) {
+                    $nid->setSPNameQualifier($nameId['SPNameQualifier']);
+                }
+                if (array_key_exists('SPProvidedID', $nameId) && $nameId['SPProvidedId'] !== null) {
+                    $nid->setSPProvidedID($nameId['SPProvidedID']);
+                }
+                if (array_key_exists('Format', $nameId) && $nameId['Format'] !== null) {
+                    $nid->setFormat($nameId['Format']);
+                }
+            } else {
+                $nid = $nameId;
             }
 
             $ar->setNameId($nid);
@@ -650,6 +658,7 @@ class SP extends \SimpleSAML\Auth\Source
 
         // Select appropriate SSO endpoint
         if ($ar->getProtocolBinding() === Constants::BINDING_HOK_SSO) {
+            /** @var array $dst */
             $dst = $idpMetadata->getDefaultEndpoint(
                 'SingleSignOnService',
                 [
@@ -657,6 +666,7 @@ class SP extends \SimpleSAML\Auth\Source
                 ]
             );
         } else {
+            /** @var array $dst */
             $dst = $idpMetadata->getEndpointPrioritizedByBinding(
                 'SingleSignOnService',
                 [
@@ -829,6 +839,10 @@ class SP extends \SimpleSAML\Auth\Source
 
         $session = Session::getSessionFromRequest();
         $data = $session->getAuthState($this->authId);
+        if ($data === null) {
+            throw new Error\NoState();
+        }
+
         foreach ($data as $k => $v) {
             $state[$k] = $v;
         }
@@ -1022,6 +1036,7 @@ class SP extends \SimpleSAML\Auth\Source
 
         $idpMetadata = $this->getIdPMetadata($idp);
 
+        /** @var array $endpoint */
         $endpoint = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', [
             Constants::BINDING_HTTP_REDIRECT,
             Constants::BINDING_HTTP_POST], false);
@@ -1182,6 +1197,8 @@ class SP extends \SimpleSAML\Auth\Source
         $state = $authProcState['saml:sp:State'];
 
         $sourceId = $state['saml:sp:AuthId'];
+
+        /** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
         $source = Auth\Source::getById($sourceId);
         if ($source === null) {
             throw new \Exception('Could not find authentication source with id '.$sourceId);
diff --git a/modules/saml/lib/IdP/SAML1.php b/modules/saml/lib/IdP/SAML1.php
index 1254f0d169d41b291cfcaf3f4bfbdf7f81d7af06..0cb5cf5eac1d3c6eddfdad0d9737e1baab5584e6 100644
--- a/modules/saml/lib/IdP/SAML1.php
+++ b/modules/saml/lib/IdP/SAML1.php
@@ -58,6 +58,7 @@ class SAML1
             $hasNewCert = true;
         }
 
+        /** @var array $certInfo */
         $certInfo = Utils\Crypto::loadPublicKey($config, true);
         $keys[] = [
             'type' => 'X509Certificate',
diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index f6cf7b5aa1e4e70bd2722e170fe7458dd22e0e9a..0a5c220d990a798510a08c8dde37e5243f6ec1f1 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -371,6 +371,10 @@ class SAML2
                 );
             } elseif ($issuer instanceof Issuer) {
                 $spEntityId = $issuer->getValue();
+                if ($spEntityId === null) {
+                    /* Without an issuer we have no way to respond to the message. */
+                    throw new Error\BadRequest('Received message on logout endpoint without issuer.');
+                }
             } else { // we got a string, old case
                 $spEntityId = $issuer;
             }
@@ -430,6 +434,9 @@ class SAML2
             $protocolBinding,
             $consumerIndex
         );
+        if ($acsEndpoint === null) {
+            throw new \Exception('Unable to use any of the ACS endpoints found for SP \''.$spEntityId.'\'');
+        }
 
         $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', [])));
         if ($ProxyCount === null) {
@@ -509,6 +516,7 @@ class SAML2
             'idpEntityID' => $idpMetadata->getString('entityid'),
         ]);
 
+        /** @var array $dst */
         $dst = $spMetadata->getEndpointPrioritizedByBinding(
             'SingleLogoutService',
             [
@@ -564,6 +572,8 @@ class SAML2
             'idpEntityID' => $idpMetadata->getString('entityid'),
             'partial'     => $partial
         ]);
+
+        /** @var array $dst */
         $dst = $spMetadata->getEndpointPrioritizedByBinding(
             'SingleLogoutService',
             [
@@ -601,6 +611,10 @@ class SAML2
             throw new Error\BadRequest('Received message on logout endpoint without issuer.');
         } elseif ($issuer instanceof Issuer) {
             $spEntityId = $issuer->getValue();
+            if ($spEntityId === null) {
+                /* Without an issuer we have no way to respond to the message. */
+                throw new Error\BadRequest('Received message on logout endpoint without issuer.');
+            }
         } else {
             $spEntityId = $issuer;
         }
@@ -682,6 +696,8 @@ class SAML2
             Constants::BINDING_HTTP_REDIRECT,
             Constants::BINDING_HTTP_POST
         ];
+
+        /** @var array $dst */
         $dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings);
 
         if ($dst['Binding'] === Constants::BINDING_HTTP_POST) {
@@ -793,6 +809,7 @@ class SAML2
             $hasNewCert = true;
         }
 
+        /** @var array $certInfo */
         $certInfo = Utils\Crypto::loadPublicKey($config, true);
         $keys[] = [
             'type' => 'X509Certificate',
@@ -803,6 +820,7 @@ class SAML2
         ];
 
         if ($config->hasValue('https.certificate')) {
+            /** @var array $httpsCert */
             $httpsCert = Utils\Crypto::loadPublicKey($config, true, 'https.');
             $keys[] = [
                 'type' => 'X509Certificate',
diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php
index 5ef132ed20b7491a0e8c2d589ba54529002c0cdd..42632885e8da05f5b78e4000cfcb2ebd04579cf7 100644
--- a/modules/saml/lib/Message.php
+++ b/modules/saml/lib/Message.php
@@ -44,9 +44,11 @@ class Message
         $dstPrivateKey = $dstMetadata->getString('signature.privatekey', null);
 
         if ($dstPrivateKey !== null) {
+            /** @var array $keyArray */
             $keyArray = Utils\Crypto::loadPrivateKey($dstMetadata, true, 'signature.');
             $certArray = Utils\Crypto::loadPublicKey($dstMetadata, false, 'signature.');
         } else {
+            /** @var array $keyArray */
             $keyArray = Utils\Crypto::loadPrivateKey($srcMetadata, true);
             $certArray = Utils\Crypto::loadPublicKey($srcMetadata, false);
         }
@@ -415,7 +417,11 @@ class Message
             }
         }
 
-        /** @var \Exception $lastException */
+        /**
+         * The annotation below is not working - See vimeo/psalm#1909
+         * @psalm-suppress InvalidThrow
+         * @var \Exception $lastException
+         */
         throw $lastException;
     }
 
diff --git a/modules/saml/templates/metadata.php b/modules/saml/templates/metadata.tpl.php
similarity index 100%
rename from modules/saml/templates/metadata.php
rename to modules/saml/templates/metadata.tpl.php
diff --git a/modules/saml/templates/proxy/invalid_session.php b/modules/saml/templates/proxy/invalid_session.tpl.php
similarity index 100%
rename from modules/saml/templates/proxy/invalid_session.php
rename to modules/saml/templates/proxy/invalid_session.tpl.php
diff --git a/modules/saml/www/idp/certs.php b/modules/saml/www/idp/certs.php
index 7a2597519abfc06c5aaf63e31dd4718b98bfca5b..7d4ba82aff3ce8edf27965a18e67319d5e9b204e 100644
--- a/modules/saml/www/idp/certs.php
+++ b/modules/saml/www/idp/certs.php
@@ -18,19 +18,22 @@ $idpmeta = $metadata->getMetaDataConfig($idpentityid, 'saml20-idp-hosted');
 
 switch ($_SERVER['PATH_INFO']) {
     case '/new_idp.crt':
-        $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, false, 'new_');
+        /** @var array $certInfo */
+        $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, true, 'new_');
         break;
     case '/idp.crt':
+        /** @var array $certInfo */
         $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, true);
         break;
     case '/https.crt':
+        /** @var array $certInfo */
         $certInfo = SimpleSAML\Utils\Crypto::loadPublicKey($idpmeta, true, 'https.');
         break;
     default:
         throw new \SimpleSAML\Error\NotFound('Unknown certificate.');
 }
-
 header('Content-Disposition: attachment; filename='.substr($_SERVER['PATH_INFO'], 1));
 header('Content-Type: application/x-x509-ca-cert');
+
 echo $certInfo['PEM'];
 exit(0);
diff --git a/modules/saml/www/proxy/invalid_session.php b/modules/saml/www/proxy/invalid_session.php
index 1afc151ae11fbc928a374f9d8e5062be007077bf..3b9c09610414b1712b7aed1c205a947d22fdcefa 100644
--- a/modules/saml/www/proxy/invalid_session.php
+++ b/modules/saml/www/proxy/invalid_session.php
@@ -16,9 +16,11 @@ if (!array_key_exists('AuthState', $_REQUEST)) {
 
 try {
     // try to get the state
+    /** @var array $state  State can never be null without a third argument */
     $state = \SimpleSAML\Auth\State::loadState($_REQUEST['AuthState'], 'saml:proxy:invalid_idp');
 } catch (\Exception $e) {
     // the user probably hit the back button after starting the logout, try to recover the state with another stage
+    /** @var array $state  State can never be null without a third argument */
     $state = \SimpleSAML\Auth\State::loadState($_REQUEST['AuthState'], 'core:Logout:afterbridge');
 
     // success! Try to continue with reauthentication, since we no longer have a valid session here
@@ -38,14 +40,15 @@ if (isset($_POST['cancel'])) {
 }
 
 if (isset($_POST['continue'])) {
-    // log the user out before being able to login again
-    $as = \SimpleSAML\Auth\Source::getById($state['saml:sp:AuthId'], '\SimpleSAML\Module\saml\Auth\Source\SP');
     /** @var \SimpleSAML\Module\saml\Auth\Source\SP $as */
+    $as = \SimpleSAML\Auth\Source::getById($state['saml:sp:AuthId'], '\SimpleSAML\Module\saml\Auth\Source\SP');
+
+    // log the user out before being able to login again
     $as->reauthLogout($state);
 }
 
 $cfg = \SimpleSAML\Configuration::getInstance();
-$template = new \SimpleSAML\XHTML\Template($cfg, 'saml:proxy/invalid_session.php');
+$template = new \SimpleSAML\XHTML\Template($cfg, 'saml:proxy/invalid_session.tpl.php');
 $translator = $template->getTranslator();
 $template->data['AuthState'] = (string) $_REQUEST['AuthState'];
 
diff --git a/modules/saml/www/sp/discoresp.php b/modules/saml/www/sp/discoresp.php
index 94ac7c1d77aad0da838d2b0e61142e4cee7063dd..1b53506e70a8fbf3af644753e22ae301c81ba0fb 100644
--- a/modules/saml/www/sp/discoresp.php
+++ b/modules/saml/www/sp/discoresp.php
@@ -11,7 +11,12 @@ if (!array_key_exists('AuthID', $_REQUEST)) {
 if (!array_key_exists('idpentityid', $_REQUEST)) {
     throw new \SimpleSAML\Error\BadRequest('Missing idpentityid to discovery service response handler');
 }
+
+/** @var array $state */
 $state = \SimpleSAML\Auth\State::loadState($_REQUEST['AuthID'], 'saml:sp:sso');
+if ($state === null) {
+    throw new \SimpleSAML\Error\NoState();
+}
 
 // Find authentication source
 assert(array_key_exists('saml:sp:AuthId', $state));
diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php
index a9557379db31c456c208605c4365607bed056601..ebf1a948177dedcc670330fb665b65ba70d4dd87 100644
--- a/modules/saml/www/sp/metadata.php
+++ b/modules/saml/www/sp/metadata.php
@@ -208,9 +208,11 @@ if ($spconfig->hasValue('contacts')) {
 // add technical contact
 $email = $config->getString('technicalcontact_email', 'na@example.org');
 if ($email && $email !== 'na@example.org') {
-    $techcontact['emailAddress'] = $email;
-    $techcontact['name'] = $config->getString('technicalcontact_name', null);
-    $techcontact['contactType'] = 'technical';
+    $techcontact = [
+        'emailAddress' => $email,
+        'name' => $config->getString('technicalcontact_name', null),
+        'contactType' => 'technical'
+    ];
     $metaArray20['contacts'][] = \SimpleSAML\Utils\Config\Metadata::getContact($techcontact);
 }
 
@@ -268,7 +270,7 @@ if (isset($metaArray20['attributes']) && is_array($metaArray20['attributes'])) {
 $xml = \SimpleSAML\Metadata\Signer::sign($xml, $spconfig->toArray(), 'SAML 2 SP');
 
 if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] == 'xhtml') {
-    $t = new \SimpleSAML\XHTML\Template($config, 'saml:metadata.php', 'admin');
+    $t = new \SimpleSAML\XHTML\Template($config, 'saml:metadata.tpl.php', 'admin');
 
     $t->data['clipboard.js'] = true;
     $t->data['header'] = 'saml20-sp'; // TODO: Replace with headerString in 2.0
diff --git a/modules/saml/www/sp/saml1-acs.php b/modules/saml/www/sp/saml1-acs.php
index db18627fd98e37ac800e7609708f392d23cb0057..6b981774ce113d96c7e72748fab7c3c4874f5a83 100644
--- a/modules/saml/www/sp/saml1-acs.php
+++ b/modules/saml/www/sp/saml1-acs.php
@@ -21,6 +21,7 @@ if ($end === false) {
 }
 $sourceId = substr($sourceId, 1, $end - 1);
 
+/** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
 $source = \SimpleSAML\Auth\Source::getById($sourceId, '\SimpleSAML\Module\saml\Auth\Source\SP');
 
 SimpleSAML\Logger::debug('Received SAML1 response');
@@ -35,6 +36,7 @@ if (preg_match('@^https?://@i', $target)) {
         'saml:sp:RelayState' => \SimpleSAML\Utils\HTTP::checkURLAllowed($target),
     ];
 } else {
+    /** @var array $state  State can never be null without a third argument */
     $state = \SimpleSAML\Auth\State::loadState($_REQUEST['TARGET'], 'saml:sp:sso');
 
     // Check that the authentication source is correct
@@ -66,7 +68,7 @@ if (array_key_exists('SAMLart', $_REQUEST)) {
     $responseXML = base64_decode($responseXML);
     $isValidated = false; /* Must check signature on response. */
 } else {
-    assert(false);
+    throw new \SimpleSAML\Error\BadRequest('Missing SAMLResponse or SAMLart parameter.');
 }
 
 $response = new \SimpleSAML\XML\Shib13\AuthnResponse();
diff --git a/modules/saml/www/sp/saml2-acs.php b/modules/saml/www/sp/saml2-acs.php
index 34c80bc766a0c4c9f6f19a183548d732269ae669..c2ec998ffb4aa7c5f7603214ab9ffb3c05b46e8c 100644
--- a/modules/saml/www/sp/saml2-acs.php
+++ b/modules/saml/www/sp/saml2-acs.php
@@ -9,9 +9,11 @@ if (!array_key_exists('PATH_INFO', $_SERVER)) {
 }
 
 $sourceId = substr($_SERVER['PATH_INFO'], 1);
+
+/** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
 $source = \SimpleSAML\Auth\Source::getById($sourceId, '\SimpleSAML\Module\saml\Auth\Source\SP');
-$spMetadata = $source->getMetadata();
 
+$spMetadata = $source->getMetadata();
 try {
     $b = \SAML2\Binding::getCurrentBinding();
 } catch (Exception $e) {
@@ -51,14 +53,17 @@ if ($issuer === null) {
     }
 }
 
-$idp = $issuer;
 if ($issuer instanceof \SAML2\XML\saml\Issuer) {
-    $idp = $idp->getValue();
+    $issuer = $issuer->getValue();
+    if ($issuer === null) {
+        // no issuer found in the assertions
+        throw new Exception('Missing <saml:Issuer> in message delivered to AssertionConsumerService.');
+    }
 }
 
 $session = \SimpleSAML\Session::getSessionFromRequest();
 $prevAuth = $session->getAuthData($sourceId, 'saml:sp:prevAuth');
-if ($prevAuth !== null && $prevAuth['id'] === $response->getId() && $prevAuth['issuer'] === $idp) {
+if ($prevAuth !== null && $prevAuth['id'] === $response->getId() && $prevAuth['issuer'] === $issuer) {
     /* OK, it looks like this message has the same issuer
      * and ID as the SP session we already have active. We
      * therefore assume that the user has somehow triggered
@@ -103,8 +108,8 @@ if ($state) {
 
     // check that the issuer is the one we are expecting
     assert(array_key_exists('ExpectedIssuer', $state));
-    if ($state['ExpectedIssuer'] !== $idp) {
-        $idpMetadata = $source->getIdPMetadata($idp);
+    if ($state['ExpectedIssuer'] !== $issuer) {
+        $idpMetadata = $source->getIdPMetadata($issuer);
         $idplist = $idpMetadata->getArrayize('IDPList', []);
         if (!in_array($state['ExpectedIssuer'], $idplist, true)) {
             SimpleSAML\Logger::warning('The issuer of the response not match to the identity provider we sent the request to.');
@@ -124,10 +129,10 @@ if ($state) {
     ];
 }
 
-SimpleSAML\Logger::debug('Received SAML2 Response from '.var_export($idp, true).'.');
+SimpleSAML\Logger::debug('Received SAML2 Response from '.var_export($issuer, true).'.');
 
 if (empty($idpMetadata)) {
-    $idpMetadata = $source->getIdPmetadata($idp);
+    $idpMetadata = $source->getIdPmetadata($issuer);
 }
 
 try {
@@ -138,13 +143,13 @@ try {
     \SimpleSAML\Auth\State::throwException($state, $e);
 }
 
-
 $authenticatingAuthority = null;
 $nameId = null;
 $sessionIndex = null;
 $expire = null;
 $attributes = [];
 $foundAuthnStatement = false;
+
 foreach ($assertions as $assertion) {
     // check for duplicate assertion (replay attack)
     $store = \SimpleSAML\Store::getInstance();
@@ -165,7 +170,6 @@ foreach ($assertions as $assertion) {
         $store->set('saml.AssertionReceived', $aID, true, $notOnOrAfter);
     }
 
-
     if ($authenticatingAuthority === null) {
         $authenticatingAuthority = $assertion->getAuthenticatingAuthority();
     }
@@ -186,6 +190,7 @@ foreach ($assertions as $assertion) {
         $foundAuthnStatement = true;
     }
 }
+$assertion = end($assertions);
 
 if (!$foundAuthnStatement) {
     $e = new \SimpleSAML\Error\Exception('No AuthnStatement found in assertion(s).');
@@ -206,7 +211,7 @@ if (!empty($nameId)) {
     // we need to save the NameID and SessionIndex for logout
     $logoutState = [
         'saml:logout:Type'         => 'saml2',
-        'saml:logout:IdP'          => $idp,
+        'saml:logout:IdP'          => $issuer,
         'saml:logout:NameID'       => $nameId,
         'saml:logout:SessionIndex' => $sessionIndex,
     ];
@@ -233,7 +238,7 @@ if (!empty($nameId)) {
 
 $state['LogoutState'] = $logoutState;
 $state['saml:AuthenticatingAuthority'] = $authenticatingAuthority;
-$state['saml:AuthenticatingAuthority'][] = $idp;
+$state['saml:AuthenticatingAuthority'][] = $issuer;
 $state['PersistentAuthData'][] = 'saml:AuthenticatingAuthority';
 $state['saml:AuthnInstant'] = $assertion->getAuthnInstant();
 $state['PersistentAuthData'][] = 'saml:AuthnInstant';
@@ -249,7 +254,7 @@ if ($expire !== null) {
 // note some information about the authentication, in case we receive the same response again
 $state['saml:sp:prevAuth'] = [
     'id'     => $response->getId(),
-    'issuer' => $idp,
+    'issuer' => $issuer,
 ];
 if (isset($state['\SimpleSAML\Auth\Source.ReturnURL'])) {
     $state['saml:sp:prevAuth']['redirect'] = $state['\SimpleSAML\Auth\Source.ReturnURL'];
@@ -258,5 +263,5 @@ if (isset($state['\SimpleSAML\Auth\Source.ReturnURL'])) {
 }
 $state['PersistentAuthData'][] = 'saml:sp:prevAuth';
 
-$source->handleResponse($state, $idp, $attributes);
+$source->handleResponse($state, $issuer, $attributes);
 assert(false);
diff --git a/modules/saml/www/sp/saml2-logout.php b/modules/saml/www/sp/saml2-logout.php
index e1814841232a37c915eee7b98721f86781a5a8cd..9f835d370616abae7d0fc4638f6e0fac770aea3b 100644
--- a/modules/saml/www/sp/saml2-logout.php
+++ b/modules/saml/www/sp/saml2-logout.php
@@ -12,11 +12,11 @@ if (!array_key_exists('PATH_INFO', $_SERVER)) {
 
 $sourceId = substr($_SERVER['PATH_INFO'], 1);
 
+/** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
 $source = \SimpleSAML\Auth\Source::getById($sourceId);
 if ($source === null) {
     throw new \Exception('Could not find authentication source with id '.$sourceId);
-}
-if (!($source instanceof \SimpleSAML\Module\saml\Auth\Source\SP)) {
+} elseif (!($source instanceof \SimpleSAML\Module\saml\Auth\Source\SP)) {
     throw new \SimpleSAML\Error\Exception('Source type changed?');
 }
 
@@ -35,15 +35,18 @@ try {
 $message = $binding->receive();
 
 $issuer = $message->getIssuer();
-if ($issuer === null) {
-    // Without an issuer we have no way to respond to the message.
-    throw new \SimpleSAML\Error\BadRequest('Received message on logout endpoint without issuer.');
-} elseif ($issuer instanceof \SAML2\XML\saml\Issuer) {
+if ($issuer instanceof \SAML2\XML\saml\Issuer) {
     $idpEntityId = $issuer->getValue();
 } else {
     $idpEntityId = $issuer;
 }
 
+if ($idpEntityId === null) {
+    // Without an issuer we have no way to respond to the message.
+    throw new \SimpleSAML\Error\BadRequest('Received message on logout endpoint without issuer.');
+}
+
+/** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */
 $spEntityId = $source->getEntityId();
 
 $metadata = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
@@ -122,6 +125,7 @@ if ($message instanceof \SAML2\LogoutResponse) {
         \SimpleSAML\Logger::warning('Logged out of '.$numLoggedOut.' of '.count($sessionIndexes).' sessions.');
     }
 
+    /** @var array $dst */
     $dst = $idpMetadata->getEndpointPrioritizedByBinding(
         'SingleLogoutService',
         [
diff --git a/psalm.xml b/psalm.xml
index c80d81a44d8b3029d616dae8cf0fde164a947343..9d3da2f5c9407bbbe2ec57dfc74cc6515ee7ab00 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -20,11 +20,23 @@
         <directory name="lib/SimpleSAML/Utils" />
         <directory name="lib/SimpleSAML/XHTML" />
         <directory name="lib/SimpleSAML/XML" />
+        <directory name="modules/admin" />
+        <directory name="modules/core" />
+        <directory name="modules/cron" />
+        <directory name="modules/exampleauth" />
+        <directory name="modules/multiauth" />
+        <directory name="modules/portal" />
+        <directory name="modules/saml" />
 
         <!-- Ignore deprecated classes -->
         <ignoreFiles>
             <file name="lib/SimpleSAML/Auth/DefaultAuth.php" />
             <file name="lib/SimpleSAML/Utilities.php" />
+
+            <!-- Ignore deprecated PHP-templates - Remove for 2.0 -->
+            <file name="modules/**/templates/*.tpl.php" />
+            <file name="modules/saml/templates/proxy/*.tpl.php" />
+            <file name="modules/saml/templates/sp/*.tpl.php" />
         </ignoreFiles>
     </projectFiles>
 
@@ -52,14 +64,29 @@
         <DocblockTypeContradiction errorLevel="suppress" />
         <RedundantConditionGivenDocblockType errorLevel="suppress" />
 
-        <!-- Suppress false-positive -->
-        <UnresolvableInclude errorLevel="suppress" />
         <!-- See #1141 -->
         <MissingDependency>
             <errorLevel type="suppress">
                 <file name="lib/SimpleSAML/HTTP/Router.php" />
             </errorLevel>
         </MissingDependency>
+
+        <!-- Ignore UnresolvableInclude on CLI-scripts -->
+        <UnresolvableInclude>
+            <errorLevel type="suppress">
+                <file name="bin/*.php" />
+                <file name="lib/SimpleSAML/XHTML/Template.php" />
+                <file name="modules/*/bin/*.php" />
+            </errorLevel>
+        </UnresolvableInclude>
+
+        <!-- Ignore MissingFile on www-scripts - Remove for 2.0 -->
+        <MissingFile>
+            <errorLevel type="suppress">
+                <file name="www/*.php" />
+                <file name="modules/*/www/*.php" />
+            </errorLevel>
+        </MissingFile>
     </issueHandlers>
 
     <stubs>