将Firebase身份验证与Next.js集成

时间:2020-03-08 15:18:05

标签: javascript reactjs firebase next.js

我在理解Next.js将Firebase与serverless app结合使用的示例时遇到麻烦。

我已经成功地实现了身份验证阶段,并将Google添加为身份验证提供者。我遇到的问题是,登录后立即加载索引页面时,服务器似乎无权访问Next团队创建的共享AuthUserAuthUserInfo对象验证信息。

但是,如果刷新页面,则该信息可用。这让我感到困惑。 在下面,我分享了index.jsAuthUserAuthUserInfo组件的代码。

import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import Link from 'next/link'
import Router from 'next/router'
import withAuthUser from '../utils/pageWrappers/withAuthUser'
import withAuthUserInfo from '../utils/pageWrappers/withAuthUserInfo'
import logout from '../utils/auth/logout'

const Index = props => {
  const { AuthUserInfo, data } = props
  const AuthUser = get(AuthUserInfo, 'AuthUser', null)
  const { favoriteFood } = data
  // [OP's COMMENT]: This logs the AuthUser object correctly even immediately after the login
  console.log(AuthUser)

  return (
    <div>
      <p>Hi there!</p>
      {!AuthUser ? (
        <p>
          You are not signed in.{' '}
          <Link href={'/auth'}>
            <a>Sign in</a>
          </Link>
        </p>
      ) : (
        <div>
          <p>You're signed in. Email: {AuthUser.email}</p>
          <p
            style={{
              display: 'inlinelock',
              color: 'blue',
              textDecoration: 'underline',
              cursor: 'pointer',
            }}
            onClick={async () => {
              try {
                await logout()
                Router.push('/auth')
              } catch (e) {
                console.error(e)
              }
            }}
          >
            Log out
          </p>
        </div>
      )}
      <div>
        <Link href={'/example'}>
          <a>Another example page</a>
        </Link>
      </div>
      <div>
        <div>Your favorite food is {favoriteFood}.</div>
      </div>
    </div>
  )
}

// Just an example.
const mockFetchData = async userId => ({
  user: {
    ...(userId && {
      id: userId,
    }),
  },
  favoriteFood: 'pizza',
})

Index.getInitialProps = async ctx => {
  // Get the AuthUserInfo object. This is set in `withAuthUser.js`.
  // The AuthUserInfo object is available on both the server and client.
  const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)
  const AuthUser = get(AuthUserInfo, 'AuthUser', null)

  // You can also get the token (e.g., to authorize a request when fetching data)
  const AuthUserToken = get(AuthUserInfo, 'token', null)
  // [OP's COMMENT]: In the initial login this returns null, but after refreshing while logged in it returns the infomation correctly correctly
  console.log(AuthUser)

  // You can fetch data here.
  const data = await mockFetchData(get(AuthUser, 'id'))

  return {
    data,
  }
}

Index.displayName = 'Index'

Index.propTypes = {
  AuthUserInfo: PropTypes.shape({
    AuthUser: PropTypes.shape({
      id: PropTypes.string.isRequired,
      email: PropTypes.string.isRequired,
      emailVerified: PropTypes.bool.isRequired,
    }),
    token: PropTypes.string,
  }),
  data: PropTypes.shape({
    user: PropTypes.shape({
      id: PropTypes.string,
    }).isRequired,
    favoriteFood: PropTypes.string.isRequired,
  }).isRequired,
}

Index.defaultProps = {
  AuthUserInfo: null,
}

// Use `withAuthUser` to get the authed user server-side, which
// disables static rendering.
// Use `withAuthUserInfo` to include the authed user as a prop
// to your component.
export default withAuthUser(withAuthUserInfo(Index))

withAuthUser.js

/* eslint react/jsx-props-no-spreading: 0 */

import React from 'react'
import PropTypes from 'prop-types'
import { get, set } from 'lodash/object'
import { AuthUserInfoContext, useFirebaseAuth } from '../auth/hooks'
import { createAuthUser, createAuthUserInfo } from '../auth/user'

// Gets the authenticated user from the Firebase JS SDK, when client-side,
// or from the request object, when server-side. Add the AuthUserInfo to
// context.
export default ComposedComponent => {
  const WithAuthUserComp = props => {
    const { AuthUserInfo, ...otherProps } = props

    // We'll use the authed user from client-side auth (Firebase JS SDK)
    // when available. On the server side, we'll use the authed user from
    // the session. This allows us to server-render while also using Firebase's
    // client-side auth functionality.
    const { user: firebaseUser } = useFirebaseAuth()
    const AuthUserFromClient = createAuthUser(firebaseUser)
    const { AuthUser: AuthUserFromSession, token } = AuthUserInfo
    const AuthUser = AuthUserFromClient || AuthUserFromSession || null

    return (
      <AuthUserInfoContext.Provider value={{ AuthUser, token }}>
        <ComposedComponent {...otherProps} />
      </AuthUserInfoContext.Provider>
    )
  }

  WithAuthUserComp.getInitialProps = async ctx => {
    const { req, res } = ctx

    // Get the AuthUserInfo object.
    let AuthUserInfo
    if (typeof window === 'undefined') {
      // If server-side, get AuthUserInfo from the session in the request.
      // Don't include server middleware in the client JS bundle. See:
      // https://arunoda.me/blog/ssr-and-server-only-modules
      const { addSession } = require('../middleware/cookieSession')
      addSession(req, res)
      AuthUserInfo = createAuthUserInfo({
        firebaseUser: get(req, 'session.decodedToken', null),
        token: get(req, 'session.token', null),
      })
    } else {
      // If client-side, get AuthUserInfo from stored data. We store it
      // in _document.js. See:
      // https://github.com/zeit/next.js/issues/2252#issuecomment-353992669
      try {
        const jsonData = JSON.parse(
          window.document.getElementById('__MY_AUTH_USER_INFO').textContent
        )
        if (jsonData) {
          AuthUserInfo = jsonData
        } else {
          // Use the default (unauthed) user info if there's no data.
          AuthUserInfo = createAuthUserInfo()
        }
      } catch (e) {
        // If there's some error, use the default (unauthed) user info.
        AuthUserInfo = createAuthUserInfo()
      }
    }

    // Explicitly add the user to a custom prop in the getInitialProps
    // context for ease of use in child components.
    set(ctx, 'myCustomData.AuthUserInfo', AuthUserInfo)

    // Evaluate the composed component's getInitialProps().
    let composedInitialProps = {}
    if (ComposedComponent.getInitialProps) {
      composedInitialProps = await ComposedComponent.getInitialProps(ctx)
    }

    return {
      ...composedInitialProps,
      AuthUserInfo,
    }
  }

  WithAuthUserComp.displayName = `WithAuthUser(${ComposedComponent.displayName})`

  WithAuthUserComp.propTypes = {
    AuthUserInfo: PropTypes.shape({
      AuthUser: PropTypes.shape({
        id: PropTypes.string.isRequired,
        email: PropTypes.string.isRequired,
        emailVerified: PropTypes.bool.isRequired,
      }),
      token: PropTypes.string,
    }).isRequired,
  }

  WithAuthUserComp.defaultProps = {}

  return WithAuthUserComp
}

withAuthUserInfo.js

/* eslint react/jsx-props-no-spreading: 0 */

import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import { AuthUserInfoContext } from '../auth/hooks'

// Provides an AuthUserInfo prop to the composed component.
export default ComposedComponent => {
  const WithAuthUserInfoComp = props => {
    const { AuthUserInfo: AuthUserInfoFromSession, ...otherProps } = props
    return (
      <AuthUserInfoContext.Consumer>
        {AuthUserInfo => (
          <ComposedComponent
            {...otherProps}
            AuthUserInfo={AuthUserInfo || AuthUserInfoFromSession}
          />
        )}
      </AuthUserInfoContext.Consumer>
    )
  }

  WithAuthUserInfoComp.getInitialProps = async ctx => {
    const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)

    // Evaluate the composed component's getInitialProps().
    let composedInitialProps = {}
    if (ComposedComponent.getInitialProps) {
      composedInitialProps = await ComposedComponent.getInitialProps(ctx)
    }

    return {
      ...composedInitialProps,
      AuthUserInfo,
    }
  }

  WithAuthUserInfoComp.displayName = `WithAuthUserInfo(${ComposedComponent.displayName})`

  WithAuthUserInfoComp.propTypes = {
    AuthUserInfo: PropTypes.shape({
      AuthUser: PropTypes.shape({
        id: PropTypes.string.isRequired,
        email: PropTypes.string.isRequired,
        emailVerified: PropTypes.bool.isRequired,
      }),
      token: PropTypes.string,
    }),
  }

  WithAuthUserInfoComp.defaultProps = {}

  return WithAuthUserInfoComp
}

我很确定服务器端渲染和(可能带有诺言)发生了一些我不了解的事情。如果需要,我可以将数据提取推送到客户端,但是我想了解为什么这不起作用。登录后是否应该立即强制刷新? Next团队本身已在getInitialProps

index.js方法中添加了以下代码段
  // You can also get the token (e.g., to authorize a request when fetching data)
  const AuthUserToken = get(AuthUserInfo, 'token', null)

所以我猜它打算在登录后立即生效。

0 个答案:

没有答案