Next-auth: Example with getSession() in _app.js?

Created on 27 Jun 2020  路  10Comments  路  Source: nextauthjs/next-auth

Your question

All the examples I've seen for getSession() is for a specific page, but I'd like to pass the session prop at the layout level, via _app.js.

What is the proper way of using it in _app.js for both client/server side application?

What are you trying to do

Apply the session prop to all page props without duplicating logic by setting it through _app.js.

// _app.tsx
// some psuedo-code
import { Provider } from 'react-redux'
import { getSession, Provider as AuthProvider } from 'next-auth/client'

const WrappedApp: FC<CustomProps> & StaticComponents = (props) => {
  const { Component, pageProps, session } = props

  return (
    <Provider store={store}>
      <AuthProvider session={session} options={{ site: 'http://localhost:3000' }}>
            <HeaderNav {...props} />
            <Component {...pageProps} />
      </AuthProvider>
    </Provider>
  )
}

// https://github.com/isaachinman/next-i18next/issues/615
WrappedApp.getInitialProps = async (context) => {
  const appProps = await App.getInitialProps(context)
  const session = await getSession(context)

  // ----------> returns null
  console.log(session)

  return {
    ...appProps,
    session
  }
}

export default WrappedApp

With the example, I also get despite having the proper site value:

[next-auth][error][CLIENT_FETCH_ERROR] [
  '/api/auth/session',
  TypeError: Only absolute URLs are supported

Documentation feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.

  • [ ] Found the documentation helpful
  • [ ] Found documentation but was incomplete
  • [ ] Could not find relevant documentation
  • [ ] Found the example project helpful
  • [x] Did not find the example project helpful
question

Most helpful comment

I'm happy to leave this issue open until we have a more satisfying answer to your question. :-)

All 10 comments

I agree, I think this a thing it would be helpful to have a tutorial for. This is tricky as the Provider isn't initialised yet so doesn't help if you want to call getSession() from getInitialProps() in _app.js.

There is an undocumented work around, where you can export and call setOptions() directly from next-auth/client, as in the example below, which should solve this problem.

import { Provider } from 'react-redux'
import { setOptions, getSession, Provider as AuthProvider } from 'next-auth/client'
setOptions({ site: 'http://localhost:3000 })

const WrappedApp: FC<CustomProps> & StaticComponents = (props) => {
  const { Component, pageProps, session } = props

  return (
    <Provider store={store}>
      <AuthProvider session={session} options={{ site: 'http://localhost:3000' }}>
            <HeaderNav {...props} />
            <Component {...pageProps} />
      </AuthProvider>
    </Provider>
  )
}

WrappedApp.getInitialProps = async (context) => {
  const appProps = await App.getInitialProps(context)
  const session = await getSession(context)

  return {
    ...appProps,
    session
  }
}

export default WrappedApp

Note: You can pass a regular environment variable (e.g. process.env.SITE) and pass it as the value for the site option.

Thanks for the response. I no longer get the fetch error, but the session value still comes out as null. I've verified that I do have an active session as useSession() will work on the client side.

Another question - how do folks generally implement getSession() in a reusable way? Is calling the method on every page the thing to do in next when you have common init/fetch logic across pages?

@iaincollins I think I see where the problem is, and I believe it's a bug in the client code under this particular situation.

I put some log statements in dist/client/index.js:

var getSession = function () {
  var _ref = _asyncToGenerator(function* () {
    var {
      req
    } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

    var baseUrl = _baseUrl();

    var options = req ? {
      headers: {
        cookie: req.headers.cookie
      }
    } : {};

    console.log(arguments)
    console.log('req', req)
    console.log('opts', options)
    console.log(baseUrl, "/session")

The output looks like this:

[Arguments] {
  '0': {
    AppTree: [Function: AppTree],
    Component: [Function: IndexPage] { getNavbarContent: [Function] },
    router: ServerRouter {
      route: '/',
      pathname: '/',
      query: {},
      asPath: '/',
      basePath: '',
      events: undefined,
      isFallback: false
    },
    ctx: {
      err: undefined,
      req: [IncomingMessage],
      res: [ServerResponse],
      pathname: '/',
      query: {},
      asPath: '/',
      AppTree: [Function: AppTree]
    }
  }
}
req undefined
opts {}
http://localhost:3000/api/auth /session

req is undefined because it's looking for it directly under arguments[0] when it's actually under arguments[0].ctx.req in this case.

If I do this, the flow works, but it feels quite hacky:

WrappedApp.getInitialProps = async (context) => {
  context['req'] = context.ctx.req

Not sure what the appropriate fix would be for getSession() since I'm pretty new to next.

Another question - how do folks generally implement getSession() in a reusable way? Is calling the method on every page the thing to do in next when you have common init/fetch logic across pages?

The best approach is to use the useSession() hook, as it will share the session object between components (and even between page navigations) and avoids constantly calling the server.

If you need server side rendering then yes, you need to call getSession() on every page view.

You can do that by creating an 'AuthPage' class and extending it (or a HOC and using that) to apply it only to pages that require authentication, or add it to _app.js as you've done here, if all pages on your site require authentication with server side rendering.

We plan to do some tutorial for folks in future to help with this.

Thanks for you PR will leave a note there!

I went with this route because useSession would have this brief flash where the page renders in a logout state then immediately flashes the layout to the login one, which was pretty jarring.

The example project doesn't have that problem and was created to show how to avoid that, including on sites that also need to support server side rendering on some pages.

If you go this route, do aware your website will be slower to access as a result and Next.js will generate a warning if you do this because it prevents automatic static optimization of your site.

The performance impact isn't usually noticeable running locally as everything runs very quickly, but can be significant when the site is deployed to Vercel / AWS Lambda.

Related: I've added some examples of alternative approaches to securing routes in #347 that contain approaches that are somewhat relevant.

Thank you so much for your time and providing guidance. I'll check out your updated examples.

I'm happy to leave this issue open until we have a more satisfying answer to your question. :-)

I know this issue is closed, but would it be possible to pass crsfToken on _app too? (i'm asking because I have a few pages where I check if a user is logged in or not after a certain action, e.g. click on a button).

For this I'm fetching the session and csrfToken on pageload or on the server. It would be nice if we could pass those down, so we don't need to check on those pages.. (this means I'd be able to have those pages be SSG instead of SSR).

As a side note, I'm fetching those since after the click. I'm showing a login modal, where I need to pass the csrf for email login.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

simonbbyrne picture simonbbyrne  路  3Comments

loonskai picture loonskai  路  3Comments

benoror picture benoror  路  3Comments

eatrocks picture eatrocks  路  3Comments

dmi3y picture dmi3y  路  3Comments