es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cómo implementar un guardia de autenticación para suscripciones de GraphQL (passportjs + cookies)

Como puedo pasar al usuario a la solicitud? ¿Existe alguna manera posible de implementar algo como SubscriptionAuthGuard?

Sin la suscripción, todo funciona bien.

Código:

GraphQLModule.forRoot({
      installSubscriptionHandlers: true,
      subscriptions: {
        'subscriptions-transport-ws': {
          onConnect: (connectionParams, webSocket) =>
            new Promise((resolve) => {
              passportInit(webSocket.upgradeReq, {} as any, () => {
                resolve(webSocket.upgradeReq);
              });
            }),
        },
      },
      context: ({ req }) => ({ req }),
    }),

Error:

TypeError: No se puede establecer la propiedad 'authInfo' como indefinida
Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Para pasar el usuario a la solicitud, puedes usar el middleware passport.authenticate y pasar la opción session como false para evitar crear una sesión. Luego, el objeto user se agregará a req.user o ctx.req.user dependiendo de tu implementación. Aquí hay un ejemplo:

    import { createParamDecorator, ExecutionContext } from '@nestjs/common';
    import { GqlExecutionContext } from '@nestjs/graphql';
    import { PassportStatic } from 'passport';
    
    export const AuthUser = createParamDecorator((data: unknown, context: ExecutionContext) => {
      const gqlCtx = GqlExecutionContext.create(context);
      const ctx = gqlCtx.getContext();
      // Verifica si passport ha sido inicializado con tu estrategia.
      const passport = ctx.req._passport;
      if (!passport) {
        throw new Error('Passport no ha sido inicializado');
      }
      // Autentica al usuario y agrega el usuario a la solicitud.
      passport.authenticate('jwt', { session: false }, (err, user) => {
        if (err || !user) {
          throw new Error('No autorizado');
        }
        ctx.req.user = user;
      })(ctx.req, ctx.res, ctx.next);
      return ctx.req.user;
    });
    

    Puedes usar el decorador AuthUser en un resolutor o en una parte de tu servicio que necesite el objeto user. Por ejemplo:

    import { Resolver, Query, Context } from '@nestjs/graphql';
    import { AuthUser } from './decorators/auth-user.decorator';
    
    @Resolver()
    export class MyResolver {
      @Query(() => String)
      myQuery(@AuthUser() user, @Context() ctx) {
        // Accede al objeto user y/o al contexto.
        console.log(user, ctx);
        return '¡Hola Mundo!';
      }
    }
    

    Respecto al SubscriptionAuthGuard, es posible crear una implementación similar para las suscripciones. Puedes usar la función onConnect de la biblioteca subscriptions-transport-ws para autenticar al usuario y agregar el objeto user al contexto de la conexión websocket. Aquí hay un ejemplo:

    import { ExecutionParams } from 'subscriptions-transport-ws';
    import { PassportStatic } from 'passport';
    
    // Define una función que ejecuta el proceso de autenticación y agrega el usuario al contexto.
    export const authenticateSubscription = async (params: ExecutionParams, passport: PassportStatic) => {
      const token = params?.context?.authorization;
      if (!token) {
        throw new Error('No autorizado');
      }
      const user = await new Promise((resolve, reject) => {
        passport.authenticate('jwt', { session: false }, (err, user) => {
          if (err || !user) {
            reject(err || new Error('No autorizado'));
          }
          resolve(user);
        })({ headers: { authorization: token } }, undefined, undefined);
      });
      return { user };
    };
    
    // Define un guardia que verifica si el usuario ha sido agregado al contexto de la suscripción.
    export const SubscriptionAuthGuard = async (resolve, parent, args, context, info) => {
      if (!context.user) {
        throw new Error('No autorizado');
      }
      return resolve(parent, args, context, info);
    };
    
    // En tu módulo App, pasa la función de autenticación a la función onConnect.
    GraphQLModule.forRoot({
      installSubscriptionHandlers: true,
      subscriptions: {
        'subscriptions-transport-ws': {
          onConnect: async (connectionParams, webSocket) => {
            // Pasa tu instancia de passport a la función authenticateSubscription.
            const { user } = await authenticateSubscription(connectionParams, webSocket.upgradeReq._passport);
            return { user };
          },
        },
      },
      context: ({ req, connection }) => {
        // Agrega el objeto user al objeto context para GraphQL y suscripciones.
        return connection ? connection.context : { req, user: req.user };
      },
    });
    

    Puedes usar el middleware del resolutor SubscriptionAuthGuard para verificar si el usuario ha sido agregado al contexto de la suscripción. También puedes usar el decorador AuthUser en tus resolutores de suscripción para acceder al objeto user.

Comments are closed.