Axios 不发送 cookie

Axios is not sending cookies

我有两个应用程序,server-side 应用程序是用 Laravel 编写的,client-side 应用程序是用 VueJS 编写的。 vue 应用程序使用 laravel 应用程序提供的 api。

授权流程:

用户尝试登录,服务器向客户端发送两个令牌,a) access_token 和 b) refresh_token 成功登录。服务器还以 httpOnly cookie 的形式向客户端发送刷新令牌,以便在访问令牌过期时,可以使用来自 cookie 的刷新令牌进行刷新。

问题:

当用户登录时,在响应中,服务器发送以下 Set-Cookie header:

Set-Cookie: refresh_token=tokenvalue; expires=Mon, 04-Nov-2019 09:13:28 GMT; Max-Age=604800; path=/v1/refresh; domain=http://app.test; httponly; samesite=none

这意味着只要有对 /v1/refresh 端点的请求,我就希望将 cookie 发送到服务器。但是,请求中不存在 cookie。 (我在控制器中记录了 $request->cookie('refresh_token') 但它记录了 null)。

整个令牌刷新机制在 vuex 操作中处理:

export function refreshToken({commit}, payload) {
    return new Promise((resolve, reject) => {
        // axios.defaults.withCredentials = true;

        // here, payload() function just converts the url to:
        // "http://app.test/v1/refresh"

        axios.post(payload('/refresh'), {}, {
            withCredentials: true, transformRequest: [(data, headers) => {
                delete headers.common.Authorization;
                return data;
            }]
        }).then(response => {
            let token = response.data.access_token;
            localStorage.setItem('token', token);
            commit('refreshSuccess', token);
            resolve(token);
        }).catch(err => reject(err));
    });
}

如您所见,我已将 withCredentials 配置设置为 true。我还从服务器发送 Access-Control-Allow-Credentials: true。这是我的 cors 中间件:

public function handle($request, Closure $next)
    {
        $whiteList = ['http://localhost:8080'];
        if (isset($request->server()['HTTP_ORIGIN'])) {
            $origin = $request->server()['HTTP_ORIGIN'];
            if (in_array($origin, $whiteList)) {
                header('Access-Control-Allow-Origin: ' . $request->server()['HTTP_ORIGIN']);
                header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
                header('Access-Control-Allow-Headers: Origin, Content-Type, Authorization');
                header('Access-Control-Allow-Credentials: true');
                header('Access-Control-Expose-Headers: Content-Disposition');
            }
        }
        return $next($request);
    }

我不知道我做错了什么。我的 PHP 版本是:7.3.5。这是 /v1/refresh 端点的请求 header:

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,bn;q=0.8
Connection: keep-alive
Content-Length: 15
Content-Type: application/x-www-form-urlencoded
Host: app.test
Origin: http://localhost:8080
Referer: http://localhost:8080/products
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36

...以及响应 headers:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, Content-Type, Authorization
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Expose-Headers: Content-Disposition
Cache-Control: no-cache, private
Connection: keep-alive
Content-Type: application/json
Date: Mon, 28 Oct 2019 09:40:31 GMT
Server: nginx/1.15.5
Transfer-Encoding: chunked
X-Powered-By: PHP/7.3.5
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59

我不知道浏览器的cookie存储机制inner-workings,我也不知道文件系统中是否可以找到httpOnly cookie,但无奈,想知道浏览器是否确实是保存 cookie,我用谷歌搜索发现 cookie 存储在 ~/Library/Application Support/Google/Chrome/Default/Cookies 文件中,这是一个 SQLite 文件。我打开那个文件并搜索我的 cookie ,但它也不在那里(也许 httpOnly cookies 存储在其他地方?)。

现在,我的问题是,如何从 client-side 应用程序检索 cookie?

由于你的 Vue 应用程序和 Laravel (API) 有不同的主机,它不会工作。

您可以重新检查您的服务器响应:

Set-Cookie: refresh_token=tokenvalue; expires=Mon, 04-Nov-2019 09:13:28 GMT; Max-Age=604800; path=/v1/refresh; domain=http://app.test; httponly; samesite=none

它将 cookie 设置为 http://app.test,而不是 http://localhost:8080。因此,您的 http://localhost:8080.

中没有设置 refresh_token cookie

非常典型的解决方案是:

  1. 您需要使用子域,并将您的cookie设置为 domain=.app.test(整个域)。我的意思是,你需要确定 Laravel和vue在同一个域下

  2. 您不需要在 Laravel 应用程序中再次从 cookie 获取 refresh_token。首先,您只需要将从 API 获得的 refresh_token 保存到 Vue 应用程序的 localStorage 或 cookie 中。然后,只需通过表单(表单数据)发送您的 refresh_token。最后,通过 $request->get('refresh_token').

  3. 获取您的 refresh_token

这是示例,只是为了说明我对第二种解决方案的意思。

让我们假设(通常)http://app.test/api/login 会回应:

{
    "token_type": "Bearer",
    "expires_in": 31622399,
    "access_token": "xxx",
    "refresh_token": "xxx"
}
import Cookies from 'js-cookie'

async login() {
    const { data } = await axios.post('http://app.test/api/login', {
        email: 'hi@app.test',
        password: 'secret',
    })

    const refreshToken = data.refresh_token

    Cookies.set('refresh_token', refreshToken)
},
async refreshToken() {
    const refreshToken = Cookies.get('refresh_token')

    const response = await axios.post('http://app.test/api/refresh-token', {
        refresh_token: refreshToken,
    })
}