lightSAML: Unknown InResponseTo

I get random critical errors like this in Symfony when users try to log in with Okta.

Uncaught PHP Exception LightSaml\Error\LightSamlContextException: "Unknown InResponseTo '_7ab8386ad4fcf931733b87639a2bb45a5505a0e9af'" at /services/applications/red-secure/vendor/lightsaml/lightsaml/src/LightSaml/Action/Profile/Inbound/StatusResponse/InResponseToValidatorAction.php line 55

Bug or something I can fix?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 2
  • Comments: 25 (1 by maintainers)

Most upvoted comments

We have figured out what this problem means. When your client accesses your login URL and gets redirected to the IDP a RequestID is generated and sent. When the IDP resolves the logging process and will return to your login_check URL it adds the InResponseTo as part of the request. The thing is that: 1 - InResponseTo is not a mandatory part of the SAML protocol, 2 - if the bundle sees the InResponseTo it tries to MATCH it. The matching is done through the RequestStore.

So in the same way that you implement (by following the tutorial SpBundle+symfony bridge) your own id_entry IdStore, you will have to implement a persistent RequestStore with it’s set, get and remove. The workflow will call your implementation when it first sets the id, then verify when it receives the request with the “InResponseTo” and if it matches, it will call for the removal. The logic behind the Request store is that it will make sure you only receive requests originating from your own system (/login -> setID -> IDP login verification -> /login_check + InResponseTo) and also, that you don’t receive a duplicated request (by removing the ID from your persistent resource ).

If it still doesn’t make sense let me know and I can share the code. Take a look here: https://gist.github.com/djonatanb/194118e5b15727331b8b6279d84e29fe (fixed the link)

I store requests in DB, using Doctrine. Symfony 4.4:

<?php
// src/Store/RequestStore.php
namespace App\Store;

use App\Entity\SAMLRequestEntry as RequestEntry;
use Doctrine\ORM\EntityManagerInterface;

use LightSaml\State\Request\RequestState;
use LightSaml\Store\Request\RequestStateStoreInterface;

class RequestStore implements RequestStateStoreInterface
{

    /** @var ObjectManager */
    private $manager;

    /**
     * @param EntityManagerInterface $manager
     */
    public function __construct(EntityManagerInterface $manager)
    {
        $this->manager = $manager;
    }

    /**
     * @param RequestState $state
     *
     * @return RequestStateStoreInterface
     */
    public function set(RequestState $state)
    {
        $requestEntry = $this->manager->find(RequestEntry::class, ['id' => $state->getId()]);
        if (null == $requestEntry) {
            $requestEntry = new RequestEntry();
        }
        $requestEntry->setId($state->getId())
            ->setState(serialize($state));
        $this->manager->persist($requestEntry);
        $this->manager->flush();

        return $this;
    }

    /**
     * @param string $id
     *
     * @return RequestState|null
     */
    public function get($id)
    {
        $requestEntry = $this->manager->find(RequestEntry::class, ['id' => $id]);
        if (null != $requestEntry) {
            return unserialize($requestEntry->getState());
        }

        return null;
    }

    /**
     * @param string $id
     *
     * @return bool
     */
    public function remove($id)
    {
        $requestEntry = $this->manager->find(RequestEntry::class, ['id' => $id]);
        if (null == $requestEntry) {
            return false;
        }

        $this->manager->remove($requestEntry);
        $this->manager->flush();

        return true;
    }

    public function clear()
    {
        //$this->setArray(array());
    }

}
<?php
// src/Entity/SAMLRequestEntry.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="saml_request_entry")
 * @ORM\Entity(repositoryClass="App\Repository\SAMLRequestEntryRepository")
 */
class SAMLRequestEntry
{
    /**
     * @var string
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     * @ORM\Column(type="string", length=255, nullable=false)
     */
    protected $id;

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    protected $state;


    public function getId(): ?string
    {
        return $this->id;
    }

    public function setId($id): self
    {
        $this->id = $id;

        return $this;
    }


    public function getState(): ?string
    {
        return $this->state;
    }

    public function setState(?string $state): self
    {
        $this->state = $state;

        return $this;
    }
}
# config/services.yaml
saml_request:
        class: App\Store\RequestStore
        arguments: ["@doctrine.orm.entity_manager"]
# config/packages/light_saml_symfony_bridge.yaml
light_saml_symfony_bridge:
    own:
        entity_id: "%auth_module_entity_id%"
        credentials:
            -
                certificate: "%auth_module_cert_path%"
                key:         "%auth_module_key_path%"
                password:    ~
    party:
        idp:
            files:
                - "%auth_module_xml_path%"
    store:
        id_state: saml_id_store
        request: saml_request

Sorry for the mess in my code, I have not got enough time to make it look nice, but for now it works for me