NestJS GraphQL使用graphql-ws与Apollo Client订阅认证失败问题
GraphQLWsLink订阅认证失败(WebSocketLink正常)
我在基于@nestjs/graphql的NestJS API中开发,Next.js前端订阅需要认证的GraphQL时,使用WebSocketLink可以正常工作,但切换到GraphQLWsLink时认证失败,报错:TypeError: Cannot read properties of undefined (reading 'authorization')。
相关代码片段
订阅解析器
@Subscription(() => Post) @Roles(UserRole.USER) @UseGuards(JwtAuthGuard, RolesGuard) postCreated() { return this.pubSub.asyncIterator('postCreated') }
GraphQL配置
@Injectable() export class GqlConfigService implements GqlOptionsFactory { constructor(private configService: ConfigService) {} createGqlOptions(): ApolloDriverConfig { const graphqlConfig = this.configService.get<GraphqlConfig>('graphql') return { autoSchemaFile: graphqlConfig.schemaDestination, sortSchema: graphqlConfig.sortSchema, buildSchemaOptions: { numberScalarMode: 'integer', }, subscriptions: { 'graphql-ws': { path: '/graphql', onConnect: (context: Context) => { const { connectionParams } = context return { req: { headers: { authorization: connectionParams.Authorization }, }, } }, }, 'subscriptions-transport-ws': { path: '/graphql', onConnect: (connectionParams) => { return { req: { headers: { authorization: connectionParams.Authorization }, }, } }, }, }, includeStacktraceInErrorResponses: graphqlConfig.debug, playground: graphqlConfig.playgroundEnabled, context: ({ req }) => ({ req }), formatError: (error) => { return { message: error.message, code: error.extensions?.code || 'INTERNAL_SERVER_ERROR', } }, } } }
AuthGuard
import { ExecutionContext, Injectable } from '@nestjs/common' import { AuthGuard } from '@nestjs/passport' import { GqlExecutionContext } from '@nestjs/graphql' @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { getRequest(context: ExecutionContext) { const ctx = GqlExecutionContext.create(context) return ctx.getContext().req } }
Apollo Client配置
import { getSession } from 'next-auth/react' import { createClient } from 'graphql-ws' import { ApolloClient, createHttpLink, InMemoryCache, split, } from '@apollo/client' import { setContext } from '@apollo/client/link/context' import { GraphQLWsLink } from '@apollo/client/link/subscriptions' import { getMainDefinition } from '@apollo/client/utilities' import { WebSocketLink } from '@apollo/client/link/ws' import { config } from '@/config' const token = 'A_VALID_TOKEN_GOES_HERE' const wsLinkOld = new WebSocketLink({ uri: 'ws://localhost:4000/graphql', options: { reconnect: true, connectionParams: { Authorization: token ? `Bearer ${token}` : '', }, }, }) const wsLink = new GraphQLWsLink( createClient({ url: 'ws://localhost:4000/graphql', connectionParams: () => ({ authorization: `Bearer ${token}`, }), }), ) const httpLink = createHttpLink({ uri: config.api.graphql.url.href, }) const authLink = setContext(async (_, { headers }) => { const session = await getSession() return { headers: { ...headers, authorization: session?.accessToken ? `Bearer ${session.accessToken}` : '', }, } }) const link = split( ({ query }) => { const definition = getMainDefinition(query) return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ) }, wsLink, authLink.concat(httpLink), ) const client = new ApolloClient({ link, cache: new InMemoryCache(), }) export default client
解决方案
1. 统一前后端参数键名大小写
问题根源是前端GraphQLWsLink发送的认证参数键名为小写authorization,但后端graphql-ws的onConnect方法中读取的是大写Authorization,导致无法获取到token,进而引发后续错误。
修改前端代码:将GraphQLWsLink的connectionParams键名改为大写Authorization,与WebSocketLink保持一致:
const wsLink = new GraphQLWsLink( createClient({ url: 'ws://localhost:4000/graphql', connectionParams: () => ({ Authorization: `Bearer ${token}`, // 改为大写A }), }), )
或者修改后端代码:将graphql-ws的onConnect方法中读取的键名改为小写authorization:
'onConnect: (context: Context) => { const { connectionParams } = context return { req: { headers: { authorization: connectionParams.authorization }, // 改为小写a }, } },'
2. 完善上下文配置(可选)
确保WebSocket订阅的上下文能正确合并,修改GraphQL配置中的context函数,兼容HTTP请求和WebSocket连接:
context: ({ req, connection }) => { // 处理WebSocket连接,返回onConnect设置的上下文 if (connection) { return connection.context; } // 处理HTTP请求 return { req }; },
这样可以保证订阅请求的上下文能正确获取到req对象,避免因上下文缺失导致的错误。
内容的提问来源于stack exchange,提问作者Phoax
相关产品推荐
相关产品推荐

