Storing refresh_token in cookie

Tl;Dr;

Is it considered safe to store a refresh_token in a cookie if the cookie is marked HTTP-only and is only transmitted over HTTPS?

Longer version

We are creating a solution with a frontend SPA (VueJS) and the backend is Asp.Net Core. Every page in the solution is provided trough the Vue-SPA. The initial first-page is served up with a simple controller action with Asp.Net Core. The loginpage is served up with Vue, using vue-router. Based on if there is an access_token present in the local storage or not the user is considered logged in. When this access_token expires or is invalid, we want to use a refresh token to get a new access token. What we ended up doing was storing the refresh_token in a cookie that is sent back to the “DotNet Core Backend” when asking for a new access token. Here is a description of how the application works:

  1. The loginpage POST the username and password to the backend API

  2. The controller-action uses the Auth0-Authentication nuget-package and authenticates the user, adding the offline_access-scope. If success it adds the users auth0id and the refresh_token to the users claims. The cookie-path is set to ‘/account’, so it is only sent to the server when accessing something under the ‘/account’-path. The action method sets the cookie and returns a 200 OK with an access_token.

  3. Now the Vue-client stores the access_token in localstorage and the user is logged in.

  4. Whenever the Vue-client accesses the backend API we set the access_token (Bearer token) and the API receives the token and does its thing. And we see that the cookie is not sent when the path is ‘/api’-something.

  5. When the token expires we intercept the 401 and do a GET on ‘/account/accessToken’. Now on we see that the cookie is sent to the server, and on the server we can find the refresh_token in the users claims. Again, using the Auth0-Authentication nuget-package we can ask for a new access token providing the refresh token. If the refresh_token is not revoked, we get a new access_token and returns that to the client, and the client retries the initial request with the new access token.

  6. If we serverside see that the refresh_token is invalid (ex. has been revoked) we wipe the cookie, and redirect the user to the loginpage, and the user will have to sign in again.

With this solution we can customize the loginpage as we want, the user stays on our domain, in comparison to when using the hosted loginpage. We have also found a way to re-authenticate users without using the hosted loginpage and the renewAuth-method with the silent callback.

Since we are talking about security here, I would like to get some feedback and thoughts from someone more seasoned in security than myself.

Is this considered a safe and viable solution? Are there any security issues to be aware of?

Thanks to Auth0 for a great product.

This is a really interesting solution. I thought something similar when deciding to switch from refresh tokens to renewAuth and found no way to keep the user logged in aside from reinventing the wheel and having to handle sessions server side for a SPA. Also, Auth0 already provides this solution but it is not cusomizable.
The safest place where to put the refresh token is on the server, but this requires to switch to authorization code flow, keep the token on the server, keep the session, etc.
Using http only cookies may be interesting… But still, you are in the hands of the browser.

I am really interested in this solution… A few questions:

Step 1: Do you plan to make the POST using ajax?

Step 1b: How do you handle new users creation instead?

What’s your audience value? Your client or your backend API?

What do you do at explicit user logout? Do you invalidate the refresh token using Auth0’s management API?

Thanks!

Last thought regarding safety: it seems to me that renewAuth mechanism is working in a similar way. They don’t store the refresh token, they store some session data in an http only cookie instead. Still, unless the session is someway limited to an IP address or in some other way, if someone can steal your token, he can also steal the session data from Auth0’s cookie.

  1. Yes, the POST from the login-page is done with Ajax using Axios
    1b) We will use the ManagementAPI-nuget. We will take on more responsibility with verifying users, resetting password etc. by having this in our own code. But since this is a invite-only application, we believe it is a viable approach for us on this project.

The audienceValue is for the API.

On user logout we do ajax-POST to ‘/account/logout’ to clear the cookie, and on client we delete access_token from localStorage. Atm we do not invalidate the refresh_token, but I believe we should do that. Thanks for pointing that out.

Which flow are you going to use for getting the refresh token? I asked about the audience because if it’s your API, I think you will have to use OIDC that should require a redirect (authorization code flow). Have you already manually tested the entire flow?

We are using the Resource Owner Grant flow. Our dotnet core backend receive the access_token and refresh_token in the result. We then add the refresh_token to the users claims, that is signed in with cookie middleware, and we return the access_token to the client. We now have a working solution like the one previously outlined. But the question still remains, is this a good idea? Or are there any pitfalls?