nuxt-community / auth-module

Zero-boilerplate authentication support for Nuxt 2
https://auth.nuxtjs.org
MIT License
1.93k stars 926 forks source link

HTTP only cookie auth mode #142

Closed sky-code closed 3 years ago

sky-code commented 6 years ago

I want to store my auth token in HTTP only cookie, with version 3.4.1 this scheme worked, but after update to 4.1.0 I get strange behavior, I can authorize but after reloading page, I am not authorized any more. So can you please add proper support for HTTP only cookie auth strategy, and add documentation for this usage scenario ?

This question is available on Nuxt.js community (#c106)
luiseok commented 6 years ago

in issue #92 , @breakingrobot said

Also, setting httpOnly will involve that Nuxt will not be able to modify the cookie client side, which for a static website or a SPA would be a bad idea.

sky-code commented 6 years ago

which for a static website or a SPA would be a bad idea.

Why is a bad idea? I just want to get more secure solution, and for previous version this strategy work fine, with this config

token: {
            enabled: false,
            localStorage: false,
            cookie: false,
        }

but for new version it doesn't work. There are no reasons to not support this token storage method, just need a documentation how to use auth-module with https only cookies

breakingrobot commented 6 years ago

By using httpOnly cookie, Nuxt won't be able to interact with it on client-side.

@pi0 we should have a discussion about cookies again asap.

sky-code commented 6 years ago

By using httpOnly cookie, Nuxt won't be able to interact with it on client-side.

Yes I know that, but it is also not necessary. Auth module hit user endpoint, if response is 200 OK then set loggedIn to true, otherwise false, cookies will be sent automatically, access to cookies isn't required, same logic with logout. In V3 I used such approach and it worked.

sky-code commented 6 years ago

@breakingrobot approach is not wrong, is different, I understanding that it not the best option, but In my case, HttpOnly much better for security, thats all.
If you don't need this, use another approach, but auth-module can handle this storage method for those who need this.

ggirodda commented 6 years ago

I will start to use this module, but I agree with @sky-code about the possibility to use a HttpOnly cookie at least for the refresh_token. Is this one also stored in a non HttpOnly cookie? I think that it can be vulnarable to XSS attacks, am I wrong?

ghost commented 6 years ago

Did you make any progress on this @sky-code ? I'm currently trying to achieve the same goal.

sky-code commented 6 years ago

@NetBzz No, I have switched off HttpOnly for now

jsardev commented 6 years ago

@breakingrobot Storing the tokens in a httpOnly cookie does not break anything, it's just another approach. Actually, it's a lot safer approach as it mitigates XSS vulnerabilities. If you're storing secret tokens in a non-httpOnly cookie you're a very easy target. It is most certainly not a safe way to store tokens on the client-side.

Take a look at this: https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage

sky-code commented 6 years ago

@sarneeh that's what i am talking about. auth-module must have the first class support, for the httpOnly cookie auth flow and it must also have the clear instruction how to use it.

araratmartirossyan commented 5 years ago

But if no have httpOnly flow, how we can protect our cookies?

rur0 commented 5 years ago

I am using an approach where I split my JWT into two cookies containing signature.payload and signature. The cookie containing signature is httpOnly and signature.payload can be accessed by the browser. This way I have the safety of httpOnly cookies while getting the user info in the payload and not risking of leaking the full JWT, as they are separated. I think this is a reasonable and should be implemented. Meanwhile I guess I will have to write a custom solution.

blowsie commented 5 years ago

I'm a bit confused on the outcome of this conversation; Am I right in thinking its currently not possible to authenticate and keep session using httpOnly cookies?

sky-code commented 5 years ago

@blowsie try to use https://auth.nuxtjs.org/schemes/local.html#tokenrequired tokenRequired option

blowsie commented 5 years ago

Thanks @sky-code but doing this had no effect for me. The login request work perfectly fine, sends set-cookie headers, but the next request to user does not send any cookies.

My config;

 auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            url: '/api/auth/login',
            method: 'post',
            propertyName: 'result'
          },
          logout: { url: '/api/auth/logout', method: 'post' },
          user: { url: '/api/auth/ping', method: 'get', propertyName: 'result' }
        },
        tokenRequired: false
      }
    }
  },

This authentication Rest API is working perfectly fine in other solutions of mine

blowsie commented 5 years ago

This was due to my application using secure cookies but i wasnt running https 🤦‍♂ . Thanks!

Jack74r commented 5 years ago

But if no have httpOnly flow, how we can protect our cookies?

Is it a good idea to encrypt the token?

jsardev commented 5 years ago

@Jack74r The problem is not that the token is going to be decoded, but stolen. Encrypting the token won't give you anything in a situation of a stolen access token.

zspine commented 5 years ago

@sky-code @sarneeh OMG!!! I am in the process of migrating a vuejs SPA app to nuxt framework (SPA only and not Universal) and I just realized this issue. I really don't understand the complication here (I think I am missing something). I always listen for 401 from server responses and invalidate the local session.

@breakingrobot is there any way to extend this behavior with a plugin?

zspine commented 5 years ago

@sky-code @sarneeh @breakingrobot Sorry! I guess my problem is not relevant here.... I solved the issue with following configuration

  auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            url: '/login',
            method: 'post',
            propertyName: 'data',
            withCredentials: true
          },
          user: {
            url: '/is-logged-in',
            method: 'get',
            withCredentials: true
          },
          logout: false
        },
        tokenRequired: false,
        tokenType: false
      }
    }
  }

Setting the axios to 'withCredentials: true' solves my issue!

EryouHao commented 4 years ago

This question any progress? How to set httpOnly useapolloHelpers.onLogin ?

chanar commented 4 years ago

Setting propertyName: false for user endpoint made it work for me

auth: {
    strategies: {
      local: {
        endpoints: {
          login: { url: '/auth/login', method: 'post'},
          logout: { url: '/auth/logout', method: 'post' },
          user: { url: '/auth/me', method: 'post', propertyName: false }
        },
        tokenRequired: false,
        tokenType: false
      }
    }
  },
mckraemer commented 4 years ago

The new version will also provide a schema for refresh tokens. Will it be possible to set them as httpOnly cookies here? Especially refresh tokens, which are long-lived, should not be stored just like that.

jdbdnz commented 4 years ago

I left a pretty lengthy explanation of why you should absolutely not listen to anyone telling you it's ok to set session credentials anywhere client side JavaScript can access on the original issue https://github.com/nuxt-community/auth-module/issues/92

I've been digging into trying to get to get httpOnly to work with the module for about an hour, and come to the conclusion that maybe I shouldn't be using an auth module where the authors express ideas and opinions like those shared above ^

fahrinh commented 4 years ago

I am using an approach where I split my JWT into two cookies containing signature.payload and signature. The cookie containing signature is httpOnly and signature.payload can be accessed by the browser. This way I have the safety of httpOnly cookies while getting the user info in the payload and not risking of leaking the full JWT, as they are separated. I think this is a reasonable and should be implemented. Meanwhile I guess I will have to write a custom solution.

I agree with @rur0 's approach. Server only verifies cookie with httpOnly (signature)

chanar commented 4 years ago

In what cases it's important to use payload compared to doing request against /auth/me? Because of unnecessary api call on every route change?

I have httpOnly cookie setup in the following way:

  1. When I log in, server sends back httpOnly cookie.
  2. When I receive a request on backend, I have a middleware that sets the Bearer token (Laravel)
if ($request->hasCookie('...')) {
  $token = $request->cookie('...');
  $request->headers->add(['Authorization' => 'Bearer ' . $token]);
}

And in my nuxt config

strategies: {
      local: {
        endpoints: {
          user: { url: '/auth/me', method: 'post', propertyName: false }
        },
        tokenRequired: false,
        tokenType: false
      }
    }
harsha935 commented 4 years ago

In what cases it's important to use payload compared to doing request against /auth/me? Because of unnecessary api call on every route change?

I have httpOnly cookie setup in the following way:

  1. When I log in, server sends back httpOnly cookie.
  2. When I receive a request on backend, I have a middleware that sets the Bearer token (Laravel)
if ($request->hasCookie('...')) {
  $token = $request->cookie('...');
  $request->headers->add(['Authorization' => 'Bearer ' . $token]);
}

And in my nuxt config

strategies: {
      local: {
        endpoints: {
          user: { url: '/auth/me', method: 'post', propertyName: false }
        },
        tokenRequired: false,
        tokenType: false
      }
    }

@chanar Your httpOnly cookie came from Laravel API? So that cookie will show in the API request instead of storing in the browser, In that case, that cookie will disappear when you are refreshing the browser. what you did for persisting your httpOnly cookie?

t3hmrman commented 4 years ago

Not that we needed another opinion in here, but just to make it clear -- HTTPOnly cookies are absolutely a security best practice, it protects from many common forms of XSS attacks.

There seems to already be a PR for this:

https://github.com/nuxt-community/auth-module/pull/390

I'm going to have to move off this library but it would be nice if that PR was given a second look, I'd be willing to help work on it if the submitter is no longer responsive.

[EDIT] - I finally got around to writing a small post on how I got cookie-based auth working without nuxt-auth for now. As always thanks for all the hard work to the nuxt-auth team, will be looking to see if that PR gets looked at/merged!

dekadentno commented 4 years ago

Not using HTTPOnly cookies is often reported by Dynamic application security testing tools like Burp and penetration testing tools like OWASP Zed. It is important to use them and it should at least be optional to use them in this marvelous module. I think it is bad to just ignore them. Also, this issue is dating back from 2018 :(

myvo commented 3 years ago

Maybe it will be helpful https://github.com/nuxt/nuxt.js/issues/575#issuecomment-393514815

kissu commented 3 years ago

Interesting article on why this is important: https://dev.to/cotter/localstorage-vs-cookies-all-you-need-to-know-about-storing-jwt-tokens-securely-in-the-front-end-15id

ghost commented 3 years ago

I think this is a pressing problem at 2020 for an authentication module..

A potential malicious actor that had somehow managed to execute untrusted JavaScript in the web application would be able to take over other users' sessions.

At the very minimum I'd add a warning in the module's documentation and a technical explanation what's taking this long.

chanar commented 3 years ago

@chanar Your httpOnly cookie came from Laravel API? So that cookie will show in the API request instead of storing in the browser, In that case, that cookie will disappear when you are refreshing the browser. what you did for persisting your httpOnly cookie?

@harsha935 Yes from Laravel but from any backend, send the cookie with httpOnly.

Just for the update if it helps anyone. I have removed auth configuration from my nuxt config. My axios module has credentials set to true and that's it for my app.

axios: {
  baseURL: process.env.API_URL,
  credentials: true
}

When I login/register, I send a response with httpOnly cookie with value of token.

Whenever I refresh the page, close the tab or window I still have my cookie token included in request headers. When I need to use axios "outside" the Nuxt I use options { withCredentials: true }

pi0 commented 3 years ago

Hi. A quick update on this issue:

Closing issue as cookie scheme is already supported with v5 (which is now used as main docs) and most of concerns are related to API/Frontend misconfiguration not auth module. Still we would need to improve docs to make it more bold.

spire-mike commented 3 years ago

I'm trying to use the cookie-schema. After logging in, I can see my cookie being set, but the front-end still doesn't think I am logged in. This appears to be because the cookie that gets set is http-only. When I refresh the page, the site now knows I am logged in.

I am not sure what I'm doing wrong. This is my auth configuration:

strategies: {
    cookie: {
      cookie: {
        name: 'SHOP_TOKEN'
      },
      user: {
        property: 'body.data.customer',
        autoFetch: false,
      },
      endpoints: {
        login: { url: '/customerLogin', method: 'post' },
      }
    }
}

After logging in, I see the SHOP_TOKEN cookie being set:

image

But $auth.loggedIn remains false until I do a full page refresh. What am I missing here? I've been at this for hours and am not having any luck. I must be missing something obvious. Can anybody help?

sPaCeMoNk3yIam commented 3 years ago

I'm trying to use the cookie-schema. After logging in, I can see my cookie being set, but the front-end still doesn't think I am logged in. This appears to be because the cookie that gets set is http-only. When I refresh the page, the site now knows I am logged in.

I am not sure what I'm doing wrong. This is my auth configuration:

strategies: {
  cookie: {
    cookie: {
      name: 'SHOP_TOKEN'
    },
    user: {
      property: 'body.data.customer',
      autoFetch: false,
    },
    endpoints: {
      login: { url: '/customerLogin', method: 'post' },
    }
  }
}

After logging in, I see the SHOP_TOKEN cookie being set:

image

But $auth.loggedIn remains false until I do a full page refresh. What am I missing here? I've been at this for hours and am not having any luck. I must be missing something obvious. Can anybody help?

Same for me, did you get it to work?

hamblin commented 3 years ago

Hi @spire-mike and @sPaCeMoNk3yIam - I had the same issue with cookie scheme on HTTP-only cookies, but found a way to make it work.

After way too much time debugging it, I got it to work simply by removing the cookie name in my auth config (ie: the name of my HTTP-only cookie sent from my backend). It worked either by setting strategies.cookie.cookie.name to null, or removing the name key completely, or removing the cookie.cookie key altogether. So under strategies.cookie I just have 'endpoints' and 'user'. (From source code, when Nuxt instantiates the cookie scheme, it has a default of cookie.coookie.name = null... so that's what will end up getting set)

I also got it to work by keeping the name of my backend cookie in auth config (cookie.cookie.name = 'name_of_my_cookie') but making it not HTTP-only. Obviously that is not advised, and not what we all were trying to do. I only did it to help figure out what was happening.

Anyways, removing the cookie name (and keeping the cookie HTTP-only) then allowed Nuxt to successfully set the $auth state to loggedIn = true, and it fetched the user data from the user endpoint.

I didn't completely go through all the source code to understand how this works, but I believe that setting cookie.cookie.name to the name of an HTTP-only cookie is what causes problems. If you DON'T provide the name of your own cookie, then Nuxt seems to look at its own cookie(s) (ie: auth._token.cookie with a value of true) and that's what it will use to know the logged in state. But if you DO provide the name of your own cookie in cookie.cookie.name, then it will try to use that to know the logged-in state (via the Scheme.check() method I believe) but if its HTTP-only it won't find the cookie, and Scheme.check() will return false, Scheme.fetchUser() will never get executed during the initial login. Not exactly sure why doing a hard refresh worked... but I noticed the same thing.

Bottom line... removing cookie name made it work, even with HTTP-only cookie.

abernh commented 2 years ago

I didn't completely go through all the source code to understand how this works, but I believe that setting cookie.cookie.name to the name of an HTTP-only cookie is what causes problems.

The check is happening here: https://github.com/nuxt-community/auth-module/blob/a6ee431fe1c696fc184d6b2ad94fd79a706acac7/src/schemes/cookie.ts#L68

If you DON'T provide the name of your own cookie, then Nuxt seems to look at its own cookie(s) (ie: auth._token.cookie with a value of true) and that's what it will use to know the logged in state.

Before this line you can see the call to super.check() which checks if there was a successful token request in the past (retrieved from auth.$storage.syncUniversal) and based on that assume you are logged in (or not). If you'd have a cookie name set it would now check if that cookie is still set/has not expired yet (and would fail). But so it returns true for "user has successfully requested a token in the past".

(Slightly off topic: it would also be nice if httpOnly cookies would at least be "visible" (as in cookies[name] -> true but without revealing its contents) to JS as knowing that a cookie is set (whatever its contents) simply means that it was once received and hasn't expired yet ... another debate for another time)

So what is happening is:

This also means: if you want to know if your cookie meanwhile expired you might want to do a userFetch() on each page-load just to make sure you have still the necessary valid credentials in form of that invisible cookie

gelinger777 commented 2 years ago

In my case i have no bearer at all and need to authorize on a third party API and reuse the cookie set by api in further calls. can anybody hint what will be the strategy settings for this case? There is no refresh token and