Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(@nestjs/passport): support graphql context in auth guard #341

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Edgar-P-yan
Copy link

@Edgar-P-yan Edgar-P-yan commented Jul 6, 2020

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[ ] Bugfix
[x] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Other... Please describe:

What is the current behavior?

When adding AuthGuard to graphql resolver class/method you have to add custom logic for resolving the request object as described in Nest.js documentation https://docs.nestjs.com/techniques/authentication#graphql.
For example, to create a guard that would support both HTTP and GraphQL contexts you have to write something like this (the code is taken from one of my projects where both REST and GraphQL APIs should be supported):

import { AuthGuard } from '@nestjs/passport';
import { ExecutionContext, Injectable, ContextType } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { IncomingMessage } from 'http';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext): any {
    if (context.getType<ContextType | 'graphql'>() === 'graphql') {
      const gqlExecCtx = GqlExecutionContext.create(context);
      const request: IncomingMessage = gqlExecCtx.getContext().req;
      return request;
    }
    return context.switchToHttp().getRequest();
  }
}

which works just perfect. But it is a boilerplate.

What is the new behavior?

Now, with the built-in check for graphql context type and request object resolving logic, users will not have to write boilerplate code in each guard.

Does this PR introduce a breaking change?

[ ] Yes
[x] No

Other information

@kamilmysliwiec kamilmysliwiec added the enhancement New feature or request label Jul 9, 2020
'@nestjs/graphql',
'AuthGuard'
);
return GqlExecutionContext.create(context).getContext().req;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if someone has a different value than req for the context? Like with Fastify the property is request.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Didn't know that Fastify adapter uses different properties. Right now i don't have any good ideas on how to sole this issue. Also there are some other adapters, and their properties may be different too

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this depends on platform. Doesn't getContext() return the context provided to the GraphQLModule/ApolloServer constructor (docs)? So it would depend on how people initialize the context, e.g.

GraphQLModule.forRoot({
  context: ({ req }) => {
    return {
      foo: 'bar',
      contextRequest: req,
    }
  }
})

Maybe a better solution is to make this more first class; e.g.

https://github.com/nestjs/graphql/blob/f80f28770db48848160ddf82a81547f537663a5e/lib/services/gql-execution-context.ts#L8-L19

Maybe override the constructor to store the request

class GqlExecutionContext {
  constructor(readonly private originalContext: ExecutionContext, ...) {
    super(...)
  }

  static create(context: ExecutionContext) {
    ...
    new GqlExecutionContext(context, ...)
  }

  getRequest<T = any>() {
    return this.originalContext.switchToHttp().getRequest()
  }
}

or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the same docs you linked

The fields of the object passed to your context function differ if you're using middleware besides Express. See the API reference for details..

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 still, this assumes that someone set up the context key correctly and wouldn't account for someone using the snippet I posted above. Nothing wrong with it; it would just require documentation (admittedly, the workaround we have to do because of this problem should have also been documented)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is current documentation on using Passport with GraphQL, though it still is also only valid for working with Express (as at the time of writing the docs, apollo-server-fastify wasn't compatible). Now when setting the context for fastify, the context should be

context({ request, reply}) => ({ request, reply })

The snippet above looks promising, but I would have to check how well context.switchToHttp().getRequest() works on a GQL request in the first place, as the arguments for HTTP and GQL are different.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmcdo29 we should be able to fix this on the @nestjs/graphql level, here https://github.com/nestjs/graphql/blob/master/lib/utils/merge-defaults.util.ts#L9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants