Невозможно пройти проверку подлинности в сумерках с помощью sanctum

#reactjs #laravel #laravel-dusk #laravel-sanctum

#reactjs #laravel #laravel-dusk #laravel-sanctum

Вопрос:

Я пытаюсь создать страницу входа в систему, используя Laravel sanctum и React.

Я могу войти в свой браузер (вижу сообщение «аутентифицировано»), но я не могу войти в Dusk.

Если быть точным, я могу войти в систему один раз в Dusk, но я не прошел аутентификацию при следующем доступе.

Я не могу понять, почему это произошло.

Как я могу пройти аутентификацию в Dusk с помощью sanctum?

Окружающая среда

  • Laravel 7.x
  • laravel / sanctum ^ 2.6
  • Реагировать 16.13.1

Login.js

 import React, { useState } from 'react';
const axios = window.axios;

export default function Login(props) {
  const [strId, setStrId] = useState('');
  const [password, setPassword] = useState('');
  const [hoge, setHoge] = useState('ini state');

  function normalUserLogin() {
    axios
      .get('/sanctum/csrf-cookie')
      .then((response) => {
        axios
          .post('/api/login', {
            strId: strId,
            password: password,
          })
          .then((response) => {
            setHoge(response.data);
            authCheck();
          })
          .catch((error) => {
            console.log(error);
          });
      })
      .catch((error) => {
        alert('Error happened.');
      });
  }

  function authCheck() {
    axios
      .get('/api/user/auth')
      .then((response) => {
        setHoge(response.data);
      })
      .catch((error) => {
        console.log(error);
        alert('error happened getting auth information');
      });
  }

  return (
    <>
      <div className="row mt-5">
        <div className="col-12">
          <form>
            <label htmlFor="user-id" className="d-block">
              User ID
              <input
                name="user-id"
                id="user-id"
                className="d-block"
                value={strId}
                onChange={(e) => setStrId(e.target.value)}
              />
            </label>
            <label htmlFor="password" className="d-block">
              Password
              <input
                type="password"
                name="password"
                id="password"
                autoComplete="off"
                className="d-block"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
              />
            </label>
          </form>
          <button
            type="button"
            id="normal-login"
            className="btn btn-outline-success d-block"
            onClick={normalUserLogin}
          >
            Login
          </button>
        </div>
      </div>
      <div className="col-12">
        {hoge}
      </div>
    </>
  );
}
  

MyCustomLoginController (app/Http/Api/LoginController.php )

 <?php

namespace AppHttpControllersApi;

use AppHttpControllersController;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use LaravelSanctumSanctum;
use AppUser;

class LoginController extends Controller
{
    // routing is '/api/login'
    public function login(Request $request)
    {
        $str_id = $request->input('strId');
        $password = $request->input('password');

        $credentials = compact('str_id', 'password');

        if (Auth::attempt($credentials)) {
            $hoge = Auth::check() ? 'OK' : 'NG';
            // $hoge is OK in dusk.
            return response($hoge);
        } else {
            return response('Cannot Authenticated', 401);
        }
    }
}
  

Действие проверки подлинности

 <?php

namespace AppHttpControllersApi;

use AppHttpControllersController;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use AppUser;

class UserController extends Controller
{
    // routing is '/api/user/auth'
    public function auth(Request $request)
    {
        $user = Auth::user();
        if ($user) {
            $params = User::getParamsForApp($user->str_id);
            // in local environment, I can see 'Authenticated.'
            return response('AUTHENTICATED');
        } else {
            // in dusk, i can see 'Not Authenticated.'
            return response('Not Authenticated.');
        }
    }
}
  

Dusk

 <?php

namespace TestsBrowser;

use IlluminateFoundationTestingDatabaseMigrations;
use LaravelDuskBrowser;
use TestsDuskTestCase;
use AppUser;

class LoginTest extends DuskTestCase
{
    use DatabaseMigrations;

    public function testLogin()
    {
        $this->browse(function (Browser $browser) {

            $user = Factory(User::class)->create();
            
            $str_id = $user->str_id;
            $password = config('app.guest_password');
            
            $browser->visit('/login')
                    ->waitFor('#user-id')
                    ->type('user-id', $str_id)
                    ->type('password', $password)
                    ->press('Login')
                    ->waitFor('#message');
        });
    }
}
  

Setting file

.env

web (APP_URL) is container name of nginx

 APP_URL=http://web

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:8000

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=****_test
DB_USERNAME=****_test
DB_PASSWORD=*********

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
  

phpunit.dusk.xml

 <?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Browser Test Suite">
            <directory suffix="Test.php">./tests/Browser</directory>
        </testsuite>
    </testsuites>
    <php>
        <server name="SESSION_DRIVER" value="file"/>
    </php>
</phpunit>
  

DuskTestCase.php

 <?php

namespace Tests;

use FacebookWebDriverChromeChromeOptions;
use FacebookWebDriverRemoteDesiredCapabilities;
use FacebookWebDriverRemoteRemoteWebDriver;
use LaravelDuskTestCase as BaseTestCase;

abstract class DuskTestCase extends BaseTestCase
{
    use CreatesApplication;

    public static function prepare()
    {
        // static::startChromeDriver();
    }

    protected function driver()
    {
        $options = (new ChromeOptions())->addArguments([
            '--disable-gpu',
            '--headless',
            '--window-size=1920,1080',
            '--no-sandbox',
            '--enable-file-cookies',
        ]);

        // I use Docker, container name is chrome.
        return RemoteWebDriver::create(
            'http://chrome:4444',
            DesiredCapabilities::chrome()->setCapability(
                ChromeOptions::CAPABILITY,
                $options
            )
        );
    }
}

  

Ответ №1:

Попробуйте установить драйвер сеанса в файл

У меня есть файл phpunit.dusk.xml в корне моего приложения и в разделе php

 <server name="SESSION_DRIVER" value="file"/>
  

Попробуйте в классе DuskTestCase в методе driver() установить параметр ‘—enable-file-cookies’

 $options = (new ChromeOptions())->addArguments([
        '--disable-gpu',
        '--headless',
        '--window-size=1920,1080',
        '--ignore-certificate-errors',
        '--no-sandbox',
        '--enable-file-cookies',
    ]);
  

Комментарии:

1. Спасибо за вашу доброту. Но я не могу решить эту проблему и сейчас. Если у вас есть какие-либо идеи, не могли бы вы рассказать мне о них?

2. @keciek3 Почему в prepare() вы отключили // static::startChromeDriver(); ? Также попробуйте проверить, совпадает ли SESSION_DOMAIN с именем хоста вашего сервера (конечной точки API) и начинается с точки .my-back-end.com

3. Я закомментировал это, потому что я использую selenium / standalone-chrome с docker, ссылающимся на document . Я понимаю, что SESSION_DOMAIN неверен, но я не знаю, как это исправить… Я пробовал SESSION_DOMAIN=app (.app, chrome, .chrome, .web и web), но это не работает. Здесь app — это php-fpm, chrome — selenium, web — имя контейнера nginx.

4. Я решил эту проблему с помощью SESSION_DOMAIN=web, SANCTUM_STATEFUL_DOMAINS=web, SESSION_DRIVER=cookie. Ваш совет мне очень помогает. Спасибо!

5. @keciek3 SESSION_DOMAIN — это домен, в котором находится ваше приложение laravel. Например, приложение laravel находится в домене test.local, SESSION_DOMAIN должен быть .test.local.