Tanmay
Tanmay

Reputation: 3159

Axios is not sending cookies

I have two apps, the server-side app which is written in Laravel and the client-side app, written in VueJS. The vue app consumes the api provided by the laravel app.

The auth flow:

The user attempts to log in, the server sends two tokens to the client, a) access_token and b) refresh_token upon successful login. The server also sends the refresh token in the form of an httpOnly cookie to the client so that when the access token is expired, it can be refreshed using the refresh token from the cookie.

The problem:

When the user logs in, in the response, the server sends the following 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

This means that I expect the cookie to be sent to the server whenever there is a request to the /v1/refresh endpoint. However, the cookie is not present in the request. (I've logged $request->cookie('refresh_token') in controller but it logs null).

This whole token refreshing mechanism is handled in a vuex action:

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));
    });
}

As you can see, I've set the withCredentials config to true. I am also sending the Access-Control-Allow-Credentials: true from the server. Here is my cors middleware:

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);
    }

I don't know what have I done wrong. My PHP version is: 7.3.5. Here are the request headers of /v1/refresh endpoint:

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

...and the response 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

I don't know the inner-workings of browser's cookie storing mechanism, I also don't know if an httpOnly cookie can be found in the filesystem, but in despair, to know whether the browser is indeed saving the cookie, I googled and found that cookies are stored in ~/Library/Application Support/Google/Chrome/Default/Cookies file, which is an SQLite file. I opened that file and searched for my cookie 🍪, but it wasn't there either (maybe httpOnly cookies are stored somewhere else?).

Now, my question is, how do I retrieve the cookie from the client-side app?

Upvotes: 3

Views: 12580

Answers (1)

nmfzone
nmfzone

Reputation: 2903

Since your Vue App and Laravel (API) has different HOST, it will not working.

You can re-check your server response:

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

It sets the cookie to http://app.test, not http://localhost:8080. So, there is no refresh_token cookie set in your http://localhost:8080.

The very typical solution is:

  1. You need to use subdomain, and let your cookie set to the domain=.app.test (whole domain). I mean, you need to make sure Laravel and Vue under the same domain.

  2. You don't need to get the refresh_token from cookie again in your Laravel app. First, you just need to save your refresh_token you get from API, to the either localStorage or cookie at your Vue App. Then, just send your refresh_token via forms (form-data). Finally, get your refresh_token via $request->get('refresh_token').

Here is the example, just to illustrate what i mean for the second solution.

Let's assume (typically) the http://app.test/api/login would response:

{
    "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: '[email protected]',
        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,
    })
}

Upvotes: 4

Related Questions