使用护照 + 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? (举例)的主要内容,如果未能解决你的问题,请参考以下文章