首页 > 解决方案 > 使用firebase auth时如何自动刷新idToken?

问题描述

我在我的 Next.js 应用程序中使用 firebase 进行身份验证,并且我有一个提供 REST API 的快速服务器,该服务器具有一个中间件,用于firebase-admin验证idToken从我的应用程序发送的信息,以传递经过身份验证的路由

目前

firebase 生成的 idToken 持续一小时,如果客户端仍在我的应用程序上并命中任何需要的路由,idToken并且如果idToken已过期,则服务器会抛出未经身份验证的错误,这是非常好的工作,但这不是我们想要的,我知道我的用户在那里,只是idToken过期了

问题

如果用户已过期,如何刷新我idToken的用户,而无需在浏览器中进行完全刷新以获取新用户idToken

一些代码

AuthContext.tsx

/* eslint-disable no-unused-vars */
import { useRouter } from 'next/router'
import nookies from 'nookies'
import { createContext, useContext, useEffect, useState } from 'react'
import { axios } from '../config/axios'
import firebase from '../config/firebase'
import { AuthUser } from '../types'
import { BaseUser } from '../types/user'
import { getProvider } from '../utils/oAuthProviders'

type AuthContextType = {
  user: AuthUser | null
  login: (email: string, password: string) => Promise<any>
  signup: (email: string, password: string) => Promise<any>
  logout: () => Promise<any>
  oAuthLogin: (provider: string) => Promise<any>
}

const AuthContext = createContext<AuthContextType>({} as AuthContextType)
export const useAuth = () => useContext(AuthContext)

const fromPaths = ['/login', '/signup']

const formatUser = (user: BaseUser, idToken: string): AuthUser => {
  return {
    ...user,
    idToken,
  }
}

export const AuthContextProvider = ({ children }: { children: React.ReactNode }) => {
  const [user, setUser] = useState<AuthUser | null>(null)
  const [loading, setLoading] = useState(true)
  const router = useRouter()
  console.log(user)

  useEffect(() => {
    const unsub = firebase.auth().onIdTokenChanged((user) => {
      if (user) {
        user
          .getIdToken()
          .then(async (idToken) => {
            try {
              const userResp = await axios.get('/user/me', {
                headers: {
                  Authorization: `Bearer ${idToken}`,
                },
              })
              nookies.set(undefined, 'idk', idToken, { path: '/' })
              const {
                data: { userFullDetials },
              } = userResp
              setUser(formatUser(userFullDetials, idToken))
              setLoading(false)
              if (fromPaths.includes(router.pathname)) {
                router.push('/home')
              }
            } catch (err) {
              console.log(err)
              setUser(null)
              setLoading(false)
            }
          })
          .catch((err) => {
            console.log(err.message)
            setUser(null)
            setLoading(false)
          })
      } else {
        setLoading(false)
        setUser(null)
      }
    })
    return () => unsub()
  }, [router])

  const login = (email: string, password: string) => {
    return firebase.auth().signInWithEmailAndPassword(email, password)
  }

  const signup = (email: string, password: string) => {
    return firebase.auth().createUserWithEmailAndPassword(email, password)
  }

  const oAuthLogin = (provider: string) => {
    return firebase.auth().signInWithPopup(getProvider(provider))
  }

  const logout = async () => {
    setUser(null)
    await firebase.auth().signOut()
  }

  const returnObj = {
    user,
    login,
    signup,
    logout,
    oAuthLogin,
  }

  return (
    <AuthContext.Provider value={returnObj}>
      {loading ? (
        <div className="flex items-center justify-center w-full h-screen bg-gray-100">
          <h1 className="text-indigo-600 text-8xl">S2Media</h1>
        </div>
      ) : (
        children
      )}
    </AuthContext.Provider>
  )
}

// auth.ts
// Auth Middleware in express

import { NextFunction, Request, Response } from 'express'
import fbadmin from 'firebase-admin'
import { DecodedIdToken } from '../types/index'

export default async (req: Request, res: Response, next: NextFunction) => {
  const authorization = req.header('Authorization')
  if (!authorization || !authorization.startsWith('Bearer')) {
    return res.status(401).json({
      status: 401,
      message: 'authorization denied',
    })
  }

  const idToken = authorization.split(' ')[1]
  if (!idToken) {
    return res.status(401).json({
      status: 401,
      message: 'authorization denied',
    })
  }

  try {
    const decodedToken = await fbadmin.auth().verifyIdToken(idToken)
    req.user = decodedToken as DecodedIdToken
    return next()
  } catch (err) {
    console.log(err.message)
    return res.status(401).json({
      status: 401,
      message: 'authorization denied',
    })
  }
}


标签: javascriptreactjsfirebasefirebase-authentication

解决方案


Firebase SDK 会为您做到这一点。每当您调用user.getIdToken()它时,它肯定会返回一个有效的令牌。如果现有令牌已过期,它将刷新并返回一个新令牌。您可以使用onIdTokenChanged()and which 将在刷新令牌时触发并将其存储在您的状态中。

getIdToken()但是,每当您向服务器发出 API 请求时,我看不到使用方法的任何缺点。您不必与 IdToken 观察者打交道并始终获得有效令牌。

const makeAPIRequest = async () => {
  // get token before making API request
  const token = await user.getIdToken()

  // pass the token in request headers
}

现在,您的代码会在令牌刷新时向服务器发出请求以获取用户信息,这可能是多余的。


推荐阅读