使用Laravel为后端刷新Vue.js SPA的身份验证令牌

Dan*_*eij 7 php jwt laravel single-page-application vue.js

我使用Laravel(5.5)作为后端,使用Vue(2.5)构建单页应用程序.一切正常,除了在退出后再次直接登录.在这种情况下,对/ api/user的调用(以检索用户的帐户信息并再次验证用户的身份)失败,401未经授权(即使登录成功).作为回应,用户直接退回到登录屏幕(我自己写了这个措施作为对401响应的反应).

注销是什么,用ctrl/cmd + R刷新页面,然后再次登录.一个页面刷新解决我的问题,这一事实让我有理由相信,我不处理X-CSRF-TOKEN的刷新正确,也可以忘掉那Laravel使用某些cookie(如描述在这里).

这是在用户单击登录按钮后执行的登录表单代码的片段.

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);
            });
    });
},  
Run Code Online (Sandbox Code Playgroud)

authenticate()方法是一个vuex动作,它调用laravel端的登录端点.

/ refreshTokens端点只是调用此Laravel控制器函数,该函数返回当前登录用户的CSRF令牌:

public function getCsrfToken(){
    return ['csrfToken' => csrf_token()];
}
Run Code Online (Sandbox Code Playgroud)

在重新获取令牌之后,用户被重定向到主页面(或提供的另一页面),this.$router.replace(this.$route.query.redirect || '/');并且在那里api/user调用该功能以检查当前登录用户的数据.

我是否应该采取其他措施来完成这项工作?

谢谢你的帮助!


编辑:2017年11月7日

在提出所有有用的建议之后,我想补充一些信息.我正在使用Passport在Laravel端进行身份验证,并且CreateFreshApiToken中间件已就绪.

我一直在查看我的应用程序设置的cookie,特别laravel_token是据说持有加密的JWT,Passport将用它来验证来自JavaScript应用程序的API请求.注销时,会删除laravel_token cookie.之后直接再次登录(使用axios发送AJAX帖子请求)没有laravel_token设置新的,所以这就是它不验证用户的原因.我知道Laravel没有在登录POST请求上设置cookie,但是之后直接对/ refreshTokens(没有保护)的GET请求应该设置cookie.但是,这似乎并没有发生.

我已经尝试增加请求/refreshTokens和请求之间的延迟/api/user,以便可能给服务器一些时间来使事情井然有序,但无济于事.

为了完整起见,这是我正在处理登录请求服务器端的Auth\LoginController:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Dan*_*eij 4

终于修好了!

通过直接在 LoginControllers 方法中返回 UserResource authenticated,它不是有效的 Laravel 响应(但我猜原始 JSON 数据?),所以可能没有附加像 cookie 这样的东西。我必须在资源上附加对 response() 的调用,现在一切似乎都工作正常(尽管我需要进行更广泛的测试)。

所以:

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

    return (new UserResource($user))->additional(
        ['permissions' => $user->getUIPermissions()]
    );
}
Run Code Online (Sandbox Code Playgroud)

变成

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

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

Laravel 文档万岁,将其中的一个部分归因于此: 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);  
            }
        });
Run Code Online (Sandbox Code Playgroud)

我的vuex商店中的操作authenticate()如下:

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) );
    });
},
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)