aacotroneo / laravel-saml2

A Laravel 5 package for Saml2 integration as a SP (service provider) based on the simple OneLogin toolkit
MIT License
567 stars 238 forks source link

Authentication persistence #39

Open spinhalf-com opened 8 years ago

spinhalf-com commented 8 years ago

Hi Alejandro

I've successfully integrated the library in Laravel 5.0, got the login/redirecting working properly, but cannot access SSO session persistence. What this means is that the isAuthenticated() function in Saml2Auth.php returns true if it is executed in the same request where the login process succeeds, but false at any other time, even if it's in the next request immediately after the successful login.

This makes it very hard to design my middleware properly, because ideally I would run the isAuthenticated() function as a check and only allow the Saml2::login() to proceed when the isAuthenticated() function returns false. However, it always returns false, thus causing infinite looping redirects, because the login function is called on a valid session on all requests after the initial login.

Have you had this problem before?

Thanks

John

aacotroneo commented 8 years ago

Hi John,

That's by design, we don't maintain any kind of session because it's not part of saml. Like you said that method will return true only in the same request if the response was valid (it can't tell without redirecting in saml). We fire an event to announce the authentication, that's the hook in which you usually open your local session (in memory, a database, memcached, or create a token, we don't care) and implement your local isAuthenticated to use in the middleware. You also have the logout hooks so you can destroy that session too and keep it in sync. There are a few hints on the readme. Cheers Alejandro On May 16, 2016 1:06 PM, "John Riordan" notifications@github.com wrote:

Hi Alejandro

I've successfully integrated the library in Laravel 5.0, got the login/redirecting working properly, but cannot access SSO session persistence. What this means is that the isAuthenticated() function in Saml2Auth.php returns true if it is executed in the same request where the login process succeeds, but false at any other time, even if it's in the next request immediately after the successful login.

This makes it very hard to design my middleware properly, because ideally I would run the isAuthenticated() function as a check and only allow the Saml2::login() to proceed when the isAuthenticated() function returns false. However, it always returns false, thus causing infinite looping redirects, because the login function is called on a valid session on all requests after the initial login.

Have you had this problem before?

Thanks

John

— You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub https://github.com/aacotroneo/laravel-saml2/issues/39

spinhalf-com commented 8 years ago

Hi Alejandro

Thanks very much for the quick response, very helpful.

Kind Regards

John

izhekov commented 8 years ago

Trying to follow the setup guide. I'm having issue registering the local user session. I created a SamlEventListener hooking it to Saml2LoginEvent. When the event is fired, the handle is properly trigerred so I'm executing Auth::login($laravelUser) which seems successful at that point. Although when the page is reloaded and the middleware executed, $this->auth->guest() returns true, going into an infinite loop. Any ideas?

gavinbenda commented 8 years ago

I'm also experiencing something similar, have setup as per the instructions, added a listener, everything works nicely by the looks of it - however it's in a constant redirect loop when I add the Auth::login($user) code into the listener as per the example displayed here.

izhekov commented 8 years ago

I actually figured it out (at least the case I had). The session middleware was not active which prevents the user data to be saved. This is because the library's assertion consumer service route by default is not within any middleware group. Check your saml2_settings.php, there should be a 'routesMiddleware' variable and adjust it accordingly. In my case I set it to 'routesMiddleware' => ['web'] which fixed the problem.

gavinbenda commented 8 years ago

Thanks! Yep got the web middleware active in the config file already.

I have in Listeners/ADFSLoginListener.php

public function handle(Saml2LoginEvent $event)
{

        $user = $event->getSaml2User();

        // user lookup code $laravel_user = ....

        Auth::login($laravel_user);

}

Routes look like this:


|        | POST                           | saml2/acs                                     | saml_acs      | Aacotroneo\Saml2\Http\Controllers\Saml2Controller@acs         | web                          |
|        | GET|HEAD                       | saml2/login                                   | saml_login    | Aacotroneo\Saml2\Http\Controllers\Saml2Controller@login       | web                          |
|        | GET|HEAD                       | saml2/logout                                  | saml_logout   | Aacotroneo\Saml2\Http\Controllers\Saml2Controller@logout      | web                          |
|        | GET|HEAD                       | saml2/metadata                                | saml_metadata | Aacotroneo\Saml2\Http\Controllers\Saml2Controller@metadata    | web                          |
|        | GET|HEAD                       | saml2/sls                                     | saml_sls      | Aacotroneo\Saml2\Http\Controllers\Saml2Controller@sls         | web                          |

If I disable the web middleware, I can dd the user details fine, it's picking up both the SAML attributes and the laravel_user I want to 'login'. But as soon as I enable the web middleware, it gets into a redirect loop between the /acs and the IDP going back and forth... not sure how to troubleshoot from here without pulling the whole lot apart.

izhekov commented 8 years ago

Check the session store you use is working? Can you send me your routes (i.e. web.php or wherever they are), the saml2_settings.php and Kernel.php ?

gavinbenda commented 8 years ago

Got it working! Your question about Kernel.php was the issue. This app I'm working on uses quite a lot of extra middleware, I created a new dedicated 'session' middlewaregroup which only uses the session/cookie middleware, and we're up and running. Thanks!

shaan207 commented 7 years ago

Hey I'm new to Laravel. Can Someone help me with LoginListener Event?? I'm confuse where to create it and how to call it. Thanks.

shulard commented 7 years ago

@gavinbenda, can you gave more details about your resolution ? I'm currently stuck in the redirect process...

My session id is not the same inside saml2/acs route than on my application home page after a successful authentication...

I've also applies that Middlewares on SAML2 routes :

\Taskl\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,

What's the missing point... ?

gavinbenda commented 7 years ago

@shulard I stuck my SAML2 routes inside the 'web' middleware.

Route::group(['middleware' => ['web']], function () {
....
});
shulard commented 7 years ago

Hello @gavinbenda,

Thanks for your message... My problem didn't come from the middlewares, now it works 😄.

mortennajbjerg commented 7 years ago

I had this problem too. Solved it with the 'web' middleware in the saml2_settings.php configuration file. Perhaps it would be an idea to mention the 'web' middelware option for laravel 5.4 in the documentation ... I take it that this could be a very common pitfall for everybody using the library with Laravel 5.4+.

ReArmedHalo commented 6 years ago

I was having the same issue, adding the web middle resolved the issue for me. However, I needed to comment out the VerifyCsrfToken middleware from App\Http\Kernal.php otherwise I got a "Sorry this page has expired" error, I am using Laravel 5.5. Anyone know how I can work around that? I obviously don't want to disable CSRF protection for all routes but I am not that advanced in Laravel to know how to solve this one :)

shulard commented 6 years ago

Hello @ReArmedHalo, I think that you mustn't use the default web Middleware shortcut for your saml2 routes. You must define only the required middleware for that specific part which are not the same than the rest of your application.

With that approach, you can disable CSRF only for the SAML2 part and let it active on the rest of your app.

ReArmedHalo commented 6 years ago

@shulard Thanks for the reply! I think I get what you are saying, I've added the below to my Kernel.php file

protected $middlewareGroups = [
    'web' => [ ... ],

    'saml' => [
        \Illuminate\Session\Middleware\StartSession::class,
    ],
];

And in my SAML2 settings file, under routesMiddleware I've set to $routedMiddleware => [ 'saml' ],

Is that accurate?


And just now in the config file in the repo here noticed it says Session middleware... Perhaps that detail could get added to the setup instructions?

shulard commented 6 years ago

I think you get the point 😄. With that approach you can split middleware logic and it's lighter.

axeloz commented 6 years ago

I finally made it work ! For some reason, adding a group middleware for SAML didn't work either: sessions still didn't persist. So I'm using the web middleware that I set into the saml2_settings config file. Then, I got a problem with the CSRF verification. So I edited the VerifyCsrfToken middleware:

protected $except = [
    'saml/*'
];

It's working now... Thanks all

fernandocoronatomf commented 6 years ago

Any change someone got it working with JWT? I log the user in on the event listener but then the user is redirect to / and the token is not present anymore.

aacotroneo commented 6 years ago

yep, the login event will give you the $userDataand after that you are on your own to do whatever you need with it (create a JWT in this case). If you are redirecting to / you can use session to preserve it, or POST it. If you need to send it to a SPA you might need some other strategy as ajax and SAML redirects don't play well.

fernandocoronatomf commented 6 years ago

@aacotroneo Yes, I got to the point where I create the JWT but after that it redirects me to somewhere else.. and yes, I have to pass it to a SPA

aacotroneo commented 6 years ago

I mean.. after that event, the library doesn't do anything else... it's all your code. If you call Auth::login() or have an auth-middleware configured to redirect, it will redirect (try commenting out all the code in the function and it will die there). To make the token available to the SPA you'll need to render the SPA with the token, or create some popup and send it with a message to the caller, or send it as a url param (or post if you have a server) and/or other... but all that goes outside the library.

fernandocoronatomf commented 6 years ago

@aacotroneo Yeah thanks for the suggestions, you have given me some good options... But after the event I am still being redirected to "/"... I thought was the library redirecting me but I will have a look.

Cyrille37 commented 6 years ago

the Middleware\StartSession is not enough, use :

In class App\Http\Kernel:

        'saml' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
        ],
fernandocoronatomf commented 6 years ago

@aacotroneo I got the login with JTW working, thanks!

Any idea about the logout? on the Saml2LogoutEvent we don't have any information about which user is logged out, so on the listener I can't know the user id for example, therefore, I can't invalidate the token.

juliardi commented 3 years ago

I still got a problem with authentication persistence. I've set up a middleware group in App\Http\Kernel like @Cyrille37 suggested.

'saml' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
        ],

And I've also configure the routesMiddleware in saml2_settings.php

/**
   * which middleware group to use for the saml routes
   * Laravel 5.2 will need a group which includes StartSession
   */
    'routesMiddleware' => ['saml'],

But still, after login the Auth::check() still return false. Do I miss something ?

========================================================= EDIT : OK, I've found the problem. It is because my default guard in config/auth.php is set to api. After I changed it to web the auth can persist.

Emad-Hamza commented 3 years ago

I have the same problem on laravel 8. Tried all the variations of the saml middleware group in this thread and tried using the web middlegroup.

hackerkok commented 3 years ago

@Emad-Hamza Same here. Did you find the solution by any chance?

zubaer-ahammed commented 2 years ago

I have the same problem on laravel 8. Tried all the variations of the saml middleware group in this thread and tried using the web middlegroup.

I am facing this issue too with Laravel 8

hackerkok commented 2 years ago

I have the same problem on laravel 8. Tried all the variations of the saml middleware group in this thread and tried using the web middlegroup.

I am facing this issue too with Laravel 8

In my case, I was not storing the logged in user info in the db (because I didn't have one for my project). Created a db, stored the user info and problem solved.

auth()->login() and auth()->check() needs the user to be stored in the database.

zubaer-ahammed commented 2 years ago

Found solution at last! So, Auth::login($user) was not working for me even after trying everything. (Added middlewares, etc). It worked for me after removing all dump(), var_dump(), die(), etc. from the Listener! :D

david34corbalan commented 2 years ago

hi, i am in laravel 7.0 and not persisted session eventLogin

my code: “app/Providers/EventServiceProvider.php”: image

“Kernel.php” image

image

“web.php” Route::middleware(['checkSaml'])->group(function () { ……… }); i need help!!!

andreadme commented 1 year ago

hello, I am having the same issue.

app\Http\Kernel.php

'saml' => [
    \App\Http\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
]

Inside my EventServiceProvider

protected $listen = [
    Registered::class => [
        SendEmailVerificationNotification::class,
    ],
    SignedIn::class => [
        UserLoggedIn::class,
    ],
];

UserLoggedIn file

/**
     * Handle the event.
     *
     * @param  Slides\Saml2\Events\SignedIn  $event
     * @return void
     */
    public function handle(SignedIn $event)
    {
            $messageId = $event->auth->getLastMessageId();
            // your own code preventing reuse of a $messageId to stop replay attacks
            $samlUser = $event->auth->getSaml2User();
            $userData = [
                'id' => $samlUser->getUserId(),
                'attributes' => $samlUser->getAttributes(),
                'assertion' => $samlUser->getRawSamlAssertion()
            ];

            $searchUser = User::where('email', $userData['attributes']['email'])->first();

            if ($searchUser) {
                Auth::login($searchUser);
                Session::put('userData', "helloworld2");
            } else {
                $newUser = User::create([
                    'name' => $userData['attributes']['first_name'][0],
                    'email' => $userData['attributes']['email'][0],
                    'role' => 'admin',
                    'password' => bcrypt('password')
                ]);

                Auth::login($newUser);
                Session::put('userData', "helloworld");
            }

                Session::save();
        }

saml2.php 'routesMiddleware' => ['saml'], Already tried the fixes stated above but to no avail

hackerkok commented 1 year ago

hello, I am having the same issue.

app\Http\Kernel.php

'saml' => [
    \App\Http\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
]

Inside my EventServiceProvider

protected $listen = [
    Registered::class => [
        SendEmailVerificationNotification::class,
    ],
    SignedIn::class => [
        UserLoggedIn::class,
    ],
];

UserLoggedIn file

/**
     * Handle the event.
     *
     * @param  Slides\Saml2\Events\SignedIn  $event
     * @return void
     */
    public function handle(SignedIn $event)
    {
            $messageId = $event->auth->getLastMessageId();
            // your own code preventing reuse of a $messageId to stop replay attacks
            $samlUser = $event->auth->getSaml2User();
            $userData = [
                'id' => $samlUser->getUserId(),
                'attributes' => $samlUser->getAttributes(),
                'assertion' => $samlUser->getRawSamlAssertion()
            ];

            $searchUser = User::where('email', $userData['attributes']['email'])->first();

            if ($searchUser) {
                Auth::login($searchUser);
                Session::put('userData', "helloworld2");
            } else {
                $newUser = User::create([
                    'name' => $userData['attributes']['first_name'][0],
                    'email' => $userData['attributes']['email'][0],
                    'role' => 'admin',
                    'password' => bcrypt('password')
                ]);

                Auth::login($newUser);
                Session::put('userData', "helloworld");
            }

                Session::save();
        }

saml2.php 'routesMiddleware' => ['saml'], Already tried the fixes stated above but to no avail

try something like this:

EventServiceProvider:

    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        \Aacotroneo\Saml2\Events\Saml2LoginEvent::class => [
            Saml2Login::class
        ]
    ];

Saml2Login:

class Saml2Login
{

    public function __construct()
    {
        //
    }

    public function handle(\Aacotroneo\Saml2\Events\Saml2LoginEvent $event)
    {
        $user     = $event->getSaml2User();
        $userData = [
            'id'         => $user->getUserId(),
            'attributes' => $user->getAttributes(),
            'assertion'  => $user->getRawSamlAssertion(),
            'sessionIndex' => $user->getSessionIndex(),
            'nameId' => $user->getNameId()
        ];
        $laravelUser = User::firstOrCreate(
            [
                'email' => $userData["attributes"]["email"][0] ?? null,
            ],
            [
                'name' => $userData["attributes"]["first_name"][0] ?? null,
                'surname' => $userData["attributes"]["last_name"][0] ?? null,
                'sector_code' => $userData["attributes"]["sector"][0] ?? null,
            ]
        );
        // dd($user, $userData,$laravelUser);

        session(['sessionIndex' => $userData['sessionIndex']]);
        session(['nameId' => $userData['nameId']]);
        Auth::login($laravelUser);
    }
}
andreadme commented 1 year ago

it's not working even if I saved in global session, I want the userData to be saved in session then later used it in controller. However, even if I logged using Event listener, I still get a null value when retrieving the Session in controller. All routes are in a group called 'saml' and in Kernel file, I've already added the StartSession class.