I am building a single-page-app with Vue (2.5) using Laravel (5.5) as the backend. Everything works well, except for directly logging in again after having logged out. In this case, the call to /api/user (to retrieve the user's account information and to verify the user's identity once more) fails with a 401 unauthorized (even though the log-in succeeded). As a response, the user is bounced back directly to the login screen (I wrote this measure myself as a reaction to 401 responses).
What does work is to log out, refresh the page with ctrl/cmd+R, and then log in again. The fact that a page refresh fixes my problem, gives me reason to believe that I am not handling refresh of the X-CSRF-TOKEN correctly, or may be forgetting about certain cookies that Laravel uses (as described here ).
This is a snippet of the code of the login form that is executed after a user clicks the login button.
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);
});
});
},
the authenticate()
method is a vuex action, which calls the login endpoint at the laravel side.
The /refreshTokens endpoint simply calls this Laravel controller function that returns the CSRF token of the currently logged-in user:
public function getCsrfToken(){
return ['csrfToken' => csrf_token()];
}
After the tokens have been refetched, the user is redirected to the main page (or another page if supplied)
with this.$router.replace(this.$route.query.redirect || '/');
and there the api/user
function is called to check the data of the currently logged in user.
Are there any other measures I should take to make this work, that I am overlooking?
Thanks for any help!
EDIT: 07 Nov 2017
After all the helpful suggestions, I would like to add some information. I am using Passport to authenticate on the Laravel side, and the CreateFreshApiToken middleware is in place.
I have been looking at the cookies set by my app, and in particular the laravel_token
which is said to hold the encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. When logging out, the laravel_token cookie is deleted. When logging in again directly afterwards (using axios to send an AJAX post request) no new laravel_token
is being set, so that's why it doesn't authenticate the user. I am aware that Laravel doesn't set the cookie on the login POST request, but the GET request to /refreshTokens (which is not guarded) directly afterwards should set the cookie. However, this doesn't appear to be happening.
I have tried increasing the delay between the request to /refreshTokens
and the request to /api/user
, to maybe give the server some time to get things in order, but to no avail.
For completeness sake, here is my Auth\LoginController that is handling the login request server-side:
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 \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(\Illuminate\Http\Request $request)
{
//return $request->only($this->username(), 'password');
return ['email' => $request->{$this->username()}, 'password' => $request->password, 'active' => 1];
}
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
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()]
);
}
/**
* Log the user out of the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function logout(\Illuminate\Http\Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
}
}