You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:27:02