【问题标题】:Secure a GraphQL API with passport + JWT's or sessions? (with example)使用护照 + JWT 或会话保护 GraphQL API? (举例)
【发布时间】:2026-02-07 07:00:01
【问题描述】:

提供一些背景信息:我正在编写一个 API 来为 React 中的内部 CMS 提供服务,该 CMS 需要 Google 登录和一个应该支持 SMS、电子邮件和 Apple 登录的 React Native 应用程序,我不知道应该采用哪种身份验证方式是最好的,我目前在下面有一个示例身份验证流程,其中团队成员使用 Google 登录,刷新令牌在 httpOnly cookie 中发送并存储在客户端的变量中,然后可以将令牌交换为 accessToken, cookie 中的刷新令牌也有一个 tokenVersion,它在发送 accessToken 之前会被检查,这确实会给数据库增加一些额外的负载,但如果有人的帐户被盗,则可以增加,在允许任何 GraphQL 查询/突变之前,用户的令牌是解码并添加到 GraphQL 上下文中,以便我可以使用 graphql-shield 检查角色并在需要时访问用户以在我的查询/突变中进行数据库操作

因为我仍然在访问数据库,即使它在页面/应用程序加载中只有一次我想知道这是否是一种好方法,或者我是否会更好地使用会话来代替

// index.ts
import "./passport"

const main = () => {
  const server = fastify({ logger })
  const prisma = new PrismaClient()

  const apolloServer = new ApolloServer({
    schema: applyMiddleware(schema, permissions),
    context: (request: Omit<Context, "prisma">) => ({ ...request, prisma }),
    tracing: __DEV__,
  })

  server.register(fastifyCookie)
  server.register(apolloServer.createHandler())
  server.register(fastifyPassport.initialize())

  server.get(
    "/auth/google",
    {
      preValidation: fastifyPassport.authenticate("google", {
        scope: ["profile", "email"],
        session: false,
      }),
    },
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    async () => {}
  )

  server.get(
    "/auth/google/callback",
    {
      preValidation: fastifyPassport.authorize("google", { session: false }),
    },
    async (request, reply) => {
      // Store user in database
      // const user = existingOrCreatedUser
      // sendRefreshToken(user, reply) < send httpOnly cookie to client
      // const accessToken = createAccessToken(user)
      // reply.send({ accessToken, user }) < send accessToken
    }
  )
      
  server.get("/refresh_token", async (request, reply) => {
    const token = request.cookies.fid

    if (!token) {
      return reply.send({ accessToken: "" })
    }

    let payload

    try {
      payload = verify(token, secret)
    } catch {
      return reply.send({ accessToken: "" })
    }

    const user = await prisma.user.findUnique({
      where: { id: payload.userId },
    })

    if (!user) {
      return reply.send({ accessToken: "" })
    }

    // Check live tokenVersion against user's one in case it was incremented
    if (user.tokenVersion !== payload.tokenVersion) {
      return reply.send({ accessToken: "" })
    }

    sendRefreshToken(user, reply)

    return reply.send({ accessToken: createAccessToken(user) })
  })

  server.listen(port)
}
// passport.ts
import fastifyPassport from "fastify-passport"
import { OAuth2Strategy } from "passport-google-oauth"

fastifyPassport.registerUserSerializer(async (user) => user)
fastifyPassport.registerUserDeserializer(async (user) => user)

fastifyPassport.use(
  new OAuth2Strategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: "http://localhost:4000/auth/google/callback",
    },
    (_accessToken, _refreshToken, profile, done) => done(undefined, profile)
  )
)
// permissions/index.ts
import { shield } from "graphql-shield"
import { rules } from "./rules"

export const permissions = shield({
  Mutation: {
    createOneShopLocation: rules.isAuthenticatedUser,
  },
})
// permissions/rules.ts
import { rule } from "graphql-shield"
import { Context } from "../context"

export const rules = {
  isAuthenticatedUser: rule()(async (_parent, _args, ctx: Context) => {
    const authorization = ctx.request.headers.authorization

    if (!authorization) {
      return false
    }

    try {
      const token = authorization.replace("Bearer", "")
      const payload = verify(token, secret)

      // mutative
      ctx.payload = payload

      return true
    } catch {
      return false
    }
  }),
}

【问题讨论】:

    标签: node.js authentication session graphql jwt


    【解决方案1】:

    要直接回答您的问题,您希望使用 jwts 进行访问,仅此而已。这些 jwt 应该与用户会话相关联,但您不想管理它们。您希望用户身份聚合器来做这件事。

    您最好删除大部分代码来处理用户登录/刷新并使用用户身份聚合器。在处理用户身份验证流程时,您会遇到复杂的常见问题,这就是存在这些问题的原因。

    最常见的是 Auth0,但价格和复杂性可能与您的期望不符。我建议仔细阅读列表并选择最能支持您的用例的一个:

    或者您可以查看this article,它提出了一系列不同的替代方案以及他们关注的重点

    【讨论】: