laravelsaml-2.0

How to check if the user is logged in to SSO using SAML on all routes?


I've followed the steps of the package 24Slides/laravel-saml2 to setup SAML on my app: https://github.com/24Slides/laravel-saml2?tab=readme-ov-file#laravel-54-saml-service-provider

The setup is working, and a login URL was created:

https://example.com/saml2/some-long-uuid/login

When I hit this endpoint, it redirects to the login URL on the Microsoft Azure login website of the company.

However, I want to add a check if a user is logged in to every route on web.php. But I couldn't find the right way to do that, and there is no mention of it in the docs.

What I want to do is that whenever a user is visiting any URL on web.php, it will check if the user is logged via the SAML2 SSO, and if yes, continue to the link, otherwise, redirect him to the Microsoft Azure login page.

So far I was only able to do it if I specifically visit the login https://example.com/saml2/some-long-uuid/login url


Solution

  • What I want to do is that whenever a user is visiting any URL on web.php, it will check if the user is logged via the SAML2 SSO, and if yes, continue to the link, otherwise, redirect him to the Microsoft Azure login page.

    You cannot check if the user is logged in via SAML2 SSO. Only the IdP itself knows that.

    The only way for your website to determine user status is by redirecting to the IdP and receiving a valid SAML2 assertion back (which is what you're already doing) – but it wouldn't be practical to redirect to the IdP for every single request (possible in theory but very slow), so you have to cache this information in a session.

    I don't know much about Laravel, but generally speaking it works almost exactly like with a regular username/password login page. The only difference is that instead of validating a password, you validate the signature of the SAML assertion (and extract the username from it).

    In the single Laravel app that I'd converted to SAML SSO, the app had already been using Sentinel to handle password authentication, so the controller for the "login" route became a lot like this:

    if ($user = Sentinel::findByCredentials(["userid" => $saml_uid])) {
        Sentinel::login($user);
        $this->updateFromSaml($user); /* updates the user's display name, email, etc */
        return redirect()->route(self::DEFAULT_PAGE);
    }
    

    For comparison, in a few other PHP apps before that, I used SimpleSAMLphp as the SAML2 component (it is not only a standalone IdP but also an embeddable SP) – it manages its own SP session orthogonal to my app's session, so I could just call $sp->requireAuth() at the top of every page, and despite appearance this would only redirect to IdP once and automatically cache the result in a session thereafter. (In fact, for the very smallest apps I didn't even bother with my own sessions, relying on SimpleSAMLphp to handle that.)

    Let's say I create a session as I would normally do, and this session is 1 hour long. Then the user logs out of the SAML session from another website, and then he returns back to my app before that 1 hour passes - he is still considered logged in even though he logged out entirely from the SAML session

    SAML2 has a "Single Logout" (SLO) mechanism to handle this, which works most of the time:

    But generally, logout desync is a "fact of life" with SSO, whether SAML or otherwise.

    Though on private/personal devices, I'd say that 1 hour is not a big deal. It is better have the users lock their whole desktop session at OS level (e.g. hit Win+L) when leaving PCs unattended, instead of logging out of websites manually.

    And for public devices, it helps if your own session sets a "session cookie" (without an Expires) that disappears whenever the browser is closed, so that you can ask the user to fully close the browser upon logout.