I am using Nestjs API with @nestjs/graphql
.
On a Next.js front I am trying to subscribe to an authenticated GraphQL subscription, it works with WebSocketLink
but not GraphQLWsLink
.
My resolver is
@Subscription(() => Post)
@Roles(UserRole.USER)
@UseGuards(JwtAuthGuard, RolesGuard)
postCreated() {
return this.pubSub.asyncIterator('postCreated')
}
My GraphQL config is
@Injectable()
export class GqlConfigService implements GqlOptionsFactory {
constructor(private configService: ConfigService) {}
createGqlOptions(): ApolloDriverConfig {
const graphqlConfig = this.configService.get<GraphqlConfig>('graphql')
return {
// schema options
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 }),
// Error
formatError: (error) => {
return {
message: error.message,
code: error.extensions?.code || 'INTERNAL_SERVER_ERROR',
}
},
}
}
}
My 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
}
}
And my Apollo client setup
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
It works with
import { WebSocketLink } from '@apollo/client/link/ws'
const wsLinkOld = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
reconnect: true,
connectionParams: {
Authorization: token ? `Bearer ${token}` : '',
},
},
})
But not with
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4000/graphql',
connectionParams: () => ({
authorization: `Bearer ${token}`,
}),
}),
)
Everytime I got
TypeError: Cannot read properties of undefined (reading 'authorization')
I think that problem with "connectionParams.Authorization".
You can rewrite from:
to
and on client side from:
to