Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auth::user() returns null with CORS requests

Tags:

cors

laravel

I have a site with a separate REST API on a subdomain, e.g. api.mysite.com, that I send CRUD requests to. The API sub-domain has this filter adding the appropriate headers to the response:

// Simple CORS handling
Route::filter('cors', function($route, $request, $response) {
    $origin = Request::header('Origin');
    $host = parse_url($origin, PHP_URL_HOST);

    // Don't send response for external domains.
    if (!in_array($host, Config::get('domains'))) {
        App::abort();
    }

    $response->headers->set('Access-Control-Allow-Origin', $origin);
    $response->headers->set('Access-Control-Allow-Headers', 'Accept, Accept-Encoding, Accept-Language, Content-Length, Content-Type');
    $response->headers->set('Access-Control-Allow-Methods', 'DELETE, GET, PATCH, POST, PUT');
    $response->headers->set('Access-Control-Allow-Credentials', 'true');
});

I'm also setting crossdomain: true and xhrFields: { withCredentials: true } on my jQuery $.ajax request. The requests manage to go through to the server, hit the appropriate routes, etc, but something is going wrong with the authentication process. Every time, Laravel acts as if the user hasn't logged in, causing requests to Auth::user() to return null. Inspecting the requests in Firebug shows that the Cookie header is sent in the request with the Laravel session id, but the server responds with SetCookie, as if trying to start a new session. I'm probably doing something dumb here, but I'm at my wits end trying to determine just what.

Update: From some debugging, I've found something interesting out. Not sure what it means, yet. To get to the page in question, the user must be logged in. Therefore, there is a laravel_session cookie in the browser when the page loads. I then send off a couple of (cross-domain) AJAX requests by interacting with the page. The first request has no cookie set at all and gets a new laravel_session cookie set from the server. The second request then includes that cookie, but the response to it sends back yet another new cookie, as if the back-end never got the memo about the first. I'm starting to wonder if this isn't something to do with cookie domains or some-such.

like image 202
Michael Cordingley Avatar asked Apr 24 '14 14:04

Michael Cordingley


1 Answers

I finally figured it out.

First of all, the xhr and route filter were both configured correctly. The root cause of this problem was definitely a cookie issue.

The cookie domain for laravel_session was not initially set. Browsers interpret that to be a short-hand for "current domain". That is, app.mysite.com What I needed to do was explicitly set the value in Laravel's session.domain configuration to be ".mysite.com" That way, the same session cookie becomes available to app.mysite.com, api.mysite.com, and any other sub-domains of mysite.com Problem solved!

That said, there were two gotchas that I tripped over on my way to this solution:

  • The first was that cookies cannot be set for TLDs. I normally set up my development domain to be something like "mysite", leaving off the TLD. As far as DNS is concerned, that IS a TLD, and the cookies will fail. Once I changed my fake domain for development over to "mysite.dev", "mysite" was no longer a TLD, and the browser accepted cookies for it.
  • The second was that I had to remove my session cookie from my browser before I could log in to the new and different domain. I don't know why this is the case, but remember to clear your session cookie out when doing this.
    • Obviously, this is too much to ask your users to do. If you're putting this kind of change out into an already deployed site, you need to consider how to get your users migrated over to the cookie changes.
    • Since Laravel session cookies are set with an expiry time not too far into the future, one option is to simply deploy such changes when your users are not very active and accept the app appearing to be broken until all session cookies have expired. Only your currently and recently active users will be affected and the "solution" is nice and easy. But your app is broken for a while.
    • The other option is to change the name of the session cookie away from "laravel_session" at the same time that you change the cookie domain. This way, the new cookie sits beside the old one while the old ones expire and your app remains unbroken.
like image 114
Michael Cordingley Avatar answered Nov 04 '22 13:11

Michael Cordingley