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

Example with cookie auth #5821

Merged
merged 21 commits into from
Dec 14, 2018
Merged

Example with cookie auth #5821

merged 21 commits into from
Dec 14, 2018

Conversation

j0lvera
Copy link
Contributor

@j0lvera j0lvera commented Dec 5, 2018

Fixes #153

This is my attempt at #153

Following @rauchg instructions:

  • it uses an authentication helper across pages which returns a token if there's one
  • it has session synchronization across tabs
  • I deployed a passwordless backend on now.sh (https://with-cookie-api.now.sh, src) The backend is included in the repository and you can deploy everything together by running now

Also, from reviewing other PRs, I made sure to:

Here's a little demo:

GIF

@timneutkens
Copy link
Member

This PR looks pretty good @j0lv3r4! I'm going to invite you to the example repo I made but didn't finish so that you can compare notes.

@timneutkens
Copy link
Member

@timneutkens
Copy link
Member

Could you also send me a DM on Spectrum: https://spectrum.chat/users/timneutkens

@j0lvera
Copy link
Contributor Author

j0lvera commented Dec 10, 2018

thanks, @timneutkens! DM sent.

Copy link

@jsardev jsardev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was spectating the auth example issue and was interested in the final example so I left few insights into this PR 😃 Take a look in your free time!


async handleSubmit (event) {
event.preventDefault()
const username = this.state.username
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just inline this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what do you mean with inline, could you show me an example or elaborate what are you suggesting?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean... I usually bring out variables either for better readability or reusability. In the current implementation, it doesn't fulfill any of these traits - this.state.username already says clearly, that this is a username, so you could just do login({ username: this.state.username }).

But this is just nitpicking and maybe just my personal taste so you can just ignore it 😄

})
if (response.ok) {
const { token } = await response.json()
cookie.set('token', token, { expires: 1 })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good example? Storing access tokens without the secure and httpOnly attributes is vulnerable to XSS attacks. If we want to omit httpOnly, I'd suggest at least adding some info to the readme. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, but I tried to do the example as simple as possible. The only thing I'm trying to demo here is the minimal implementation you can do for cookie auth, the security and other concerns are out of the scope just for this demo.

I totally have to state this in the readme.md.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would be awesome to state that in there. People often look at the examples as "best practices" - this is my only concern here 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

httpOnly doesn't work with SSR because the server and the client both need to be able to use the token

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dav-is Of course it works, you just have to do it differently. As simple as an endpoint which returns user data if you have a valid token (cookie) does the job here. But of course we don't want to do anything like this in this example - that's why I suggested adding some info in the README.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sarneeh User logs in from client device, API adds httpOnly cookie to it's response. Fetch results from API, the browser automatically keeps the cookie in the request. The user does a hard refresh and SSR kicks in. The SSR fetches data from the API (except now it doesn't have a cookie because the other is stored on the browser of the other domain).

I guess what you're saying could be possible if the API was in a subdirectory, but I like separating SSR and the API by a subdomain. I personally don't like using cookies for auth in an API. If you're using an Auth header with a token appended on each request makes CSRF nearly impossible. Also something about using a cookie seems stateful and doesn't fit with a stateless API.

Copy link

@jsardev jsardev Dec 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dav-is I don't understand. Why do you assume that the cookie is for a different domain? This doesn't make sense to me. When you have an API endpoint for logging in, I think it's logical that it sets a cookie for the domain where the frontend is on. You can have your Next.js server on domain.com and the auth API on api.domain.com and it just works.

According to CSRF - yes, authenticating requests with an Auth header protects you from CSRF, but in this case you probably store tokens in a non-httpOnly cookie or localStorage which exposes you for XSS.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would www.domain.tld to api.domain.tld work? When I store cookies I also store them with the current domain written out so a vulnerability on another subdomain couldn't leak a users cookie.

And the difference it makes in XSS is minimal. If you had an XSS vulnerability and you were using httpOnly the malicious script might not be able to steal your token, but it could just make requests on your behalf. An XSS vulnerability could just get your username and password plaintext when a user logs in—at that point who cares about the token...

Being able to just not worry about CSRF is great and it instantly makes your API more easily made public. If you had a client that allowed you to log into multiple accounts at once and switch accounts and just provide a different Auth header with each request. This way you could maintain each session just by storing the token. Cookies are weird and stateful I kind of hate the idea of them with an API lol

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dav-is Huh, you're right! You kinda persuaded me against cookies now 😄 Even using httpOnly, when I'm vulnerable to XSS then I'm... busted. I don't know why I didn't come into this realization earlier, thanks! 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically yes, if you are vulnerable to XSS then anything can happen. But the difficulty is different. I would prefer if the attacker (for example an npm library going rogue) had to do something a bit harder than just harvesting document.cookie. So I like having auth tokens in httpOnly cookies. CSRF token (not doing auth by itself) can be non-httpOnly, that’s ok.

export const logout = () => {
cookie.remove('token')
// to support logging out from all windows
window.localStorage.setItem('logout', Date.now())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an interesting trick! I need to remember it 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a user logs out, they assume all of their data has been removed. Evidence that they have visited the site before isn't removing all data. Feel like this could have GDPR/Privacy Policy implications.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the use of Date.now keeps the date they logged out. Probably an empty object could do the trick, I need to put something there so it can trigger the event.

Other solution could be a global event emitter, but I don't think is a good idea.

I'll try with an empty object or a "random" string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@j0lvera j0lvera Dec 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally forgot about this. When withAuthSync umounts, it deletes the localStorage item.

https://github.com/j0lv3r4/next.js/blob/with-cookie-auth/examples/with-cookie-auth/utils/auth.js#L51-L52

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ok then why not call it "isLoggingOut" and set it to true or 1.

Seems borderline hacky but I like the innovation

j0lvera and others added 5 commits December 11, 2018 17:55
- Migrate to Micro
- Add backend and make everything a monorepo
- Separate each endpoint into its own file
- Edit `now.json` to deploy as monorepo
- Remove Microroutes from dependencies
- Remove unneeded comments
- Edit README.md file according to new changes on the backend server
j0lvera and others added 6 commits December 12, 2018 09:39
- Add `run` from Micro to make it work with Now
- Fix bugs in the backend not returning the right data
- Fix bug in `profile.js` to allow redirection in client and server
- Pass API URL in headers on server and window.location.host in client
Copy link
Member

@timneutkens timneutkens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the static/favicon.ico file

Copy link
Member

@timneutkens timneutkens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

Copy link
Contributor

@dav-is dav-is left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad we finally have an example for this 👍

@timneutkens timneutkens merged commit 798ae04 into vercel:canary Dec 14, 2018
@j0lvera j0lvera deleted the with-cookie-auth branch December 14, 2018 22:21
@malixsys
Copy link
Contributor

I think see some of my stuff, glad this made it in!! 😁

@timneutkens
Copy link
Member

🙌

@lukrai
Copy link

lukrai commented Jan 20, 2019

There is missing index.js file in api folder.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add login / authentication example
8 participants