8

I'm trying to follow along with this tutorial and I'm struggling to convert the implementation to GraphQL.

local.strategy.ts

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authenticationService: AuthenticationService) {
    super();
  }

  async validate(email: string, password: string): Promise<any> {
    const user = await this.authenticationService.getAuthenticatedUser(
      email,
      password,
    );

    if (!user) throw new UnauthorizedException();

    return user;
  }
}

local.guard.ts

@Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const ctx = GqlExecutionContext.create(context);
    const { req } = ctx.getContext();
    req.body = ctx.getArgs();

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

authentication.type.ts

@InputType()
export class AuthenticationInput {
  @Field()
  email: string;

  @Field()
  password: string;
}

authentication.resolver.ts

@UseGuards(LogInWithCredentialsGuard)
@Mutation(() => User, { nullable: true })
logIn(
  @Args('variables')
  _authenticationInput: AuthenticationInput,
  @Context() req: any,
) {
  return req.user;
}

mutation

mutation {
  logIn(variables: {
    email: "email@email.com",
    password: "123123"
  } ) {
    id
    email
  }
}

Even the above credentials are correct, I'm receiving an unauthorized error.

Ellie G
  • 239
  • 1
  • 4
  • 11
  • related information: https://github.com/jaredhanson/passport-local, https://github.com/nestjs/passport/blob/c500cacecc123a750f8e34098b5dcfe86779b4ef/lib/passport/passport.strategy.ts – OnlyWick Nov 30 '22 at 11:30
  • Hi @Ellie. I’m trying to learn NestJS and am having this issue. Did you manage to solve it? – Tiago Brandão May 01 '23 at 17:08

3 Answers3

8

The problem is in your LogInWithCredentialsGuard.

You shouldn't override canAcitavte method, all you have to do is update the request with proper GraphQL args because in case of API request, Passport automatically gets your credentials from req.body. With GraphQL, execution context is different, so you have to manually set your args in req.body. For that, getRequest method is used.

As the execution context of GraphQL and REST APIs is not same, you have to make sure your guard works in both cases whether it's controller or mutation.

here is a working code snippet

@Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
  // Override this method so it can be used in graphql
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    const gqlReq = ctx.getContext().req;
    if (gqlReq) {
      const { variables } = ctx.getArgs();
      gqlReq.body = variables;
      return gqlReq;
    }
    return context.switchToHttp().getRequest();
  }
}

and your mutation will be like

@UseGuards(LogInWithCredentialsGuard)
@Mutation(() => User, { nullable: true })
logIn(
  @Args('variables')
  _authenticationInput: AuthenticationInput,
  @Context() context: any, // <----------- it's not request
) {
  return context.req.user;
}
Mohsin Amjad
  • 1,045
  • 6
  • 14
  • Thanks, the guard works, but it won't set a session cookie without overriding the canActivate method. – Ellie G Jul 15 '21 at 11:55
  • @EllieG I am currently having the same problem, did you resolve it in the end? How can I get the correct request and also overwrite canActivate to set a session cookie? – Tanonic Apr 23 '22 at 12:01
  • Basicly you don't need a session and you don't need to override the canActivate method. Per definition, JWT is stateless. Maybe you missed to call something like an AuthService.login method, to return the JWT in the resolver? I know, it's a year ago, but when you still have issues, I will have a look in this thread. – hmartini Mar 25 '23 at 11:34
2

I've been able to get a successful login with a guard like this:

@Injectable()
export class LocalGqlAuthGuard extends AuthGuard('local') {
  constructor() {
    super();
  }
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    const req = ctx.getContext().req;
    req.body = ctx.getArgs();
    return req;
  }
  async canActivate(context: ExecutionContext) {
    await super.canActivate(context);
    const ctx = GqlExecutionContext.create(context);
    const req = ctx.getContext().req;
    await super.logIn(req);
    return true;
  }
}
alechko
  • 51
  • 1
  • 9
  • 1
    I have tried to implement the AuthGuard like this but I still get an `unauthorized` when sending a login request. Is there any chance you have more detail on your solution? – Tanonic Apr 23 '22 at 23:51
0

change the constructor of LocalStrategy.ts file to this

constructor(private authenticationService: AuthenticationService) {
    //you should pass this {usernameField: 'email'} object to super 
    super({
      usernameField: 'email' 
    });
  }