使用护照 + JWT 或会话保护 GraphQL API? (举例)

Posted

技术标签:

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

提供一些背景信息:我正在编写一个 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
    
  ),

【问题讨论】:

【参考方案1】:

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

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

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

Auth0 Okta Firebase Cognito Authress

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

【讨论】:

以上是关于使用护照 + JWT 或会话保护 GraphQL API? (举例)的主要内容,如果未能解决你的问题,请参考以下文章

不允许使用 JWT 令牌保护 GraphQL 端点

NodeJs JWT 护照

在客户端应用程序上刷新 JWT 和保护会话

护照-jwt sequelize 401 总是

Apollo express 没有快速护照会话

将护照 openID-stategy 与 JWT 相结合