#How to use Passport authentication for GraphQL Subscription over websockets?

9 messages · Page 1 of 1 (latest)

uneven chasm
#

I have implemented Passport authentication using a JwtStrategy as described in https://docs.nestjs.com/recipes/passport, which worked great for my HTTP-based GraphQL queries and mutations. Now I have added subscriptions over websockets, and would like to extend my authentication to cover these as well. Without updating my JwtStrategy or JwtAuthGuard, I get the following error TypeError: Cannot read properties of undefined (reading 'logIn'). Does anyone have any working examples for applying Passport-based authentication to websockets/subscriptions? I am using cookie-based auth, and Fastify & Mercurius.

tawny zealot
# uneven chasm I have implemented Passport authentication using a JwtStrategy as described in h...

Yeah, this is a common gotcha.
Passport is http-only, so it won’t work out of the box for graphql subscriptions over ws. That error happens because there’s no request and session object like in http.
For subscriptions you usually don’t use AuthGuard('jwt') and extract the JWT manually from connectionParams(WS headers) or manually parsed cookies. Also verify the token yourself and attach the user to the graphql context.
With Mercurius it’s usually done in subscription.context or onConnect.

In shortly, Passport guards != ws and for subscriptions, manually validate jwt and set context.user.

distant harness
#

@uneven chasm JwtAuthGuard must NOT be used for subscriptions.
Extract JWT from cookies during WebSocket connection.
Manually verify JWT
Attach user to GraphQL context
Access context.user in resolvers
Do NOT use Passport guards for subscriptions

tawny zealot
#

Yep @uneven chasm , Metafox summed it up well. passport guards are HTTP only, so they’ll always break on WS/subscriptions.
For subs, grab the jwt from cookies or connectionParams on connect, verify it yourself, stick the user on context, and just read context.user in resolvers. once you do that, the logIn error disappears

uneven chasm
#

Thanks @tawny zealot and @distant harness . I actually did manage to get this working with Passport and a fairly simple change. Feels a bit hacky, and I don't know if this bad idea or not, but this eliminates duplication of JWT validation logic and I still have access to the user in the context (i.e @CurrentUser).

In my GraphQLModule config, I added the following subscription.context:

context: (_, req) => {
  return req
},

In my extended AuthGuard, I override getRequest:

getRequest(context: ExecutionContext) {
  if (context.getType<GqlContextType>() === 'graphql') {
    const ctx = GqlExecutionContext.create(context).getContext()
    // Determine if it's a WebSocket or HTTP request
    if (ctx.ws) {
      return ctx
    } else {
      return ctx.req
    }
  }
  return super.getRequest(context)
}
#

What's happening is Passport validates the token in the cookie during the upgrade request, which is a typical HTTP request, and then only allows it if it's valid.

#

Interested to hear what you think on this, whether it's a viable solution, or has any potential pitfalls I'm not aware of.

tawny zealot
#

Nice find @uneven chasm, that’s actually a clever workaround.
It can work, yeah. you’re basically authenticating once on the htttp upgrade, then reusing that context for WS. As long as you’re aware of a couple things that auth only happens on connect(token won’t re-validate later) and reconnects matter about expired token won’t be caught mid connection and also relies a bit on internal behavior, so future Nest/Mercurius changes could break it
If you’re fine with those tradeoffs, it’s a totally reasonable approach imq

uneven chasm
#

Thanks @tawny zealot I appreciate the help and feedback. Indeed, figuring out how to timeout the connection is the next thing. May try setting and storing a timeout on the context object that forces a disconnect, causing client to re-establish connection with a refreshed token. Will need to explore this a bit. And figured there would be a risk of breaking change, but assume I can tweak it if necessary in the future.