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

Add login / authentication example #153

Closed
rauchg opened this issue Oct 29, 2016 · 209 comments · Fixed by #5821
Closed

Add login / authentication example #153

rauchg opened this issue Oct 29, 2016 · 209 comments · Fixed by #5821
Assignees

Comments

@rauchg
Copy link
Member

rauchg commented Oct 29, 2016

With:

  • re-usable authentication helper across pages
  • session synchronization among tabs
  • simple passwordless email backend hosted on now.sh

I think this will be hugely helpful to a lot of newcomers.

@nodegin
Copy link
Contributor

nodegin commented Oct 29, 2016

Suggestion: Use Redux and JWT to accomplish the example

@nsantini
Copy link

Im working on an example for this. Currently having issues getting componentWillReceiveProps to fire on my high level component (where Im planning to check if user is authenticated and redirect to login page if not)

@jaredpalmer
Copy link
Contributor

jaredpalmer commented Nov 2, 2016

So I have auth working swimmingly. As mentioned elsewhere, it's client-side only, which is ultimately just half the battle.

"Pretty-secure"

Like php, the atomic unit of Next is the page. One of the coolest features is that it lazy loads each page only when it's requested. With client-side only auth but with server-rendering, the js for that protected page is in fact downloaded by the browser. In the future when Next adds server workflows, you'll hopefully be able to block render and redirect on the server to prevent this entirely. This will require cookies, sessions, and AFAIK session stores, but that's just the cost of doing hybrid apps like these.

Auth Example

Assume you have a JWT-secured API with two endpoints of interest: /token and /me. /token accepts email/password credentials and returns a signed JWT (id_token) while /me returns profile information related to the JWT-authenticated user. I adapted the following AuthService.js from Auth0's lock (removing event emitter, although that's not the worst idea). It extracts almost all of the JWT token handling so it can be used on the login page and also in a Higher Order Component (more on that later).

// utils/AuthService.js
export default class AuthService {
  constructor(domain) {
    this.domain = domain || 'http://localhost:5000'
    this.fetch = this.fetch.bind(this)
    this.login = this.login.bind(this)
    this.getProfile = this.getProfile.bind(this)
  }

  login(email, password) {
    // Get a token
    return this.fetch(`${this.domain}/token`, {
      method: 'POST',
      body: JSON.stringify({
        email,
        password
      })
    }).then(res => {
      this.setToken(res.id_token)
      return this.fetch(`${this.domain}/user`, {
        method: 'GET'
      })
    }).then(res => {
      this.setProfile(res)
      return Promise.resolve(res)
    })
  }

  loggedIn(){
    // Checks if there is a saved token and it's still valid
    const token = this.getToken()
    return !!token && !isTokenExpired(token) // handwaiving here
  }

  setProfile(profile){
    // Saves profile data to localStorage
    localStorage.setItem('profile', JSON.stringify(profile))
  }

  getProfile(){
    // Retrieves the profile data from localStorage
    const profile = localStorage.getItem('profile')
    return profile ? JSON.parse(localStorage.profile) : {}
  }

  setToken(idToken){
    // Saves user token to localStorage
    localStorage.setItem('id_token', idToken)
  }

  getToken(){
    // Retrieves the user token from localStorage
    return localStorage.getItem('id_token')
  }

  logout(){
    // Clear user token and profile data from localStorage
    localStorage.removeItem('id_token');
    localStorage.removeItem('profile');
  }

  _checkStatus(response) {
    // raises an error in case response status is not a success
    if (response.status >= 200 && response.status < 300) {
      return response
    } else {
      var error = new Error(response.statusText)
      error.response = response
      throw error
    }
  }

  fetch(url, options){
    // performs api calls sending the required authentication headers
    const headers = {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    }

    if (this.loggedIn()){
      headers['Authorization'] = 'Bearer ' + this.getToken()
    }

    return fetch(url, {
      headers,
      ...options
    })
    .then(this._checkStatus)
    .then(response => response.json())
  }
}

Next up is a HOC to make protecting pages simpler. To prevent an unwanted flash of sensitive info, the page will server-render Loading... on first render while react boots up / reads the token from localStorage. This means that protected pages will not SEO, which is probably okay as of now, but definitely not optimal.

// utils/withAuth.js - a HOC for protected pages
import React, {Component} from 'react'
import AuthService from './auth'

export default function withAuth(AuthComponent) {
    const Auth = new AuthService('http://localhost:5000')
    return class Authenticated extends Component {
      constructor(props) {
        super(props)
        this.state = {
          isLoading: true
        };
      }

      componentDidMount () {
        if (!Auth.loggedIn()) {
          this.props.url.replaceTo('/')
        }
        this.setState({ isLoading: false })
      }

      render() {
        return (
          <div>
          {this.state.isLoading ? (
              <div>LOADING....</div>
            ) : (
              <AuthComponent {...this.props}  auth={Auth} />
            )}
          </div>
        )
      }
    }
}
// ./pages/dashboard.js
// example of a protected page
import React from 'react'
import withAuth from  '../utils/withAuth'

class Dashboard extends Component {
   render() {
     const user = this.props.auth.getProfile()
     return (   
         <div>Current user: {user.email}</div>
     )
   }
}

export default withAuth(Dashboard) 

The login page can't use the HOC as it stands now, because Login needs be public. So it just makes an instance of AuthService directly. You would do something similar for a Signup page too.

// ./pages/login.js
import React, {Component} from 'react'
import AuthService from '../utils/AuthService'

const auth = new AuthService('http://localhost:5000')

class Login extends Component {
  constructor(props) {
    super(props)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  componentDidMount () {
    if (auth.loggedIn()) {
      this.props.url.replaceTo('/admin')   // redirect if you're already logged in
    }
  }

  handleSubmit (e) {
    e.preventDefault()
    // yay uncontrolled forms!
    auth.login(this.refs.email.value, this.refs.password.value)
      .then(res => {
        console.log(res)
        this.props.url.replaceTo('/admin')
      })
      .catch(e => console.log(e))  // you would show/hide error messages with component state here 
  }

  render () {
    return (
      <div>
         Login
          <form onSubmit={this.handleSubmit} >
            <input type="text" ref="email"/>
            <input type="password" ref="password"/>
            <input type="submit" value="Submit"/>
          </form>
      </div>
    )
  }
}

export default Login

Inspired by Airbnb's react-with-styles, I also started working on a next-with-auth lib which would be a function returns a HOC to be used on pages. I also played with merging AuthService and this HOC. One solution might be to make this HOC accept a permission level function as an argument in addition to the component, like redux connect. Regardless, in my mind, you would use next-with-auth like this:

// ./utils/withAuth.js
import nextAuth from 'next/auth'
import parseScopes from './parseScopes'

const Loading = () => <div>Loading...</div>

export default nextAuth({
  url: 'http://localhost:5000',
  tokenEndpoint: '/api/token',
  profileEndpoint: '/api/me',
  getTokenFromResponse: (res) => res.id_token,
  getProfileFromResponse: (res) => res,
  parseScopes,
})

Doing this all with Redux seemed unnecessarily complicated, but basically you can follow the wiki example, but move AuthService into Actions (login and logout) and have a User Reducer. You could only call these actions on the client though, since there isn't localStorage on the server, so you need to check for that in your Actions. Ultimately, redux store is put on the window anyways. So you could just a well cache the user on window on your own instead of using context. If you don't want redux, you can also try out react-broadcast.

Lastly, assuming next/server ships according to #25. next-with-auth could abstract complicated localStorage vs. cookie stuff away from the developer with middleware + a HOC. It could also handle token refreshing too.

@ugiacoman
Copy link

Excited to try this out! Thanks for the barebones implementation :)

@amccloud
Copy link
Contributor

amccloud commented Nov 2, 2016

@jaredpalmer I'm working on something similar. How does your AuthService work when a component is rendered server side? The server would need access to the JWT but can't read it from local storage.

@jaredpalmer
Copy link
Contributor

@amccloud It doesn't. That's the whole issue. The HOC renders <div>Loading..</div> on protected routes and must read the token and decide whether or not to redirect in componentDidMount. For it to work the way you want it to and render server-side, Next needs #25, or at least the ability to set a cookie with the value of the JWT AFAIK.

@luisrudge
Copy link

I used cookie-js to set a cookie, but it's a bit of a hack..
the thing is: if you don't send a cookie, you lose all the benefits of nextjs and server side rendering in authenticated routes

@impronunciable
Copy link
Contributor

@jaredpalmer this is great! thanks for the effort. I'll try to finish implementing your example (or help you doing it if you want) in the following days

@luisrudge
Copy link

Yo! I published an example with nextjs and auth0 here: https://github.com/luisrudge/next.js-auth0
It has the concept of a main layout and also "secure pages" that load only when the user is authenticated.
Let me know what you think 🎉

@impronunciable
Copy link
Contributor

@luisrudge amazing. I'm cloning and doing some changes but looks great

@luisrudge
Copy link

Cool! What do you think it's missing? What changes are you thinking?

On Sun, Nov 6, 2016 at 1:12 PM -0200, "Dan Zajdband" <notifications@github.commailto:notifications@github.com> wrote:

@luisrudgehttps://github.com/luisrudge amazing. I'm cloning and doing some changes but looks great

You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com//issues/153#issuecomment-258687108, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AA5cE8NIsvQ_ITjc1gArTFgNXzEda4TSks5q7e5NgaJpZM4KkJmi.

@impronunciable
Copy link
Contributor

  1. Using standard for linting (so it's consistent with everything we are building with next)
  2. Adding multi-tab support requested by @rauchg
  3. The css part can be simplified

I'll send you a pr :)

@luisrudge
Copy link

What do you mean by multi tab support?

On Sun, Nov 6, 2016 at 1:16 PM -0200, "Dan Zajdband" <notifications@github.commailto:notifications@github.com> wrote:

  1. Using standard for linting (so it's consistent with everything we are building with next)
  2. Adding multi-tab support requested by @rauchghttps://github.com/rauchg
  3. The css part can be simplified

I'll send you a pr :)

You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com//issues/153#issuecomment-258687373, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AA5cE1A6jq4KZc9_ynukTCI4mU-rdsNaks5q7e81gaJpZM4KkJmi.

@impronunciable
Copy link
Contributor

impronunciable commented Nov 6, 2016

You have 2 open tabs, logout on 1, automatically logs out on the other

@luisrudge
Copy link

Ahh. That's super cool!

On Sun, Nov 6, 2016 at 1:21 PM -0200, "Dan Zajdband" <notifications@github.commailto:notifications@github.com> wrote:

You have 2 open tabs, logout on 1, automatically logs out on the others

You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com//issues/153#issuecomment-258687707, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AA5cE9e2DA4_GgNQIVTMp0hx74G-6RmUks5q7fBfgaJpZM4KkJmi.

@impronunciable
Copy link
Contributor

Hi @luisrudge I sent you a PR with the changes https://github.com/luisrudge/next.js-auth0/pull/2

thank you so much for doing this <3

@impronunciable
Copy link
Contributor

btw this is the result:

2016-11-06 11 14 31

@ugiacoman
Copy link

ugiacoman commented Nov 6, 2016

@impronunciable @luisrudge Fantastic implementation! If you want to use it without Auth0, it looks you'd only need to change the files in the ./utils dir, maybe even just lock.js. I'll be trying this out soon. Btw the multi-tab looks awesome 💯

@impronunciable
Copy link
Contributor

@ugiacoman I've started implementing a small server with passwordless.net, let me know if you want to get my code as a starting point

@ugiacoman
Copy link

@impronunciable That'd be awesome! I was actually going to do something similar with Twitter Fabric's Digits.

@jaredpalmer
Copy link
Contributor

@impronuncible i suggest not using password less.net , instead you can just use passport-local, and just send users a link with their email and token in query string.

@luisrudge
Copy link

Thanks @impronunciable ❤️

@ugiacoman yeah, it's pretty easy to remove the auth0 dependency. I used it because I didn't want to have a separate api to handle auth

@sedubois
Copy link
Contributor

sedubois commented Nov 10, 2016

@jaredpalmer as far as I know, having #25 would be great but isn't blocking? I mean we have access to the server-side req in getInitialProps so nothing prevents applying cookie-parser to it? Server-side auth and session management is all new stuff to me 😬

BTW considering localStorage can't be used server-side, are cookies the only way to have server-side sessions? I have a vague remembrance it might not be the safest? But is there any other option?

@eezing
Copy link

eezing commented Dec 1, 2016

@sedubois

The cookie approach can be very safe if done properly. Doing the following is fairly trivial:

  • use httpOnly flag (prevents JavaScript access to cookie)
  • use secure flag (only set cookie for https requests)
  • Signed cookies (verify source of cookie)

@rauchg
Copy link
Member Author

rauchg commented Dec 3, 2016

There's also a very significant latency advantage when you can access authentication information directly on the server.

timneutkens pushed a commit that referenced this issue Dec 14, 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
- <strike>I deployed a passwordless backend on `now.sh` (https://with-cookie-api.now.sh, [src](https://github.com/j0lv3r4/next.js-with-cookies-api))</strike> 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:

- use [isomorphic-unfetch](https://www.npmjs.com/package/isomorphic-unfetch).
- use [next-cookies](https://www.npmjs.com/package/next-cookies).

Here's a little demo:

![GIF](https://i.imgur.com/067Ph56.gif)
@lishine
Copy link

lishine commented Dec 26, 2018

@jaredpalmer you wrote
Like php, the atomic unit of Next is the page. One of the coolest features is that it lazy loads each page only when it's requested. With client-side only auth but with server-rendering, the js for that protected page is in fact downloaded by the browser. In the future when Next adds server workflows, you'll hopefully be able to block render and redirect on the server to prevent this entirely. This will require cookies, sessions, and AFAIK session stores, but that's just the cost of doing hybrid apps like these.

We are 2 years later. Is there a server workflow to prevent loading js for protected pages?
@timneutkens maybe puting protected content in other zone?
How would I entirely prevent access to protected content ?

@jsardev
Copy link

jsardev commented Dec 28, 2018

@lishine You have a ServerResponse in the getInitialProps of your page - you can easily redirect someone unprivileged.

@affanshahid
Copy link

Is there an example of auth with redux?

@alan2207
Copy link

Is there an example of auth with redux?

You can try this example which is using redux, and check out if it works for you...
You can find it somewhere in this topic, but in case you can't find it, here it is:
https://github.com/alan2207/nextjs-jwt-authentication

@letsspeak
Copy link

I think this is more complicated problem when using Server Side API call results getInitialProps, because Virtual DOM uses old results after LOGOUT-LOGIN action. I'm thinking about solution

@letsspeak
Copy link

letsspeak commented Feb 3, 2019

EDITED
and here's my answer with redux-observable

Side Auth TODO
Server true Fetch initial data (with request cookie proxy).
Server false Show login page, and fetch data after logined.
Client true Fetch initial data.
Client false Show login page, and fetch data after logined. (This happens only when session expired in page to page moving)
  static async getInitialProps({ store, query: { rowsPerPage, pageIndex }, req, auth }) {
    store.dispatch(TemporaryStoryActions.initPageState());

    const isAuthenticated = () => req ? req.isAuthenticated()
      : store.getState().auth.isAuthenticated;

    if (isAuthenticated()) {
      // fetch initial data
      const TemporaryStoryApiProxy = withCookieProxy(req, TemporaryStoryApi);
      await TemporaryStoryApiProxy.fetchTemporaryStories({
        rowsPerPage: rowsPerPage || 15,
        pageIndex: pageIndex || 0,                                                                                                                                  }).then(json => {
        store.dispatch(TemporaryStoryActions.loadTemporaryStories(
          json.rowsPerPage, json.pageIndex, json.count, json.rows));
      }).catch(error => {
        if (error.response && error.response.status === 403) {
          store.dispatch(AuthActions.initState(false, null));
          return;
        }
        throw error;
      });
    }

    if (!isAuthenticated()) {
      // => if client side fetch failed with 403, isAuthenticated() turns off to false
      // register logined action for client side login succeeded
      const reloadAction = TemporaryStoryActions.fetchTemporaryStories({
        rowsPerPage: rowsPerPage || 15,
        pageIndex: pageIndex || 0,
      });
      store.dispatch(AuthActions.addLoginedAction(reloadAction));
    }

    return {
      ...store.getState(),
    }                                                                                                                                                           }                                                                                             }
export const withLogin = Page => class SecurePage extends React.Component {
  static async getInitialProps (ctx) {
    if (ctx.req && ctx.store) {
      // server side
      const isAuthenticated = ctx.req.isAuthenticated();
      const { user } = ctx.req;
      ctx.store.dispatch(AuthActions.initState(isAuthenticated, user));
    }
    return Page.getInitialProps && await Page.getInitialProps(ctx)
  }

  render () {
    const { auth } = this.props;
    return auth.isAuthenticated ? <Page {...this.props} /> : <LoginPage />
  }                                                                                                                                                           }
// when [front-end server] => [api server]
// on Server Side Rendering,
// needs to proxy Cookies which sent to Next.js page request
export const withCookieProxy = (req, targetApi) => {
  if (!req) {
    return targetApi;
  }
  targetApi.client.interceptors.request.use(config => {
    const cookieString = Object.keys(req.cookies).map(key => `${key}=${req.cookies[key]}`).join('; ');
    const headers = {
      ...(config.headers || {}),
      Cookie: cookieString,
    };
    return {
      ...config,
      headers: headers,
    };
  }, error => {
    return Promise.reject(error);
  });
  return targetApi;
};
const loginEpic = (action$, state$) => action$.pipe(
  ofType(AuthActionTypes.login),
  mergeMap(action => {
    const email = action.payload.email;
    const password = action.payload.password;
    return from(AuthApi.login(email, password))
      .mergeMap(json => {
        const user = json.user;
        const loginedActions = state$.value.auth.loginedActions;
        const successActions = [
          AuthActions.removeAllLoginedActions(),
          ...loginedActions,
          AuthActions.loginSuccess(user.id, user.name, user.last_login_date),
        ];
        return from(successActions);
      }).pipe(catchError(error => {
        return of$(AuthActions.loginFail(error));
      }));
  }));

@malixsys
Copy link
Contributor

malixsys commented Feb 3, 2019

Seems complicated when something simple would do, along the lines of :


export const withAuthSync = WrappedComponent => class extends Component {
  componentDidMount() {
    window.addEventListener('storage', this.syncLogout);
  }

  componentWillUnmount() {
    window.removeEventListener('storage', this.syncLogout);
    window.localStorage.removeItem('logout');
  }

    syncLogout = (event) => {
      if (event.key === 'logout') {
        console.log('logged out from storage!');
        window.location.reload();
      }
    };

    render() {
      return <WrappedComponent {...this.props} />;
    }
};

@timneutkens
Copy link
Member

This example was merged as part of Next.js 8
https://github.com/zeit/next.js/tree/canary/examples/with-cookie-auth

@pkyeck
Copy link

pkyeck commented Mar 21, 2019

@timneutkens thanks for the link.

looking at https://github.com/zeit/next.js/blob/canary/examples/with-cookie-auth/www/utils/auth.js#L26-L34 ... shouldn't there be some kind of check after auth() was called?

Testing the example without a cookie leads to Profile.getInitialProps() being called, whereas I thought the redirect would happen before even trying to get more "initial props" ...

ijjk pushed a commit to ijjk/next.js that referenced this issue Apr 13, 2019
fix several typos mostly relating to subject verb agreement in how-to-use-cloudflare.js
@joshleblanc
Copy link

I made an example here that has server-side pre-rendering + authentication w/ apollo

https://github.com/HorizonShadow/apollo-next-now-test

@ux-engineer
Copy link

Please keep in mind that OWASP security guidelines recommends against storing JWT token in Local Storage, i.e. "A single Cross Site Scripting can be used to steal all the data in these objects, so again it's recommended not to store sensitive information in local storage."

Here's Auth0: Where to Store Tokens and Tom Abbott: Where to Store your JWTs – Cookies vs HTML5 Web Storage.

Here is an example with Nuxt.js + Express.js proxy server + Django backend. Where Express server is used for proxying auth request to the actual backend and is handling CSRF protection when using JWT token storing in a cookie (imposes some restriction on the token's length / how much info can be stored in the JWT token): https://github.com/danjac/nuxt-python-secure-example

@rahul3103
Copy link

@timneutkens I need some docs on how to send token from cookie 🍪 to SSR custom redux middleware. I am getting the cookies inside _app.js . But how should I pass it to customApimiddleware. Where I have written fetch requests. Thanks

@onderonur
Copy link

onderonur commented Mar 8, 2020

I wrote a Medium article on Next.js Authentication/User accounts.
It's an extensive tutorial and my braindump from almost two years of spare-time development and thinking (my first comment on this issue is from February 2017).

https://medium.com/the-ideal-system/user-accounts-with-next-js-an-extensive-tutorial-6831cdaed16b

I think this is one of the best tutorials for handling authentication in a nextj.js app. I've seen things like storing tokens to localStorage (XSS), storing tokens in cookies (without handling CSRF), and even storing tokens in cookies from browser (both XSS and CSRF vulnerable).

I really like your solution with the reverse proxy and sharing the session info between different services. I really would like to not create a custom server for next.js app, but I think it is the most straightforward way to both handling sessions and prevent csrf (and maybe adding the reverse proxy). I may even end up creating a monolith project (both for rendering the app and handling db operations etc).

I've seen that some people (including ZEIT) keep the APIs stateless and let the next.js app to handle the session. It passes the tokens to APIs. But going with sessions only makes things a little bit more tight and less complicated.

It would be a really better thing to have a full authentication example for next.js. With things like authentication for external APIs, keeping the session in next.js app, sharing session between services or passing tokens to them, and maybe even refreshing the tokens if they are expired. (A lot of people write a lot about JWTs and just use them in their tutorials, but they mostly don't even expire them or not even refresh them.)

Anyway, you've written one of the most complete tutorials on this subject. So, thanks!

I really hope there will be much more complete and clear examples and documentation.

@7iomka
Copy link
Contributor

7iomka commented May 5, 2020

It would be a really better thing to have a full authentication example for next.js. With things like authentication for external APIs, keeping the session in next.js app, sharing session between services or passing tokens to them, and maybe even refreshing the tokens if they are expired. (A lot of people write a lot about JWTs and just use them in their tutorials, but they mostly don't even expire them or not even refresh them.)

I, too, am at a loss as to which approach to choose.
Thank you very much for the link to the article.
Currently, which of the implementations have you settled on?
Have you found a comprehensive authorization example for next v9.3+?

@james-ff
Copy link

james-ff commented May 5, 2020

Worth checking out Auth0's new cookie based approach
(Of course this is for a particular identity provider, but approach could be useful to see)
https://github.com/auth0/nextjs-auth0

  • Really cool that you can "proxy" api requests via nextjs's api routes (even via one dynamic route)
  • Then you never have to expose access tokens etc to the client side (as nextjs api routes only execute server side), server side can obtain access tokens etc through auth0 library and cookie with a secret
  • Your client side code would call your nextjs api routes, and the api routes would then perform the real api request

Bear in mind they say this approach is "experimental" in the ReadMe

@onderonur
Copy link

Worth checking out Auth0's new cookie based approach
(Of course this is for a particular identity provider, but approach could be useful to see)
https://github.com/auth0/nextjs-auth0

  • Really cool that you can "proxy" api requests via nextjs's api routes (even via one dynamic route)
  • Then you never have to expose access tokens etc to the client side (as nextjs api routes only execute server side), server side can obtain access tokens etc through auth0 library and cookie with a secret
  • Your client side code would call your nextjs api routes, and the api routes would then perform the real api request

Bear in mind they say this approach is "experimental" in the ReadMe

This article is very helpful and it covers a lot of different architecures.
https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/

Using API routes as proxy, login/logout via API routes, getting the token from API, setting it as HttpOnly cookie is a solid approach I think.
One concern might be CSRF, but you can easily create some solution with csrf npm package (not csurf, but that might work too).

@7iomka
Copy link
Contributor

7iomka commented May 6, 2020

@onderonur , thanks for auth0 article.
that is, at present there is a reliable or at least minimal example of implementation on pure jwt with next.js?
I don’t want to make an advanced layer with cookies and setting them up. In the csr application, we simply stored the token in localstorage and sent it along with the request.

@onderonur
Copy link

onderonur commented May 6, 2020

@onderonur , thanks for auth0 article.
that is, at present there is a reliable or at least minimal example of implementation on pure jwt with next.js?
I don’t want to make an advanced layer with cookies and setting them up. In the csr application, we simply stored the token in localstorage and sent it along with the request.

I've used this method for one of my repos but it's still in draft, so make sure to test them yourself :)
https://github.com/onderonur/post-gallery
Actually the "cookie layer" is not an advanced thing. Just call your API's login endpoint through /api/login API route and if the request is successful, set the token in a httpOnly cookie.
You can check my repo for the exact same implementation.

One other option is (if you want to have nearly the same flow as setting the token in local storage), you can use js-cookie npm package, call your login endpoint with a client-side request, end if it returns a token, set it in a cookie. And when you make a request (through an axios interceptor etc) read the cookie value and pass it to your API as a request header. I've seen a lot of (and even some popular) applications using this approach. But this is a little bit non-secure. Because you can't set httpOnly cookies in browser. So, JavaScript will be able to read your token cookie. Thus, there will be XSS vulnerability.

@iaincollins
Copy link
Contributor

iaincollins commented May 7, 2020

Appreciate this is an old thread (and a long running topic in general) but for those looking for additional references or examples, we've picked up work on NextAuth.js v2 recently. I mention it not so much as a plug – it's an open source project and a bunch of folks have helped out on it – but it's super simple to use and the code and approach might be useful as a reference to folks.

For some background, like NextAuth v1, it uses cookies that are signed, prefixed and HTTP only, avoiding common security pitfalls of using client side tokens.

NextAuth.js v2 supports signing in with Apple, Google, Facebook, Twitter, GitHub, Auth0, Okta, Slack, Discord and other OAuth providers (it supports both 1.x and 2.x). You can use it with MySQL, MariaDB, Postgres, MongoDB - or no database all (just OAuth and JSON Web Tokens for a 100% serverless solution).

Usage is very simple, there is a Universal static method called session() and a React Hook called useSession() you can use in components client side:

import { useSession } from 'next-auth/client'

export default () => {
  const [session, loading] = useSession()

  return <>
    {!loading && session && <p>Signed in as {session.user.name || session.user.email}.</p>}
    {!loading && !session && <p><a href="/api/auth/signin">Sign in here</a></p>}
  </>
}

It is built for Next.js 9.x and Serverless, and doesn't have dependancies like Express or PassportJS. It includes an auth provider you can use in _app.js to automatically add authentication state to all pages; it works for both client and server side rendering.

For more info, see the next-auth.js.org or check out the next-auth@beta on NPM

It's still a work in progress - we are still polishing up the documentation and event model - with a target release date of early mid June.

@SoorajChandran
Copy link

Appreciate this is an old thread (and a long running topic in general) but for those looking for additional references or examples, we've picked up work on NextAuth.js v2 recently. I mention it not so much as a plug – it's an open source project and a bunch of folks have helped out on it – but it's super simple to use and the code and approach might be useful as a reference to folks.

For some background, like NextAuth v1, it uses cookies that are signed, prefixed and HTTP only, avoiding common security pitfalls of using client side tokens.

NextAuth.js v2 supports signing in with Apple, Google, Facebook, Twitter, GitHub, Auth0, Okta, Slack, Discord and other OAuth providers (it supports both 1.x and 2.x). You can use it with MySQL, MariaDB, Postgres, MongoDB - or no database all (just OAuth and JSON Web Tokens for a 100% serverless solution).

Usage is very simple, there is a Universal static method called session() and a React Hook called useSession() you can use in components client side:

import { useSession } from 'next-auth/client'

export default () => {
  const [session, loading] = useSession()

  return <>
    {!loading && session && <p>Signed in as {session.user.name || session.user.email}.</p>}
    {!loading && !session && <p><a href="/api/auth/signin">Sign in here</a></p>}
  </>
}

It is built for Next.js 9.x and Serverless, and doesn't have dependancies like Express or PassportJS. It includes an auth provider you can use in _app.js to automatically add authentication state to all pages; it works for both client and server side rendering.

For more info, see the next-auth.js.org or check out the next-auth@beta on NPM

It's still a work in progress - we are still polishing up the documentation and event model - with a target release date of early mid June.

Great work this!
Can this be used on the client side alone? For example, I have a rails API app - and use next JS for the client side.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
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 a pull request may close this issue.