2

I'm still building auth methods to work with a SPA, I have installed laravel sanctum and followed the proper steps and created a demo form on the welcome blade just to test things out, I'm correctly receiving xsrf-cookie using sanctum/csrf-cookie, however the login returns 419 page expired. I have tried clearing cache, route, config

Serving on localhost:8000

.env

SANCTUM_STATEFUL_DOMAINS=localhost:8000
SESSION_DOMAIN=localhost

cors

    'paths' => ['api/*','login','sanctum/csrf-cookie'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,

];

web routes

Route::post('login', function(){
    echo"a";
});
// Route::post('register', [UserController::class,'register']);

Route::get('/',function(){
    return view('welcome');
});

form

    <form method="post" action="/login">
        <button type="submit">Submit</button>
    </form>

I manually visit localhost:8000/sanctum/csrf-cookie, and I can see cookies for xsrf and session are being set, on submit I still get 419 page expired. (adding @csrf made it work, however, this is just for testing, I'm intending to use a SPA)

1
  • I have just installed laravel and sanctum from scratch, ran php artisan serve, changed domain and stateful domains in .env to localhost, added login to cors, it is still not working. Commented Jan 18, 2021 at 13:03

1 Answer 1

0

Here is how it worked for me:

in kernel.php

  'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:5000,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            \Illuminate\Session\Middleware\StartSession::class
        ],

in config/sanctum.php

  <?php

 return [

/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/

'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
    '%s%s',
    'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
    env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),

/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. If this value is null, personal access tokens do
| not expire. This won't tweak the lifetime of first-party sessions.
|
*/

'expiration' => null,

/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/

'middleware' => [
    'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
    'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],

];

in .env (I use subdomains for both the main API and SPA website)

 SESSION_DRIVER=cookie
 SESSION_LIFETIME=86400
 SESSION_DOMAIN=".myapidomain.com"
 SANCTUM_STATEFUL_DOMAINS=".myapidomain.com"

in cors.php

   'paths' => ['api/*','sanctum/csrf-cookie'],
   'supports_credentials' => true,

in the user model ex: App\User

   use Laravel\Sanctum\HasApiTokens;

i did't use vuejs on frontend so i used Laravel HTTP Facade, so i created a class for managing the SPA requests for both authenticated requests and non authenticated requests

    <?php

    namespace App\Http\Controllers\Api\Settings;

    use GuzzleHttp\Client;
    use Illuminate\Support\Facades\Http;
    use Illuminate\Support\Facades\Session;

   class Master
   {


public $domainApiLink = "https://subdomain.mydomain/api/";
public $domainWebLink = "https://sbdomain.mydomain.com/";

private $cookies;

public function __construct(){
    $this->generate_cookies();
}

public function generate_cookies(){
    if(empty(Session::get('cookies'))) {
       
            $cookies = Http::acceptJson()->asJson()->get($this->domainWebLink . 'sanctum/csrf-cookie')->throw()->cookies();
            Session::put('cookies', $cookies);
    }
}

public function getRequest($get_link_part, $type = '')
{
    $activeLocale = app()->getLocale();
    if(Session::has('auth_token') ) {
        $response = Http::acceptJson()->asJson()
            ->withToken( Session::get('auth_token'))
            ->withOptions([
                //'debug' => true
            ])
            ->get($this->domainApiLink . $activeLocale . $get_link_part)
            ->throw();
    }else{
          $response = Http::acceptJson()->asJson()
            ->withToken($this->website_token)
            ->withOptions([
                //'debug' => true,
                'cookies' => Session::get('cookies'),
            ])
            ->get($this->domainApiLink . $activeLocale . $get_link_part)
            ->throw();
    }
    switch ($type) {
        case 'json':
            $response = $response->json();
            break;
        case 'assoc':
            if (is_array($response->json())) {
                return $response->json();
            } else {
                $response = json_decode($response->json(), true);
                if (empty($response)) {
                    return ['error' => 'Invalid Json response'];
                }
            }
            break;
        case 'object':
            $response = $response->object();
            break;
        default:
            $response = $response->body();
            break;
    }
    return $response;
}

public function postRequest($inputs, $get_link_part, $type = '')
{
    $activeLocale = app()->getLocale();
    if(Session::has('auth_token') ) {
        $response = Http::acceptJson()->asJson()
            ->withToken(Session::get('auth_token'))
            //->withOptions(['debug' => true])
            ->post($this->domainApiLink . $activeLocale . $get_link_part, $inputs)
            ->throw();
    }
    else{
        $response = Http::acceptJson()->asJson()
            ->withToken($this->website_token)
            ->withOptions([
                //'debug' => true,
            ])
            ->post($this->domainApiLink. $activeLocale . $get_link_part, $inputs)
            ->throw();
    }
    switch ($type) {
        case 'json':
            $result = $response->json();
            break;
        case 'assoc':
            if (is_array($response->json())) {
                $result =  $response->json();
            } else {
                $result = json_decode($response->json(), true);
                if (empty($result)) {
                    $result = ['error' => 'Invalid Json response'];
                }
            }
            break;
        case 'object':
            $result = $response->object();
            break;
        default:
            $result = $response->body();
            break;
    }
    return $result;
}
    /**
    * @return mixed
    */
    public function getCookies()
    {
       return $this->cookies;
    }

    /**
    * @param mixed $cookie
    */
    public function setCookies($cookie): void
    {
        $this->cookies = $cookie;
    }

}

i hope this helps you and i am happy to explain more, if needed

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.