subfission / cas

Simple CAS Authentication for Laravel 5 - 10.
MIT License
153 stars 72 forks source link

Issues When Using Laravel Livewire Actions #102

Closed orware closed 1 year ago

orware commented 3 years ago

You may not be using Livewire yourself currently, but I've been attempting to utilize it a bit in my two most recent projects and in my current one I'm trying to use Livewire Actions as described here (https://laravel-livewire.com/docs/2.x/actions).

If you're not already familiar with the project, Livewire helps to enable dynamic/ajaxy type interactions but without necessarily needing to write any JavaScript code so it's been kind of neat to use from that perspective.

However, I've been running into some issues using it, and at the moment it appears to be related to the fact that I'm utilizing CAS authentication (via your package) in this web application.

I had ran into some issues last week but hadn't connected the dots until today.

What appears to be happening is that Livewire attempts to send a message like the following:

Request URL: http://mywebapp.localhost/livewire/message/attribute-request
Request Method: POST
Status Code: 200 OK
Remote Address: [::1]:80
Referrer Policy: strict-origin-when-cross-origin

And the request above (initiated from the Livewire JavaScript on the frontend) will pass along some data that tells Livewire which action to run on the server side:

[
  {
    "type": "callMethod",
    "payload": {
      "method": "addAttributeRequest",
      "params": [
        "202212.12055",
        "LTC"
      ]
    }
  }
]

The issue that appears to be happening is that when the data is being refreshed on the Livewire side on the server, I have a cas() function call that is triggering the Error: Internal script failure and that's what gets returned as the response to Livewire (and ends up breaking that part of the application until I refresh the page). If I refresh the page I'm on with this functionality, everything loads fine since I am authenticated already...what I don't understand is why trying to pull in an attribute isn't working via this particular path via Livewire's POST request above (and I'm not strong enough in Laravel yet to understand if the issue is correctable on my end within Laravel's configuration, with Livewire's somewhere, or if it's an actual potential bug in the cas package here).

I was hoping to be able to rely on the cas() function throughout my web application, but this issue has caused a bit of a problem so perhaps it is better if I cache/save certain values afterward into the Laravel session? I'm not sure what would be the best approach to take here but that seems like one that would potentially work.

The line in my Livewire component that is causing the issue is a cas()->getAttribute() call like the following:

$important_user_data = cas()->getAttribute('important_user_data');

Any thoughts or suggestions I could try on my end would be helpful (and if saving attributes returned from CAS into the Laravel session is the preferred way to go then it might be a helpful addition to the wiki, or maybe even provide a nice helper method that might allow that to be completed automatically with related tips for users to follow to access the data after the initial authentication?).

Thank you!

subfission commented 3 years ago

In your case you might be better off caching the cookie and testing for cookie existence, prior to performing a CAS auth. This should short circuit this issue entirely, albeit I don't really know your app.

You could do this in your middleware. EXAMPLE

IF my_local_session DOES NOT exists THEN 
  cas()->check_auth() AND create_my_local_session()
ELSE cas()->authenticate()
orware commented 3 years ago

Thank you for the reply!

I'm not sure if what you described about caching into a cookie would work in this case or not.

As far as my app itself, I'm not utilizing any of the Laravel user authentication layers (I don't have a users table in this application and instead was relying on the attributes returned from CAS to contain my user information from our centralized user repository connected to the CAS system). I'm currently just making use of the provided cas.auth middleware from the package.

The workaround I've currently come up with looks like the following:

if (!function_exists('get_important_cas_attribute')) {
    function get_important_cas_attribute() {
        if (cas()->isAuthenticated()) {
            if (cas()->hasAttribute('important_cas_attribute')) {
                try {
                    $value = cas()->getAttribute('important_cas_attribute');
                } catch(Exception $e) {
                    // Key addition here (only pull from session if the above results in an Exception
                    // which is what appears to be happening during those Livewire Action calls)
                    $value = session('important_cas_attribute');
                }

                if ($value) {
                    session(['important_cas_attribute' => $value]);
                }
            }
        } else {
            // Important to clear the session value out as well if the user isn't authenticated:
            session()->forget('important_cas_attribute');
        }

        return session('important_cas_attribute');
    }
}

My original approach would have looked a lot simpler (so something like this as an example):

if (!function_exists('get_important_cas_attribute')) {
    function get_important_cas_attribute() {
        $value = '';
        if (cas()->isAuthenticated()) {
            if (cas()->hasAttribute('important_cas_attribute')) {
                $value = cas()->getAttribute('important_cas_attribute');
            }
        }

        return $value;
    }
}

Or even more simply, using just this in the code (which does work for the normal page loads, it's just for those Livewire action method calls that this approach was breaking down):

$value = cas()->getAttribute('important_cas_attribute');

I don't feel like it's a perfect solution (I'd much rather have preferred not relying on needing to save anything into the session, and just use cas()->getAttribute() when I need to in order to retrieve a value), but it's better than it was with the solution above, and at least provides pulling the value out of the session as a fallback for the situations where the Exception appears to be getting triggered during those Laravel Livewire Action method calls.

I'm not sure if the example above makes the situation any more clear, but it was an unexpected hurdle to run into and attempt to work around (and it's a bit odd since everything I pulled up on Livewire side made it seem like it should be inheriting any middleware/authentication checks via what is setup already in my route file when it creates these AJAX requests to call the Livewire action methods, so that makes it seem like it should be aware of the CAS authentication status and be able to access those attributes, but it's a bit difficult to troubleshoot in more detail at the moment).

subfission commented 1 year ago

Late to this response party, but if you are viewing this, hopefully you solved your issue already.

General suggestion, as I am more on the security side these days, consider using OAuth tokens for your app instead if possible. CAS, although still widely used, is an antiquated approach for authZ and is more for authN. If you are relying on passing claims through SAML2, you will need to grab them, store them, and then recall them. JWTs are much more efficient at handling granularity of user restrictions, which it sounds like your app requires.

Sorry about the late reply, this project has been deprioritized for security projects.