Graphql: Authorization middleware do not work with @Subscription()

Created on 3 Oct 2018  路  10Comments  路  Source: nestjs/graphql

I'm submitting a...


[ ] Regression 
[ ] Bug report
[ x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior


@UseGuards(), @UseInterceptors and middleware do not work with @Subscription().

Example:

@Injectable()
export class CatsGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    console.log('Guard');
    const ctx = GqlExecutionContext.create(context);
    return true;
  }
}


@Injectable()
export class AuthInterceptor implements NestInterceptor {
  constructor() {}
  public async intercept(
    context: ExecutionContext,
    call$: Observable<any>,
  ): Promise<Observable<any>> {
    console.log(`AuthInterceptor`);
    return call$;
  }
}

@Subscription('catCreated')
  @UseGuards(CatsGuard)
  @UseInterceptors(AuthInterceptor)
  catCreated() {
    return {
      subscribe: () => {
        console.log('Subscription');
        return pubSub.asyncIterator('catCreated');
      },
    };
  }

After subscription only Subscription will be logged into console.

Expected behavior


There should be middleware to work with graphQL subscriptions.

Minimal reproduction of the problem with instructions


I took 12-graphql-apollo sample and added the code from above.

https://github.com/nestjs/nest/tree/master/sample/12-graphql-apollo

What is the motivation / use case for changing the behavior?


Using authorization middleware with subscriptions looks impossible right now.

Environment


Nest version: 5.3.6


For Tooling issues:
- Platform: Mac

Others:

Most helpful comment

What you can do to protect your GraphQL subscription endpoint is this:

GraphQLModule.forRoot({
     typePaths: ['./**/*.graphql'],
      installSubscriptionHandlers: true,
      context: ({ req }) => ({ req }),
      subscriptions: {
        onConnect: connectionParams => {
          // all keys to lowercase
          connectionParams = mapKeys(connectionParams, (value: String, key: String) => key.toLowerCase());
          // get `authroization` header
          const authToken = get(connectionParams, 'authorization', null);
          if (authToken) {
            return this.jwtService.verify(authToken.split(' ')[1]);
          }
          throw new AuthenticationError('authToken must be provided');
        }
      }
    });

All 10 comments

What you can do to protect your GraphQL subscription endpoint is this:

GraphQLModule.forRoot({
     typePaths: ['./**/*.graphql'],
      installSubscriptionHandlers: true,
      context: ({ req }) => ({ req }),
      subscriptions: {
        onConnect: connectionParams => {
          // all keys to lowercase
          connectionParams = mapKeys(connectionParams, (value: String, key: String) => key.toLowerCase());
          // get `authroization` header
          const authToken = get(connectionParams, 'authorization', null);
          if (authToken) {
            return this.jwtService.verify(authToken.split(' ')[1]);
          }
          throw new AuthenticationError('authToken must be provided');
        }
      }
    });

@cschroeter While guards/interceptors don't work with subscriptions it seems to be the best solution. Thanks!

@cschroeter how would you close operations after connection when the token has expired?

Why cannot guards/interceptors work with subscriptions?

What you can do to protect your GraphQL subscription endpoint is this:

GraphQLModule.forRoot({
     typePaths: ['./**/*.graphql'],
      installSubscriptionHandlers: true,
      context: ({ req }) => ({ req }),
      subscriptions: {
        onConnect: connectionParams => {
          // all keys to lowercase
          connectionParams = mapKeys(connectionParams, (value: String, key: String) => key.toLowerCase());
          // get `authroization` header
          const authToken = get(connectionParams, 'authorization', null);
          if (authToken) {
            return this.jwtService.verify(authToken.split(' ')[1]);
          }
          throw new AuthenticationError('authToken must be provided');
        }
      }
    });

I've just been using nestjs for a while, and I want to know how this jwtService was injected under this forRoot.

@limwcj have you found a way to inject the service ?

@hxdef2517 use GraphQLModule.forRootAsync

@bcba25 I post how I get it here Document how authentication guards for GraphQL Subscriptions work

    GraphQLModule.forRootAsync({
      imports: [AuthModule],
      useFactory: async (authService: AuthService) => ({
        context: ({ req, res, payload, connection }: GqlContext) => ({ req, res, payload, connection }),
        subscriptions: {
          onConnect: (connectionParams: ConnectionParams) => {
            const connectionParamsLowerKeys = mapKeysToLowerCase(connectionParams);
            const authToken: string = ('authorization' in connectionParamsLowerKeys)
              && connectionParamsLowerKeys.authorization.split(' ')[1];
            if (authToken) {
              const jwtPayload: GqlContextPayload = authService.getJwtPayLoad(authToken);
              return { currentUser: jwtPayload.username, jwtPayload, headers: connectionParamsLowerKeys };
            }
            throw new AuthenticationError('authToken must be provided');
          },
        },
      }),
      inject: [AuthService],
    }),

note for GraphQLModule.forRootAsync, imports: [AuthModule], useFactory: async (authService: AuthService) and inject: [AuthService]

thanks m8s, and to @bcba25 to, great question ;)
it guide me to the solution :)
thanks to all

sorry I not that above link is broken, has an extra 'l' ..394l in the end,
this is the correct link Document how authentication guards for GraphQL Subscriptions work

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings