Обновление токенов аутентификации для Vue.JS SPA с использованием Laravel для бэкэнда

Я создаю одностраничное приложение с Vue (2.5), используя Laravel (5.5) в качестве бэкэнда. Все работает хорошо, за исключением прямого входа в систему после выхода из системы. В этом случае вызов /api/user (чтобы получить информацию об учетной записи пользователя и еще раз проверить личность пользователя) завершается с ошибкой 401 unauthorized (даже если вход в систему удался). В качестве ответа пользователь возвращается непосредственно на экран входа в систему (я сам написал эту меру как реакцию на 401 ответивший.)

что работает, это выйти из системы, обновить страницу с помощью ctrl / cmd+R, а затем снова войти в систему. Тот факт, что обновление страницы устраняет мою проблему, дает мне основания полагать, что я неправильно обрабатываю обновление X-CSRF-токена или могу забыть о некоторых файлах cookie, которые использует Laravel (как описано здесь ).

это фрагмент кода формы входа, который выполняется после того, как пользователь нажимает на вход кнопка.

login(){
    // Copy the form data
    const data = {...this.user};
    // If remember is false, don't send the parameter to the server
    if(data.remember === false){
        delete data.remember;
    }

    this.authenticating = true;

    this.authenticate(data)
        .then( this.refreshTokens )
        .catch( error => {
            this.authenticating = false;
            if(error.response && [422, 423].includes(error.response.status) ){
                this.validationErrors = error.response.data.errors;
                this.showErrorMessage(error.response.data.message);
            }else{
                this.showErrorMessage(error.message);  
            }
        });
},
refreshTokens(){
    return new Promise((resolve, reject) => {
        axios.get('/refreshtokens')
            .then( response => {
                window.Laravel.csrfToken = response.data.csrfToken;
                window.axios.defaults.headers.common['X-CSRF-TOKEN'] = response.data.csrfToken;
                this.authenticating = false;
                this.$router.replace(this.$route.query.redirect || '/');
                return resolve(response);
            })
            .catch( error => {
                this.showErrorMessage(error.message);
                reject(error);
            });
    });
},  

на authenticate() метод-это действие vuex, которое вызывает конечную точку входа на стороне laravel.

конечная точка /refreshTokens просто вызывает эту функцию контроллера Laravel, которая возвращает токен CSRF текущего зарегистрированного пользователя:

public function getCsrfToken(){
    return ['csrfToken' => csrf_token()];
}

после того, как токены были восстановлены, пользователь перенаправляется на главную страницу (или другую страницу, если она указана) с this.$router.replace(this.$route.query.redirect || '/'); и там api/user функция вызывается для проверки данных текущий зарегистрированный пользователь.

есть ли какие-либо другие меры, которые я должен предпринять, чтобы сделать эту работу, которую я упускаю?

Спасибо за любую помощь!


изменить: 07 ноя 2017

после всех полезных предложений, я хотел бы добавить некоторую информацию. Я использую Passport для аутентификации на стороне Laravel, и промежуточное ПО CreateFreshApiToken находится на месте.

Я смотрел на набор cookies по моему приложению, и в частности laravel_token который, как говорят, содержит зашифрованный JWT, который Passport будет использовать для аутентификации запросов API из вашего приложения JavaScript. При выходе из системы файл cookie laravel_token удаляется. При повторном входе в систему сразу после этого (используя axios для отправки запроса AJAX post) нет нового laravel_token устанавливается, поэтому он не аутентифицирует пользователя. Я знаю, что Laravel не устанавливает cookie в запросе на вход в систему, но запрос GET на / refreshTokens (который не охраняется) непосредственно после должны установите cookie. Однако, этого не происходит.

я попытался увеличить задержку между запросом на /refreshTokens и /api/user, возможно, дать серверу некоторое время, чтобы привести все в порядок, но безрезультатно.

для полноты, вот мой AuthLoginController, который обрабатывает запрос на вход на стороне сервера:

class LoginController extends Controller
{
    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        // $this->middleware('guest')->except('logout');
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  IlluminateHttpRequest  $request
     * @return array
     */
    protected function credentials(IlluminateHttpRequest $request)
    {
        //return $request->only($this->username(), 'password');
        return ['email' => $request->{$this->username()}, 'password' => $request->password, 'active' => 1];
    }

    /**
     * The user has been authenticated.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(IlluminateHttpRequest $request, $user)
    {
        $user->last_login = CarbonCarbon::now();
        $user->timestamps = false;
        $user->save();
        $user->timestamps = true;

        return (new UserResource($user))->additional(
            ['permissions' => $user->getUIPermissions()]
        );
    }


    /**
     * Log the user out of the application.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateHttpResponse
     */
    public function logout(IlluminateHttpRequest $request)
    {
        $this->guard()->logout();
        $request->session()->invalidate();
    }
}

3 ответов


учитывая, что вы используете api для аутентификации, я бы предложил использовать паспорт или проверка подлинности JWT в для обработки токенов аутентификации.


наконец-то починил!

возвращая UserResource непосредственно в LoginControllers authenticated метод, это не действительный ответ Laravel (но я думаю, что необработанные данные JSON?) так что, вероятно, такие вещи, как cookies, не прилагаются. Мне пришлось прикрепить вызов к response () на ресурсе, и теперь все работает нормально (хотя мне нужно сделать более обширное тестирование).

так:

protected function authenticated(\Illuminate\Http\Request $request, $user)
{
    ...

    return (new UserResource($user))->additional(
        ['permissions' => $user->getUIPermissions()]
    );
}

становится

protected function authenticated(\Illuminate\Http\Request $request, $user)
{
    ...

    return (new UserResource($user))->additional(
        ['permissions' => $user->getUIPermissions()]
    )->response();  // Add response to Resource
}

Ура для Laravel docs на присвоение этому разделу: https://laravel.com/docs/5.5/eloquent-resources#resource-responses

кроме того, laravel_token не установлен запросом POST для входа в систему, и вызов refreshCsrfToken() также не сделал трюк, вероятно, потому, что он был защищен гостевым промежуточным по.

что сработало для меня в конце концов, это выполнить фиктивный вызов ' / ' сразу после возврата функции входа в систему (или обещание было выполненный.)

в конце концов, моя функция входа в компонент следующим образом:

login(){
    // Copy the user object
    const data = {...this.user};
    // If remember is false, don't send the parameter to the server
    if(data.remember === false){
        delete data.remember;
    }

    this.authenticating = true;

    this.authenticate(data)
        .then( csrf_token => {
            window.Laravel.csrfToken = csrf_token;
            window.axios.defaults.headers.common['X-CSRF-TOKEN'] = csrf_token;

            // Perform a dummy GET request to the site root to obtain the larevel_token cookie
            // which is used for authentication. Strangely enough this cookie is not set with the
            // POST request to the login function.
            axios.get('/')
                .then( () => {
                    this.authenticating = false;
                    this.$router.replace(this.$route.query.redirect || '/');
                })
                .catch(e => this.showErrorMessage(e.message));
        })
        .catch( error => {
            this.authenticating = false;
            if(error.response && [422, 423].includes(error.response.status) ){
                this.validationErrors = error.response.data.errors;
                this.showErrorMessage(error.response.data.message);
            }else{
                this.showErrorMessage(error.message);  
            }
        });

и authenticate() акция в моем магазине vuex выглядит следующим образом:

authenticate({ dispatch }, data){
    return new Promise( (resolve, reject) => {
        axios.post(LOGIN, data)
            .then( response => {
                const {csrf_token, ...user} = response.data;
                // Set Vuex state
                dispatch('setUser', user );
                // Store the user data in local storage
                Vue.ls.set('user', user );
                return resolve(csrf_token);
            })
            .catch( error => reject(error) );
    });
},

потому что я не хотел делать дополнительный звонок refreshTokens в дополнение к фиктивному вызову /, я прикрепил csrf_token к ответу маршрута / login бэкэнда:

protected function authenticated(\Illuminate\Http\Request $request, $user)
{
    $user->last_login = \Carbon\Carbon::now();
    $user->timestamps = false;
    $user->save();
    $user->timestamps = true;

    return (new UserResource($user))->additional([
        'permissions' => $user->getUIPermissions(),
        'csrf_token' => csrf_token()
    ])->response();
}

вы должны использовать промежуточное ПО Passports CreateFreshApiToken в своем веб-промежуточном по паспорт потребляющий-ваш-api

web => [...,
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

это придает прикрепить право csrftoken() для всех ваших заголовков запросов как request_cookies