Axios is not sending cookies
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:
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.You don't need to get the
refresh_token
from cookie again in your Laravel app. First, you just need to save yourrefresh_token
you get from API, to the either localStorage or cookie at your Vue App. Then, just send yourrefresh_token
via forms (form-data). Finally, get yourrefresh_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,
})
}
Related videos on Youtube
Comments
-
Tanmay almost 2 years
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 anhttpOnly
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 logsnull
).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 totrue
. I am also sending theAccess-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?
-
nmfzone over 4 yearsWhat is exactly your HOST? Is it
localhost:8080
orapp.test
? -
Tanmay over 4 yearsThanks for the response.
localhost:8080
is where my vue app is served from.app.test
is where my laravel app is served from. They are two different apps/domains/origins. This is a very typical set up for a SPA that is completely isolated from the laravel app. -
nmfzone over 4 yearsSo, the vue app and api has different host, right? Of course it would not work, since your laravel set the cookie to
app.test
, notlocalhost:8080
. Take a look at the domainpath=/v1/refresh; domain=http://app.test; httponly; samesite=none
.
-
-
Tanmay over 4 yearsChanged domain to
localhost:8080
, cleared cache, cookies, and everything. Tried again. Still, the cookie could not be retrieved. -
nmfzone over 4 yearsWhat do you mean by
changed domain to localhost:8080
? You can't set cookie from Laravelapp.test
tohttp://localhost:8080
. Here is why. -
Tanmay over 4 yearsI meant I changed the value of the
domain=
fromapp.test
tolocalhost:8080
as you've mentioned. -
Tanmay over 4 yearsI am working on your first point. But your second point where you are suggesting to send the refresh token via form, it can't be done. Because this cookie is httpOnly cookie. It can't be retrieved from
$request->get()
-
nmfzone over 4 yearsFor the second point, that's the problem. You don't need to set
refresh_token
as cookie again in Laravel. You just need to save yourrefresh_token
you get on authenticate to the localStorage or cookie in your Vue App. -
Tanmay over 4 yearsI am not setting the refresh_token again. All I am trying to do is set the token to clients cookie, and retrieve the token (which is supposed to be sent automatically once it is set).
-
Tanmay over 4 yearsDo you know how can I assign my vue app to the subdomain of
app.test
? I am using laravel valet. -
nmfzone over 4 years@Tanmay No, I don't know how to setup subdomain in local machine, even with valet. I would just suggest my second solution. That's the typical use case (AFAIK). But, just do what you want.
-
Tanmay over 4 yearsThe second solution suggests storing the refresh token to localStorage. But under no circumstances should I store the refresh token in localStorage. That would defeat the whole purpose of a refresh token. Refresh token should be stored as httpOnly cookie so that javascript can't access it. The whole point of the refresh token is to prevent javascript from accessing it.
-
nmfzone over 4 yearsI said,
localStorage
orcookie
. It depends on what's your Vue App types. If it's SSR, you can set it to thehttpOnly
cookie. But, if it's SPA, of course you can't sethttpOnly
cookie. If you don't mean to save it to cookie or localStorage in SPA, so you can use vuex (or just use simple class to hold it), just the same as Auth0 used here -
nmfzone over 4 yearsAFAIK, if you're using cookie in your API, I believe it wouldn't work if your API got accessed via Android (or similar, like cURL).
-
nmfzone over 4 yearsBtw, I was forget something. Where do you store your
access_token
? -
Tanmay over 4 yearsI would create a different endpoint for android, let's say
v1/app-refresh
where instead of grabing the token from$request->cookie()
I would grab it from$request->token
-
Tanmay over 4 yearsI am storing accesd_token in localStorage. It only has 1 minute life time. If it gets stolen, it would already be expired by the time attacker makes another request.
-
nmfzone over 4 yearshaha, that's what i've guessed. You store
access_token
in localStorage, so what's the difference? ;)) Ok, just do as you please. -
Tanmay over 4 yearsI had the very same question in my mind when I was naively implementing the auth. Then one day I asked myself what exactly is the purpose of a refresh token. So here is the difference: if someone steals the
access_token
from the localStorage, he cannot do anything with it. But if someone steals therefresh_token
, then he has 7 days, which is enough to destroy the system. -
nmfzone over 4 yearsAre you sure attackers can't do anything in 1 minute? ;)) I even can access all your API just in seconds, with simple script.
-
Tanmay over 4 yearsThat's the point, I can change the access token's life time to 1 second.
-
nmfzone over 4 yearsThen, just change
refresh_token
to 1 second 🤣 -
Tanmay over 4 yearsIt seems to me that you keep insisting that access_token and refresh_token are same thing. They aren't. Refresh tokens cannot have short lifetime
-
nmfzone over 4 yearsYes, it just the same, from the aspect of security. Sorry, I can't find that text. Where is it? I can only find
refresh tokens have the potential for a long lifetime
. That's it. -
Tanmay over 4 yearsThanks for your opinion. But I refuse to accept that people who've invented the refresh token, invented it for no reason. If both access and refresh token have the same purpose, why was refresh token inventend in the first place?
-
nmfzone over 4 years
-
nmfzone over 4 yearsLet us continue this discussion in chat.
-
amucunguzi over 3 yearslocalStorage is too unsafe for a refresh token (long-lived access; attackers can keep giving themselves access).
-
nmfzone over 3 years@amucunguzi From the aspect of security, localStorage is always unsafe, even for access token. It's just an alternative, read here.