2

I have a Laravel 9 api only installation. Login/Logout and any other api routes are working as they should. I'm trying to set a httpOnly cookie on login for use with my ReactJS frontend but it is getting rejected for invalid domain.

The cookie will be used for a refresh token for persistent login. I have it all working in postman but isn't working in the browser with my React frontend. I get both access token and refresh token(that only works on the refresh routes) and things are timing out as expected. My refresh token route deletes the old tokens and refreshes them.

Currently I have have the front end and the backend domains, lets call them

api.myapp.com -> API
my-app.com:3000 -> React Dev Server

Here is my login function:

public function login(Request $request) {
    $fields = $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'remember_me' => 'boolean',
    ]);
    
    // Check email
    $user = User::where('email', $fields['email'])->first();
    
    // Check Password
    if(!$user || !Hash::check($fields['password'], $user->password)){
        return response([
            'message' => 'Invalid Login'
        ], 401);
    }
    
    // Delete old tokens
    $user->tokens()->delete();
    
    // Create new tokens
    $token = $user->createAuthToken('api')->plainTextToken;
    $refresh = $user->createRefreshToken('api')->plainTextToken;
    
    // Create Cookie
    $cookie = Cookie::create('myapp-token')
    ->withValue($refresh)
    ->withExpires(strtotime("+6 months"))
    ->withSecure(true)
    ->withHttpOnly(true)
    ->withDomain("my-app.com")
    ->withSameSite("none");
    
    $response = [
        'user' => $user,
        'token' => $token,
    ];
          
    
    // Return user, token and set refresh cookie
    return response($response, 201)->cookie($cookie);
}

The cookie is being passed in the header correctly, but isn't actually being set in the browser and I'm getting the error "Cookie 'my app-token' has been rejected for invalid domain. Using the Cookie::create function was the only way I could get it to set sameSite to none assuming I needed that.

The cookie gets handled with middleware and passes it back in as a bearer token for authorization.

public function handle(Request $request, Closure $next)
{   
    $cookie_name = env('AUTH_COOKIE_NAME');
    if (!$request->bearerToken()) {
        if ($request->hasCookie($cookie_name)) {
            $token = $request->cookie($cookie_name);
    
            $request->headers->add([
                'Authorization' => 'Bearer ' . $token
            ]);
        }
    }
    
    return $next($request);
}

Does this error come from SANCTUM_STATEFUL_DOMAINS or SESSION_DOMAIN? Or something else entirely? And what does it need to be set to? My API domain for my frontend domain? I feel like I have tried every combination I have found but nothing has worked yet, so I must be missing something.

I've tried both ways to add the cookie, both with and without queue

use Symfony\Component\HttpFoundation\Cookie;
use Illuminate\Support\Facades\Cookie;

With facades and queue It is giving me a csrf-cookie mismatch error. Which I know I can use the sanctum/csrf-cookie route to get that cookie if I need to go that route. If I understand correctly that isn't needed for api only routes.

Kernal.php middleware groups for api

'api' => [
        \App\Http\Middleware\AddAuthTokenHeader::class,
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    ],

I've been reading everything I can and lots of trial and error but I keep getting stuck. Once the cookie is set correctly I assume Axios on my frontend will pass the cookie on to my api just as my bearer tokens have.

Any direction or help would be greatly appreciated. I can provide any additional code from the other files that helps.

tafn3t
  • 43
  • 5

1 Answers1

0

Sanctum supports only one top level domain. If you have an app in myapp.com, then you can use anything.myapp.com. In this regard, something.my-app.com will be basically a different domain.

Also, once you acquire the login cookie, you have make sure to add withCredentials: true in your https request headers/params.

As for how you control what domains are supported, you can use the following .env vars:

SESSION_DOMAIN=mydomain.test
SANCTUM_STATEFUL_DOMAINS=frontend.mydomain.test

Here is a pretty good post if it helps: https://dev.to/nicolus/laravel-sanctum-explained-spa-authentication-45g1

Make sure to check the comments.

Yousof K.
  • 1,374
  • 13
  • 23
  • Thank you Yousof that post helps fill the gaps. I can’t believe I didn’t run into that in all my searching. I’m on two different domains right now which is probably 99% of my issue. I will report back once I get that all resolved. – tafn3t May 25 '22 at 01:49
  • 1
    Yousof it was the whole separate domains being the issue. Once I got them on the same domain things pretty much started working. Thank you so much for your help! – tafn3t May 25 '22 at 17:50
  • 1
    Glad @tafn3t. The Sanctum (and Laravel in general) documentation is not procedural. So you often need to look into tutorials. Make sure to mark the answer if it worked. – Yousof K. May 26 '22 at 03:57