diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
index e171ab90bfd6f205a3a9994db1ef04fac2eeda28..f39c2c0478aa20531702b53795bc3293d855b317 100644
--- a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
+++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
@@ -2,6 +2,8 @@
 
 namespace SimpleSAML\Metadata;
 
+use SimpleSAML\Utils\ClearableState;
+
 /**
  * This file defines a class for metadata handling.
  *
@@ -9,7 +11,7 @@ namespace SimpleSAML\Metadata;
  * @package SimpleSAMLphp
  */
 
-class MetaDataStorageHandler
+class MetaDataStorageHandler implements ClearableState
 {
     /**
      * This static variable contains a reference to the current
@@ -358,4 +360,14 @@ class MetaDataStorageHandler
 
         return null;
     }
+
+    /**
+     * Clear any metadata cached.
+     * Allows for metadata configuration to be changed and reloaded during a given request. Most useful
+     * when running phpunit tests and needing to alter config.php and metadata sources between test cases
+     */
+    public static function clearInternalState()
+    {
+        self::$metadataHandler = null;
+    }
 }
diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index 4118b56b7dea87f673a44a04ade198e94ee990d5..764e0d1c3881aeb064513aa537fdb9cb40e2a042 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -265,7 +265,7 @@ class SAML2
             $supportedBindings[] = \SAML2\Constants::BINDING_PAOS;
         }
 
-        if (isset($_REQUEST['spentityid'])) {
+        if (isset($_REQUEST['spentityid']) || isset($_REQUEST['providerId'])) {
             /* IdP initiated authentication. */
 
             if (isset($_REQUEST['cookieTime'])) {
@@ -279,11 +279,13 @@ class SAML2
                 }
             }
 
-            $spEntityId = (string) $_REQUEST['spentityid'];
+            $spEntityId = (string) isset($_REQUEST['spentityid']) ? $_REQUEST['spentityid'] : $_REQUEST['providerId'];
             $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
 
             if (isset($_REQUEST['RelayState'])) {
                 $relayState = (string) $_REQUEST['RelayState'];
+            } elseif (isset($_REQUEST['target'])) {
+                $relayState = (string) $_REQUEST['target'];
             } else {
                 $relayState = null;
             }
@@ -300,13 +302,20 @@ class SAML2
                 $nameIDFormat = null;
             }
 
+            if (isset($_REQUEST['ConsumerURL'])) {
+                $consumerURL = (string)$_REQUEST['ConsumerURL'];
+            } elseif (isset($_REQUEST['shire'])) {
+                $consumerURL = (string)$_REQUEST['shire'];
+            } else {
+                $consumerURL = null;
+            }
+
             $requestId = null;
             $IDPList = [];
             $ProxyCount = null;
             $RequesterID = null;
             $forceAuthn = false;
             $isPassive = false;
-            $consumerURL = null;
             $consumerIndex = null;
             $extensions = null;
             $allowCreate = true;
@@ -402,11 +411,15 @@ class SAML2
 
         $sessionLostParams = [
             'spentityid' => $spEntityId,
-            'cookieTime' => time(),
         ];
         if ($relayState !== null) {
             $sessionLostParams['RelayState'] = $relayState;
         }
+        /*
+        Putting cookieTime as the last parameter makes unit testing easier since we don't need to handle a
+        changing time component in the middle of the url
+        */
+        $sessionLostParams['cookieTime'] = time();
 
         $sessionLostURL = \SimpleSAML\Utils\HTTP::addURLParameters(
             \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(),
diff --git a/tests/Utils/StateClearer.php b/tests/Utils/StateClearer.php
index ee2b686bf58af1f5a002e324efcc402833c64443..880c66bdf6332969503f23ec23743d7d0e6868b7 100644
--- a/tests/Utils/StateClearer.php
+++ b/tests/Utils/StateClearer.php
@@ -18,7 +18,7 @@ class StateClearer
      * Class that implement \SimpleSAML\Utils\ClearableState and should have clearInternalState called between tests
      * @var array
      */
-    private $clearableState = ['SimpleSAML\Configuration'];
+    private $clearableState = ['SimpleSAML\Configuration', 'SimpleSAML\Metadata\MetaDataStorageHandler'];
 
     /**
      * Environmental variables to unset
diff --git a/tests/modules/saml/lib/IdP/SAML2Test.php b/tests/modules/saml/lib/IdP/SAML2Test.php
index c6701cbd34cf550c513625050934c16ddba176d5..ef94baf9a28b7bf2510cfba38ba5d95d1820dcce 100644
--- a/tests/modules/saml/lib/IdP/SAML2Test.php
+++ b/tests/modules/saml/lib/IdP/SAML2Test.php
@@ -2,7 +2,12 @@
 
 namespace SimpleSAML\Test\Module\saml\IdP;
 
-class SAML2Test extends \PHPUnit_Framework_TestCase
+use SimpleSAML\Configuration;
+use SimpleSAML\IdP;
+use SimpleSAML\Module\saml\IdP\SAML2;
+use SimpleSAML\Test\Utils\ClearStateTestCase;
+
+class SAML2Test extends ClearStateTestCase
 {
     public function testProcessSOAPAuthnRequest()
     {
@@ -23,6 +28,9 @@ class SAML2Test extends \PHPUnit_Framework_TestCase
         $_SERVER['PHP_AUTH_PW'] = 'password';
         unset($_SERVER['PHP_AUTH_USER']);
         $state = [];
+        Configuration::loadFromArray([
+            'baseurlpath' => 'https://idp.example.com/',
+        ], '', 'simplesaml');
 
         \SimpleSAML\Module\saml\IdP\SAML2::processSOAPAuthnRequest($state);
     }
@@ -37,4 +45,195 @@ class SAML2Test extends \PHPUnit_Framework_TestCase
 
         \SimpleSAML\Module\saml\IdP\SAML2::processSOAPAuthnRequest($state);
     }
+
+    /**
+     * Default values for the state array expected to be generated at the start of logins
+     * @var array
+     */
+    private $defaultExpectedAuthState = [
+        'Responder' =>['\SimpleSAML\Module\saml\IdP\SAML2', 'sendResponse'],
+        '\SimpleSAML\Auth\State.exceptionFunc' => ['\SimpleSAML\Module\saml\IdP\SAML2', 'handleAuthError'],
+        'saml:RelayState' => null,
+        'saml:RequestId' => null,
+        'saml:IDPList' => [],
+        'saml:ProxyCount' => null,
+        'saml:RequesterID' => null,
+        'ForceAuthn' => false,
+        'isPassive' => false,
+        'saml:ConsumerURL' => 'SP-specific',
+        'saml:Binding' => 'SP-specific',
+        'saml:NameIDFormat' => null,
+        'saml:AllowCreate' => true,
+        'saml:Extensions' => null,
+        'saml:RequestedAuthnContext' => null];
+
+    /**
+     * Test that invoking the idp initiated endpoint with the minimum necessary parameters works.
+     */
+    public function testIdPInitiatedLoginMinimumParams()
+    {
+        $state = $this->idpInitiatedHelper(['spentityid' => 'https://some-sp-entity-id']);
+        $this->assertEquals('https://some-sp-entity-id', $state['SPMetadata']['entityid']);
+
+        $this->assertStringStartsWith(
+            'http://idp.examlple.com/saml2/idp/SSOService.php?spentityid=https%3A%2F%2Fsome-sp-entity-id&cookie',
+            $state['\SimpleSAML\Auth\State.restartURL']
+        );
+        unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion
+        unset($state['SPMetadata']); // entityid asserted above
+        unset($state['\SimpleSAML\Auth\State.restartURL']); // url contains a cookie time which varies by test
+
+        $expectedState = $this->defaultExpectedAuthState;
+        $expectedState[ 'saml:ConsumerURL'] = 'https://example.com/Shibboleth.sso/SAML2/POST';
+        $expectedState[ 'saml:Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST';
+
+        $this->assertEquals($expectedState, $state);
+    }
+
+    /**
+     * Test that invoking the idp initiated endpoint with the optional parameters works.
+     */
+    public function testIdPInitiatedLoginOptionalParams()
+    {
+        $state = $this->idpInitiatedHelper([
+            'spentityid' => 'https://some-sp-entity-id',
+            'RelayState' => 'http://relay',
+            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS',
+            'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
+
+        ]);
+        $this->assertEquals('https://some-sp-entity-id', $state['SPMetadata']['entityid']);
+
+        //currently only spentityid and relay state are used in the restart url.
+        $this->assertStringStartsWith(
+            'http://idp.examlple.com/saml2/idp/SSOService.php?'
+            . 'spentityid=https%3A%2F%2Fsome-sp-entity-id&RelayState=http%3A%2F%2Frelay&cookieTime',
+            $state['\SimpleSAML\Auth\State.restartURL']
+        );
+        unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion
+        unset($state['SPMetadata']); // entityid asserted above
+        unset($state['\SimpleSAML\Auth\State.restartURL']); // url contains a cookie time which varies by test
+
+        $expectedState = $this->defaultExpectedAuthState;
+        $expectedState['saml:ConsumerURL'] = 'https://example.com/Shibboleth.sso/SAML2/ECP';
+        $expectedState['saml:Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS';
+        $expectedState['saml:NameIDFormat'] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
+        $expectedState['saml:RelayState'] = 'http://relay';
+
+        $this->assertEquals($expectedState, $state);
+    }
+
+    /**
+     * Test that invoking the idp initiated endpoint using minimum shib params works
+     */
+    public function testIdPInitShibCompatyMinimumParams()
+    {
+        //https://wiki.shibboleth.net/confluence/display/IDP30/UnsolicitedSSOConfiguration
+        // Shib uses the param providerId instead of spentityid
+        $state = $this->idpInitiatedHelper(['providerId' => 'https://some-sp-entity-id']);
+        $this->assertEquals('https://some-sp-entity-id', $state['SPMetadata']['entityid']);
+
+        $this->assertStringStartsWith(
+            'http://idp.examlple.com/saml2/idp/SSOService.php?spentityid=https%3A%2F%2Fsome-sp-entity-id&cookie',
+            $state['\SimpleSAML\Auth\State.restartURL']
+        );
+        unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion
+        unset($state['SPMetadata']); // entityid asserted above
+        unset($state['\SimpleSAML\Auth\State.restartURL']); // url contains a cookie time which varies by test
+
+        $expectedState = $this->defaultExpectedAuthState;
+        $expectedState[ 'saml:ConsumerURL'] = 'https://example.com/Shibboleth.sso/SAML2/POST';
+        $expectedState[ 'saml:Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST';
+
+        $this->assertEquals($expectedState, $state);
+    }
+
+    /**
+     * Test that invoking the idp initiated endpoint using minimum shib params works
+     */
+    public function testIdPInitShibCompatOptionalParams()
+    {
+        $state = $this->idpInitiatedHelper([
+            'providerId' => 'https://some-sp-entity-id',
+            'target' => 'http://relay',
+            'shire' => 'https://example.com/Shibboleth.sso/SAML2/ECP',
+        ]);
+        $this->assertEquals('https://some-sp-entity-id', $state['SPMetadata']['entityid']);
+
+        //currently only spentityid and relay state are used in the restart url.
+        $this->assertStringStartsWith(
+            'http://idp.examlple.com/saml2/idp/SSOService.php?'
+            . 'spentityid=https%3A%2F%2Fsome-sp-entity-id&RelayState=http%3A%2F%2Frelay&cookieTime',
+            $state['\SimpleSAML\Auth\State.restartURL']
+        );
+        unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion
+        unset($state['SPMetadata']); // entityid asserted above
+        unset($state['\SimpleSAML\Auth\State.restartURL']); // url contains a cookie time which varies by test
+
+        $expectedState = $this->defaultExpectedAuthState;
+        $expectedState['saml:ConsumerURL'] = 'https://example.com/Shibboleth.sso/SAML2/ECP';
+        $expectedState['saml:Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS';
+        $expectedState['saml:RelayState'] = 'http://relay';
+
+        $this->assertEquals($expectedState, $state);
+    }
+
+    /**
+     * Invoke IDP initiated login with the given query parameters.
+     * Callers should validate the return state array or confirm appropriate exceptions are returned.
+     *
+     * @param array $queryParams
+     * @return string[] The state array used for handling the authentication request.
+     */
+    private function idpInitiatedHelper(array $queryParams)
+    {
+        /** @var $idpStub \PHPUnit_Framework_MockObject_MockObject|IdP */
+        $idpStub = $this->getMockBuilder(IdP::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $idpMetadata = Configuration::loadFromArray([
+            'entityid' => 'https://idp-entity.id',
+            'saml20.ecp' => true, //enable additional bindings so we can test selection logic
+        ]);
+
+        $idpStub->method("getConfig")
+            ->willReturn($idpMetadata);
+
+        // phpcs:disable
+        $spMetadataXml = <<< 'EOT'
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://some-sp-entity-id">
+   <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
+      <md:AssertionConsumerService index="1" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/Shibboleth.sso/SAML2/POST" />
+      <md:AssertionConsumerService index="2" Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://example.com/Shibboleth.sso/SAML2/ECP" />
+   </md:SPSSODescriptor>
+</EntityDescriptor>
+EOT;
+        // phpcs:enable
+
+        Configuration::loadFromArray([
+            'baseurlpath' => 'https://idp.example.com/',
+            'metadata.sources' => [
+                ["type" => "xml", 'xml' => $spMetadataXml],
+            ],
+        ], '', 'simplesaml');
+
+        // Since we aren't really running on a webserver some of the url calculations done, such as for restart url
+        // won't line up perfectly
+        $_REQUEST = $_REQUEST + $queryParams;
+        $_SERVER['HTTP_HOST'] = 'idp.examlple.com';
+        $_SERVER['REQUEST_URI'] = '/saml2/idp/SSOService.php?' . http_build_query($queryParams);
+
+
+        $state = [];
+        $idpStub->expects($this->once())
+            ->method('handleAuthenticationRequest')
+            ->with($this->callback(function ($arg) use (&$state) {
+                $state = $arg;
+                return true;
+            }));
+
+        SAML2::receiveAuthnRequest($idpStub);
+
+        return $state;
+    }
 }