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

Support AuthGuard #48

Closed
w0wka91 opened this issue Sep 2, 2018 · 35 comments
Closed

Support AuthGuard #48

w0wka91 opened this issue Sep 2, 2018 · 35 comments

Comments

@w0wka91
Copy link

w0wka91 commented Sep 2, 2018

I'm submitting a...


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

Current behavior

I can't access the request within my guards anymore. The request within the execution context is always undefined. It looks like the request doesn't get passed through apollo server correctly.

Expected behavior

The request should be accessible within guards.

Minimal reproduction of the problem with instructions

https://github.com/w0wka91/nest/tree/graphql-passport-integration

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

The request should be accessible to authenticate the user. Furthermore this behavior doesn't let me integrate nestjs/passport into my application.

Environment


Nest version: 5.3.0

 This error accurs since apollo server was updated
For Tooling issues:
- Node version: 10.9  
- Platform:  Mac 

Others:

@kamilmysliwiec
Copy link
Member

kamilmysliwiec commented Sep 3, 2018

ExecutionContext is a wrapper around the arguments array. You cannot access request in GraphQL app unless, for instance, you pass it as a context value. That is explained here https://docs.nestjs.com/graphql/tooling and in more details here https://docs.nestjs.com/guards

@w0wka91
Copy link
Author

w0wka91 commented Sep 3, 2018

It's even undefined when i pass it as a context value. Do you have an example how I'm supposed to pass the request as a context value?
I tried to do it like this but the request is still undefined:


@Module({
  imports: [
    CatsModule,
    GraphQLModule.forRoot({
      typePaths: ['./**/*.graphql'],
        context: ({req}) => ({...req}),
    }),
  ],
})
export class ApplicationModule {}

@kamilmysliwiec
Copy link
Member

@w0wka91
Copy link
Author

w0wka91 commented Sep 3, 2018

Ah, yes thanks. Now I'm able to access the request through the context value in my own guards. But i still cant get it work together with the passport module. It worked all before i upgraded the graphql module. Do i have to write my own guard or is there any way that i can use the provided AuthGuard?
At the moment i get this error:


TypeError: Cannot read property 'headers' of undefined" at JwtStrategy._jwtFromRequest 

@kamilmysliwiec kamilmysliwiec reopened this Sep 3, 2018
@kamilmysliwiec
Copy link
Member

It should be pretty easy to implement. Reopening

@kamilmysliwiec kamilmysliwiec changed the title Request in guards is undefined Support AuthGuard Sep 3, 2018
@cschroeter
Copy link

@kamilmysliwiec I can not wrap my head around it. Could you tell how to switch to the original express request? This would be awesome

@cschroeter
Copy link

cschroeter commented Sep 12, 2018

@w0wka91 Here is a workaround you can use:

First pass the original req object into the graphql context.

GraphQLModule.forRoot({
      typePaths: ['./**/*.graphql'],
      context: ({ req }) => ({ req })
})

Create a fake execution context

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
import { Observable } from 'rxjs';

@Injectable()
export class GraphqlAuthGuard extends AuthGuard('jwt') {
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    const ctx = GqlExecutionContext.create(context);
    const { req } = ctx.getContext();
    return super.canActivate(new ExecutionContextHost([req]));
  }
}
// user.decorator.ts
import { createParamDecorator } from '@nestjs/common';

export const User = createParamDecorator((data, [root, args, ctx, info]) => ctx.req.user);
import { User as CurrentUser } from './user.decorator';

// ...

@UseGuards(GraphqlAuthGuard)
@Query('doSomething')
async doSomething(@CurrentUser() user: User): Promise<User> {
    return await this.userService.findById(user.id);
  }

@kamilmysliwiec
Copy link
Member

Just pushed an example: https://docs.nestjs.com/techniques/authentication (GraphQL)

@sulthanmamusa
Copy link

Hi cschroeter, I tried with your code, but not working and I got the following error message:

Error: Unknown authentication strategy jwt

@cschroeter
Copy link

@sulthanmamusa
Copy link

@sulthanmamusa please have a look here https://docs.nestjs.com/techniques/authentication

Thank you very much. Now I got it...

@kctang
Copy link

kctang commented Nov 2, 2018

Hi guys, I have been trying to get AuthGuard to work in my GraphQL server with @nestjs/passport and @nestjs/jwt modules the whole morning....but still can't get it to work.

The GraphqlAuthGuard I extended AuthGuard is called but somehow my class JwtStrategy extends PassportStrategy(Strategy) does not work properly. The GraphqlAuthGuard is called and request is passed correctly but...

...I got Error: [object Object] at GraphqlAuthGuard.handleRequest in the GQL response with a 401 Unauthorized.

Any place I can see a full example that includes code for both PassportStrategy and AuthGuard for GraphQL? Appreciate some help here. Thanks!

@trykers
Copy link

trykers commented Feb 28, 2019

Same problem, everything is ok with REST, when i try to use GqlAuthGuard, the validate() function in jwt.strategy.ts was never called... Any idea ?

@kamilmysliwiec

This comment has been minimized.

@trykers
Copy link

trykers commented Mar 1, 2019

I read the doc many and many times, my problem was that I wrote "Baerer" instead of "Bearer"... All works fine now...

@LandazuriPaul
Copy link

Just a simple tip for newcomers running their app with Nest v6.

As I wanted to make use of the integration of TypeGraphQL in Nest v6 — btw, thank you for the great job with this new release @kamilmysliwiec! — I've migrated my app.

So I had a GraphqlAuthGuard using the workaround suggested by @cschroeter and it was fine until my migration to Nest v6 which changes one file name in @nestjs/core package.

For people wishing to integrate the above workaround with Nest v6, change the import of the ExecutionContextHost to:

import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';

The only difference is the dash instead of the point in execution-context-host. 😉

@callmesoul
Copy link

callmesoul commented Jul 30, 2019

I All works fine now。 my problem is frontend headers write Authorization to authorization

@ghiscoding
Copy link

ghiscoding commented Sep 4, 2019

Hey guys, I followed this Medium post to add OAuth to my application and it has it's own AuthModule and another module CatsModule for the GraphQL implementation. The problem that I'm having is that from the GraphQL Sample I cannot get the User in the Context Request, it's always null or undefine. I think it might be because Context Requests are isolated between NestJS modules? I'm totally new so I'm not sure... I just started this week to learn NestJS, I'm on latest version 6.6.7.

In the auth.controller, I can get the User in the Context Request like this

  @Get('protected')
  @UseGuards(AuthGuard('jwt'))
  protectedResource(@Request() req) {
    return { result: 'JWT is working!', user: req.user }; // <- all good
  }

but this User is not available in any file of my other module (cats.resolver, gqp-auth.guard), I trace the problem up to the GraphQLModule which is imported in the AppModule, the Context Request in there doesn't have the User and so none of the other file will get it since they all derived from the Context passed in the GraphQLModule.

@Module({
  imports: [
    AuthModule,
    CatsModule,
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      context: ({ req }) => {
        console.log('user', req.user); // <-- this is always undefined
        return ({ req });
      },
    }),
    MongooseModule.forRoot('mongodb://localhost/nest', { useNewUrlParser: true }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

The GraphQL Guard still seems to work, I guess it's because it only checks that a context exist, but I just can't get the connected user. I would be really happy to have some help/guidance in fixing this.

Note that I'm totally new to NestJS and I wanted to learn it since for a long time (personal project) and this is basically my first week at it. Also I'm more of a frontend dev and my NodeJS knowledge is a bit low, though I played a bit with Koa some time ago.

EDIT

I actually got it working, but I'm not totally sure how it all works. I do have undefined in all the areas that I wrote earlier, but in the end, I do get the user in the createParamDecorator. Anyhow, this now works

@Query(() => UserDto)
@UseGuards(GraphqlPassportAuthGuard)
whoAmI(@CurrentUser() user: User) {
    return user;
}

@orenherman
Copy link

@ghiscoding i encountered the same scenario.
I can get the user from the http context but not from the GraphQL context.
I wonder how you overcame it ? I mean the request doesn't pass the guard to get to the paramDecorater..

This is quite frustrating.. it seems that the graphql context is created without first invoking the desserialzeUser and thus no user is present on the request.

@ghiscoding
Copy link

@orenherman

I actually got it working, but I'm not totally sure how it all works. I do have undefined in all the areas that I wrote earlier, but in the end, I do get the user in the createParamDecorator. Anyhow, this now works

@Query(() => UserDto)
@UseGuards(GraphqlPassportAuthGuard)
whoAmI(@CurrentUser() user: User) {
    return user;
}

So I just removed the console.log and it was working with the code I wrote in my last post.

@chris-ls
Copy link

chris-ls commented Nov 29, 2019

So far, this discussion has addressed only the Jwt Strategy but I am unsure how to get the Local Strategy to work.
To summarize the steps involved with the Jwt Strategy:

  • Put the Request into the GraphQL context for every request
GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      context: ({ req }) => ({ req })
})
  • Provide an implementation for getRequest inside of your Guard which will grab the Request from the GraphQL context
getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);

    return ctx.getContext().req;
}

From this point onward, the typical JwtStrategy can proceed as it only needs to access the Authorization header found in the request object.

This also leads in to being able to define a GraphQL specific @User decorator.

export const GqlUser = createParamDecorator(
  (data, [root, args, ctx, info]) => ctx.req.user,
);

This works because our Request object is now available at ctx.req and this req is passed to Passport thanks to the getRequest() method we implemented for the Guard, which then attaches a user object upon successful authentication.

I have been trying to get an AuthGuard using the Local Strategy working, but not sure how to proceed.
As I understand, the issue is that the Local Strategy is expecting a body on the request that looks something like

{
  "username": "bob",
  "password": "password123"
}

but is instead getting something like

{
  "query": "mutation { signIn(data: {username: "bob", password: "password123"}) { accessToken } }"
}

It's not a huge problem in this specific case, since AuthGuard('local') is typically only used in very few places, such as a sign-in mutation and so the logic can just be explicitly handled in the resolver.

@kamilmysliwiec
Copy link
Member

As for local strategy with GraphQL, you shouldn't use passport. I would recommend creating your own guard instead

@ghiscoding
Copy link

ghiscoding commented Nov 30, 2019

@kamilmysliwiec but how do we get access to the user from a guard (sorry I'm new to NestJS)?
In my case I use PassPort and GraphQL and if I do my own custom guard, I don't have access to the useer and I don't know how to get it. It should be part of the request, so why I can't get it?

This is my GraphQL guard, the console log always return undefined

@Injectable()
export class GraphqlPassportAuthGuard extends AuthGuard('jwt') {
  roles: string[];

  constructor(roles?: string | string[]) {
    super();
    this.roles = Array.isArray(roles) ? roles : [roles];
  }

  canActivate(context: ExecutionContext): boolean {
    const ctx = GqlExecutionContext.create(context);
    const req = ctx.getContext().req;
    console.log('graphql guard user', req && req.user)
    return true;
  }

  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    const req = ctx.getContext().req;
    console.log('graphql guard user', req && req.user)
    return req;
  }
}

and I try using it with following code

@Query(() => UserDto)
@UseGuards(GraphqlPassportAuthGuard)
whoAmI(@CurrentUser() user: User) {
    return user;
}

I can get the user from the @CurrentUser() but I can't get it inside the guard. I tried so many things, I don't know what to do anymore.

I also posted a Stack Overflow question

@ghiscoding
Copy link

@chris-ls
I got an answer to my SO question, I finally got the canActivate to work like I wanted with roles check... hopefully this would help you as well.

Cheers

async canActivate(context: ExecutionContext): Promise<boolean> {
    await super.canActivate(context);
    const ctx = GqlExecutionContext.create(context);
    const req = ctx.getContext().req;
   console.log('graphql guard user', req && req.user)
}

@VictorGaiva
Copy link

VictorGaiva commented Dec 20, 2019

I was able to authenticate HTTP, GraphqL Queries and GraphqL Subscriptions using JWT with the following solution, without using @nestjs/passport.

Fist, add context: ({ req }) => ({ req }) to the GraphQLModule import in your root module imports:

@Module({
  imports: [
    GraphQLModule.forRoot({
      typePaths: ['./**/*.gql'],
      installSubscriptionHandlers: true,
      context: ({ req }) => ({ req }), // <------ HERE
    }),
    UsersModule,
  ],
  providers: [
    MainService,
  ],
  controllers: [MainController],
})

Create auth.module.ts with the following content:

import { JwtModule } from '@nestjs/jwt';
import { Module } from '@nestjs/common';

import { HttpAuthGuard, WsAuthGuard, GqlAuthGuard } from './auth.guard';
import { AuthService } from './auth.service';

@Module({
  imports: [
    JwtModule.register({
      secret: process.env.PRIVATE_KEY,
      signOptions: { expiresIn: process.env.TOKEN_EXPIRATION },
    }),
  ],
  providers: [
    HttpAuthGuard,
    WsAuthGuard,
    GqlAuthGuard,
    AuthService
  ],
  exports: [HttpAuthGuard, WsAuthGuard, GqlAuthGuard, AuthService],
})
export class AuthModule { }

Create auth.service.ts with the following content:

import { Injectable } from '@nestjs/common';

import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(
    private readonly jwtService: JwtService,
  ) { }

  GenerateToken(payload: any) {
    return this.jwtService.sign(payload);
  }

  ValidateToken(token: string) {
    try {
      this.jwtService.verify(token);
      return true;
    } catch (error) {
      return error.name;
    }
  }
}

Finally, create auth.guard.ts with the following content:

import {
  Injectable,
  ExecutionContext,
  CanActivate,
  BadRequestException,
  UnauthorizedException
} from "@nestjs/common";

import { AuthService } from "./auth.service";

@Injectable()
export class HttpAuthGuard implements CanActivate {
  constructor(
    private readonly auth: AuthService,
  ) { }

  canActivate(context: ExecutionContext) {
    // Get the header
    const authHeader = context.switchToHttp().getRequest().headers.authorization as string;

    if (!authHeader) {
      throw new BadRequestException('Authorization header not found.');
    }
    const [type, token] = authHeader.split(' ');
    if (type !== 'Bearer') {
      throw new BadRequestException(`Authentication type \'Bearer\' required. Found \'${type}\'`);
    }
    const validationResult = this.auth.ValidateToken(token); 
    if (validationResult === true) {
      return true;
    }
    throw new UnauthorizedException(validationResult);
  }
}

@Injectable()
export class GqlAuthGuard implements CanActivate {
  constructor(
    private readonly auth: AuthService,
  ) { }

  canActivate(context: ExecutionContext) {
    // Get the header
    const authHeader = context.getArgs()[2].req.headers.authorization as string;

    if (!authHeader) {
      throw new BadRequestException('Authorization header not found.');
    }
    const [type, token] = authHeader.split(' ');
    if (type !== 'Bearer') {
      throw new BadRequestException(`Authentication type \'Bearer\' required. Found \'${type}\'`);
    }
    const validationResult = this.auth.ValidateToken(token);
    if (validationResult === true) {
      return true;
    }
    throw new UnauthorizedException(validationResult);
  }
}

@Injectable()
export class WsAuthGuard implements CanActivate {
  constructor(
    private readonly auth: AuthService,
  ) { }

  canActivate(context: ExecutionContext) {
    // Since a GraphQl subscription uses Websockets,
    //     we can't pass any headers. So we pass the token inside the query itself
    const token = context.switchToWs().getData().token;

    if (!token) {
      throw new BadRequestException('Authentication token not found.');
    }

    const validationResult = this.auth.ValidateToken(token);
    if (validationResult === true) {
      return true;
    }
    throw new UnauthorizedException(validationResult);
  }
}

That's it. We can now use the guards by adding AuthModule to module imports you want to use.

Usage examples:

  • HTTP:
@Get('profile')
@UseGuards(HttpAuthGuard)
getProfile() {
  return 'Got that';
}
  • GraphQL Query:
@Mutation()
@UseGuards(GqlAuthGuard)
async setOnline(@Args('userId') userId: string) {
  return await this.service.SetOnline(userId);
}
  • GraphQL Subscription:

Now for GraphQL Subscriptions, we have to pass the token inside the query itself. I defined the userOnline Subscription like this:

type Subscription {
  userOnline(token:String): UserSubscribeDto!
}

then the query should be something like:

subscription{
  userOnline(token:"<YOUR TOKEN>"){
    userId
  }
}

With the things above defined, we can use the guard like this:

@Subscription()
@UseGuards(WsAuthGuard)
userOnline() {
  return this.publisher.asyncIterator('userOnline');
}

The idea of this implementation is to keep it, simple, transparent and easy to understand. For the initial use case, protecting an endpoint from unauthenticated access, it should be enough. There are still things that could be added, like roles and a way to access the token payload inside the handlers. I'll try to implement these and might comeback with an update.

But for now this should be better the the magical and confusing implementation on the Oficial documentation. I really recommend you not to use @nestjs/passport, as it ends up adding unnecessary abstraction to the setup. I'll go even further and say that the Documentation should remove its usage completely. It adds a whole lot of overhead to something that should've been simple and ends up confusing beginners. If someone needs a solution like @nestjs/passport, they will look for it.

PS: I decided to post this here as it was one of the threads I found when trying to find a solution for this. This comment is targeted and people who might be going through the same problem I was and found this.

@MeStrak
Copy link

MeStrak commented Jan 6, 2020

@VictorGaiva thank you!! I've been struggling to figure out how to get it working for subscriptions and yours is the only clear explanation I've found.

I am using Auth0 with Passport, I already had a GraphQL auth guard implemented which worked for normal requests, but not for subscriptions. I'm also using TypeGraphQL and my schema is auto generated so my solution is inspired by yours but slightly different.

Read token from the arguments for subscription in my resolver:

  @Subscription(returns => ItemType)
  @UseGuards(new GqlAuthGuard('jwt'))
  itemCreatedOrUpdated(@Args('token') token: string) {
    return pubSub.asyncIterator('itemCreatedOrUpdated');
  }

Instead of a separate auth guard for websockets, I build the context using the passed token parameter if it is missing (if req is undefined):

import {
  ExecutionContext,
  Injectable,
  BadRequestException,
} from '@nestjs/common';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
  canActivate(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    let { req } = ctx.getContext();

    //Subscription requests don't have context, so check the token for header
    if (typeof req === 'undefined') {
      const token = context.switchToWs().getData().token;

      if (!token) {
        throw new BadRequestException('Authentication token not found.');
      }

      //build request context so that it can be read by super.canActivate
      const authHeader = {
        authorization: token,
      };
      req = { headers: authHeader };
    }

    return super.canActivate(new ExecutionContextHost([req]));
  }
}

Finally in the front end (I'm using vue-apollo), pass the token as an argument which is currently in localStorage in my code:

subscribeToMore: [
        {
          document: gql`
            subscription subItems($token: String!) {
              itemCreatedOrUpdated(token: $token) {
                id
                title
                estimate
                status
                description
              }
            }
          `,
          // Variables passed to the subscription. Since we're using a function,
          // they are reactive
          variables() {
            return {
              token: 'Bearer ' + localStorage.getItem('gqlbear'),
            };
          },
      ... ]

@greg-md
Copy link

greg-md commented Feb 7, 2020

After hours of searching how to enable gql auth guard for subscriptions, I came up with a working solution based on https://www.apollographql.com/docs/graphql-subscriptions/authentication/ without much refactoring.

On client side, send headers via connectionParams payload:

connectionParams: async () => {
  const auth = await authStorage.get().toPromise();

  if (auth) {
    return {
      headers: {
        Authorization: `Bearer ${auth.access_token}`
      },
    }
  }
}

On server side, listen for the payload and send it as a request via context config:

context: ({ req, connection }) => ({ req: req || connection?.context }),
subscriptions: {
  // Send client connect payload to the connection context
  onConnect: connectionParams => connectionParams,
}

And voila, you can use the base gql auth guard on your subscriptions:

@Subscription(returns => Boolean)
@UseGuards(GqlAuthGuard)
mySubscription() {
  return this.pubSub.asyncIterator('mySubscription');
}

@kamilmysliwiec
Copy link
Member

Please, note that the API for the createParamDecorator has changed in v7.0.0. The example (from the docs) is updated now: https://docs.nestjs.com/techniques/authentication#graphql

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

export const CurrentUser = createParamDecorator(
  (data: unknown, context: ExecutionContext) => {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req.user;
  },
);

@toondaey
Copy link

toondaey commented Mar 26, 2020

Please, note that the API for the createParamDecorator has changed in v7.0.0. The example (from the docs) is updated now: https://docs.nestjs.com/techniques/authentication#graphql

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

export const CurrentUser = createParamDecorator(
  (data: unknown, context: ExecutionContext) => {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req.user;
  },
);

Hi @kamilmysliwiec, so I'm relatively new to nestjs, but not graphql. However I've tried this method but keep getting this error: context.getType is not a function.

My guard below:

import { AuthService } from '../../auth.service';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Injectable, ExecutionContext } from '@nestjs/common';

@Injectable()
export class LoginGuard extends AuthGuard('local') {
    // ...
    getRequest(context: ExecutionContext) {
        const ctx = GqlExecutionContext.create(context);

        const { request } = ctx.getContext();

        request.body = ctx.getArgs();

        return request;
    }
}

and custom decorator is just as the same above.
Great job on the framework btw.

UPDATE:
Solved it by upgrading my nestjs packages.

@mateusduraes
Copy link

Does someone has a working example with the authentication process for GraphQL with NestJS using JWT with @nest/passport.

I could not make it work with what we have in the docs, and, in my case, it would be good to use @nest/passport because I am going to have also Facebook and Google login. Thanks

@toondaey
Copy link

Does someone has a working example with the authentication process for GraphQL with NestJS using JWT with @nest/passport.

I could not make it work with what we have in the docs, and, in my case, it would be good to use @nest/passport because I am going to have also Facebook and Google login. Thanks

Can you share what you've implemented so far, please?

@mateusduraes
Copy link

mateusduraes commented Mar 31, 2020

@toondaey I wasn't able to use the integration with @nest/passport for GraphQL. I believe the best option is to not use @nestjs/passport with GraphQL.

I end up using the following approach (that works pretty well)

// auth.module.ts
@Module({
  imports: [
    UserModule,
    JwtModule.register({
      secret: 'my-app-secret',
    }),
  ],
  providers: [AuthResolver, AuthService, GqlAuthGuard],
  exports: [GqlAuthGuard],
})
export class AuthModule {}
// gql-auth.guard.ts
@Injectable()
export class GqlAuthGuard implements CanActivate {
  constructor(private readonly authService: AuthService) {}

  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = this.getRequest(context);
    const authHeader = req.headers.authorization as string;

    if (!authHeader) {
      throw new BadRequestException('Authorization header not found.');
    }
    const [type, token] = authHeader.split(' ');
    if (type !== 'Bearer') {
      throw new BadRequestException(`Authentication type \'Bearer\' required. Found \'${type}\'`);
    }
    const { isValid, user } = await this.authService.validateToken(token);

    if (isValid) {
      req.user = user;
      return true;
    }
    throw new UnauthorizedException('Token not valid');
  }
}
// auth.service.ts
@Injectable()
export class AuthService {
  constructor(private jwtService: JwtService, @InjectRepository(User) private usersRepository: Repository<User>) {}

  async validateUser(email: string, password: string): Promise<LoginResponse> {
    const user = await this.usersRepository.findOne({ email });
    if (!user) {
      throw 'user not found';
    }
    if (user.password === password) {
      const token = this.jwtService.sign({
        userId: user.id,
        email: user.email,
      });
      return { user, token };
    }
  }

  async validateToken(token: string): Promise<{ isValid: boolean; user?: User }> {
    try {
      const { userId } = this.jwtService.verify(token);
      const user = await this.usersRepository.findOne(userId);
      return { user, isValid: true };
    } catch (e) {
      return { isValid: false };
    }
  }
}
// current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

export const CurrentUser = createParamDecorator((data: unknown, context: ExecutionContext) => {
  const ctx = GqlExecutionContext.create(context);
  return ctx.getContext().req.user;
});
@Resolver(() => User)
export class AuthResolver {
  constructor(private authService: AuthService) {}

  @Mutation(() => LoginResponse, { name: 'login' })
  async login(@Args('loginData') { email, password }: LoginInput): Promise<LoginResponse> {
    return this.authService.validateUser(email, password);
  }

  @UseGuards(GqlAuthGuard)
  @Query(() => User, { name: 'whoAmI' })
  async whoAmI(@CurrentUser() user: User): Promise<User> {
    return user;
  }
}

In order to finish and make it a real-life project (let's say), I just need to implement the process to encrypt the password.

I hope it may help someone that has the same problem.
I end up using a little bit of each one of the previous answers.

@osamaKu
Copy link

osamaKu commented Apr 3, 2020

@mateusduraes, this was very helpful, Thanks

@unlight
Copy link

unlight commented Apr 4, 2020

Hi, guys. Workaround above #48 (comment) for @CurrentUser decorator works, but it requires applying GraphqlAuthGuard guard, what if I want CurrentUser value be optional in my resolver?

    // no guard here
    @Query(() => User)
    async user(
        @CurrentUser() currentUser?: PassportUserFields,
    ) {
       // currentUser is always undefined, context.req.user never assigned by passport
       // even if Authorization header was sent
        return currentUser;
    }

So, is there way to apply logic for assigning req.user from nest/passport?

@humayunkabir
Copy link

Same problem, everything is ok with REST, when I try to use GqlAuthGuard, the validate() function in jwt.strategy.ts was never called... Any idea?

Any update on this problem???

@nestjs nestjs locked as too heated and limited conversation to collaborators Apr 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests