Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storing the JWT token in a httpOnly cookie #126

Closed
njt1982 opened this issue Feb 16, 2019 · 17 comments
Closed

Storing the JWT token in a httpOnly cookie #126

njt1982 opened this issue Feb 16, 2019 · 17 comments
Labels

Comments

@njt1982
Copy link

njt1982 commented Feb 16, 2019

So I get that on sign up / sign in, the token it passed back in the response in the authorisation header... However it seems to me this is putting responsibility of storing the JWT securely.

If I were building a React app, for example, almost all tutorials I've seen suggest I should trust a cookie with my JWT. LocalStorage is not recommended as a secure location to store it as its accessible under and XSS attack.
Ideally, a httpOnly cookie set by the server and just left to the Browser to deal with would be the most secure, wouldn't it?

Is it possible to configure Devise JWT in this way?

It feels like the most secure approach for an app would be to never have to deal with the token at all; just let the tried-and-trusted tech (browser cookies) handle storage and transfer?

Am I missing something?

@waiting-for-dev
Copy link
Owner

waiting-for-dev commented Feb 16, 2019

The problem (which is not a problem, but a security measure) with cookies is that they can't be shared cross-domain. If your client and server live in different domains or servers, then you are stuck with headers, params and request/response body for client/server communication. If they live in the same domain, then you can use cookies for everything, including authentication, so you don't need a token based authentication at all.

It goes without saying that even you have to send the token to the server through the headers request, the client can store it in a cookie instead of the local storage.

@njt1982
Copy link
Author

njt1982 commented Feb 16, 2019

Thanks @waiting-for-dev!

That makes perfect sense. So I’d have to have my rails app provider the authorisation header in the sign in response and my react app would have to extract that and store it in the cookie for client side persistence?
I guess that solves the CSRF issue as you have to manually sign all your API requests with an auth header...

@waiting-for-dev
Copy link
Owner

So I’d have to have my rails app provider the authorisation header in the sign in response and my react app would have to extract that and store it in the cookie for client side persistence?

That's it.

I guess that solves the CSRF issue as you have to manually sign all your API requests with an auth header...

The server will only accept tokens from which the signer is itself, so that prevents from CSRF attacks.

@waiting-for-dev
Copy link
Owner

I guess this can be closed but feel free to add more comments if needed.

@GMolini
Copy link

GMolini commented Jun 26, 2019

What do you think of the approach suggested here?
https://pragmaticstudio.com/tutorials/rails-session-cookies-for-api-authentication

@njt1982 What did you end up doing?

@njt1982
Copy link
Author

njt1982 commented Jun 26, 2019

Hi @GMolini - tbh the project that needed this got put on a back burner shortly after asking this (I plan on coming back to it!). Currently I've gone with Local Storage as its easy to implement but with a massive "TODO" at the top of the file to switch it out ;)

I do something like this...

  authenticate(email, password) {
    const data = {
      user: {email: email, password: password}
    };
    localStorage.removeItem('JWT_KEY_NAME');

    return axios.post(this.apiHost + 'users/sign_in', data)
      .then(function (response) {
        localStorage.setItem('JWT_KEY_NAME', response.headers.authorization);
        return response.data
      })
      .catch(function (error) {
        console.error(error);
        throw(error)
      });
},

Then on request I do:

  authHeader() {
    return {
      headers: {
        Authorization: `${localStorage.getItem('JWT_KEY_NAME')}`
      }
    }
  },

  authRequest(method, path, params = {}) {
    const config = {
      method,
      url: this.apiHost + path,
      ...this.authHeader(),
      ...params
    };
    return axios(config)
      .catch(function (error) {
        if (error.response.status === 401) {
          localStorage.removeItem('JWT_KEY_NAME');
          window.location.href = '/login';
        }
        console.error(error);
        throw(error)
      });
},

@GMolini
Copy link

GMolini commented Jun 26, 2019

thanks for taking the time to respond! Ill have a look at it

@kvsm
Copy link

kvsm commented Jul 2, 2019

The problem (which is not a problem, but a security measure) with cookies is that they can't be shared cross-domain. If your client and server live in different domains or servers, then you are stuck with headers, params and request/response body for client/server communication. If they live in the same domain, then you can use cookies for everything, including authentication, so you don't need a token based authentication at all.

This isn't really true, you absolutely can send cookies cross-domain, you only have to configure CORS correctly (and securely). I post here because I'm looking to do this very thing - I want devise to return my token in an httpOnly cookie, and then read it from the cookie on subsequent api requests. Does devise-jwt support this?

@waiting-for-dev
Copy link
Owner

There is no reliable way of sharing cokies between domains. If user doesn't allow third party cookies in his browser, then your CORS configuration won't work. So devise-jwt doesn't support it, but you can do it manually taking the generated token from the headers and putting it into a cookie through a rack middleware.

@kvsm
Copy link

kvsm commented Jul 2, 2019

Fair point! Thanks for pointing me in the right direction.

@veeral-patel
Copy link

@njt1982 Did you end up storing the JWT in a cookie? If you could maybe share your sample code, if you have it, I'd really appreciate it!

@njt1982
Copy link
Author

njt1982 commented Sep 23, 2019

@veeral-patel sorry, no - the code still uses local storage as an "it works, lets fix it later" step... which never got fixed. (It's a personal and unpublished project atm).

@veeral-patel
Copy link

veeral-patel commented Sep 23, 2019

@njt1982 No worries. Do you have any suggestions for me for authentication (using Rails API + React SPA) if you shouldn't store JWTs in cookies, due to cross-domain issues, and if localStorage is insecure?

@moa-novae
Copy link

moa-novae commented Jul 13, 2020

I just started reading about this recently, so correct me if I am wrong. Wouldn't storing the JWT token in a cookie and signing API requests with an Authorization header be susceptible to xss? since they can read contents of a non-httponly cookie. So essentially, it would be the same as storing in localstorage

@theblang
Copy link

theblang commented Dec 9, 2020

An additional reason we are looking to store the token in a cookie is to make use of the Domain attribute to share auth state across subdomains. From the MDN docs:

Domain attribute

The Domain attribute specifies which hosts are allowed to receive the cookie. If unspecified, it defaults to the same origin that set the cookie, excluding subdomains. If Domain is specified, then subdomains are always included. Therefore, specifying Domain is less restrictive than omitting it. However, it can be helpful when subdomains need to share information about a user.

For example, if Domain=mozilla.org is set, then cookies are available on subdomains like developer.mozilla.org.

@jeremylynch
Copy link

Please see this fork of devise-jwt: https://github.com/scarhand/devise-jwt-cookie

It is not clear if this gem support cross-domain JWT cookie. scarhand/devise-jwt-cookie#11

@absolute-boi
Copy link

@jeremylynch to answer your question, yes, the devise-jwt-cookie supports http only cookie authentication with devise. You need to use the rack-cors gem to configure a cors config file to allow headers across domains. as stated above, this is a way to get cookie based authentication w/o the xss threat. however, also stated above, this will not work if a user specifically sets their browser to block cross domain cookies. so keep that in mind.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants