Обновление токенов аутентификации для 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