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
Comments
|
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?
|
Just pass request as a |
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?
|
It should be pretty easy to implement. Reopening |
@kamilmysliwiec I can not wrap my head around it. Could you tell how to switch to the original express request? This would be awesome |
@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);
} |
Just pushed an example: https://docs.nestjs.com/techniques/authentication (GraphQL) |
Hi cschroeter, I tried with your code, but not working and I got the following error message: Error: Unknown authentication strategy jwt |
@sulthanmamusa please have a look here https://docs.nestjs.com/techniques/authentication |
Thank you very much. Now I got it... |
Hi guys, I have been trying to get The ...I got Any place I can see a full example that includes code for both |
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 ? |
This comment has been minimized.
This comment has been minimized.
I read the doc many and many times, my problem was that I wrote "Baerer" instead of "Bearer"... All works fine now... |
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 For people wishing to integrate the above workaround with Nest v6, change the import of the import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; The only difference is the dash instead of the point in |
I All works fine now。 my problem is frontend headers write |
Hey guys, I followed this Medium post to add OAuth to my application and it has it's own In the @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 ( @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 @Query(() => UserDto)
@UseGuards(GraphqlPassportAuthGuard)
whoAmI(@CurrentUser() user: User) {
return user;
} |
@ghiscoding i encountered the same scenario. 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. |
@Query(() => UserDto)
@UseGuards(GraphqlPassportAuthGuard)
whoAmI(@CurrentUser() user: User) {
return user;
} So I just removed the |
So far, this discussion has addressed only the Jwt Strategy but I am unsure how to get the Local Strategy to work.
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
This works because our I have been trying to get an AuthGuard using the Local Strategy working, but not sure how to proceed.
but is instead getting something like
It's not a huge problem in this specific case, since |
As for local strategy with GraphQL, you shouldn't use passport. I would recommend creating your own guard instead |
@kamilmysliwiec but how do we get access to the user from a guard (sorry I'm new to NestJS)? 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 I also posted a Stack Overflow question |
@chris-ls 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)
} |
I was able to authenticate Fist, add @Module({
imports: [
GraphQLModule.forRoot({
typePaths: ['./**/*.gql'],
installSubscriptionHandlers: true,
context: ({ req }) => ({ req }), // <------ HERE
}),
UsersModule,
],
providers: [
MainService,
],
controllers: [MainController],
}) Create 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 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 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 Usage examples:
@Get('profile')
@UseGuards(HttpAuthGuard)
getProfile() {
return 'Got that';
}
@Mutation()
@UseGuards(GqlAuthGuard)
async setOnline(@Args('userId') userId: string) {
return await this.service.SetOnline(userId);
}
Now for GraphQL Subscriptions, we have to pass the token inside the query itself. I defined the 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 But for now this should be better the the magical 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. |
@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'),
};
},
... ] |
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: 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: ({ 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');
} |
Please, note that the API for the 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: 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. UPDATE: |
Does someone has a working example with the authentication process for GraphQL with NestJS using JWT with I could not make it work with what we have in the docs, and, in my case, it would be good to use |
Can you share what you've implemented so far, please? |
@toondaey I wasn't able to use the integration with 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. |
@mateusduraes, this was very helpful, Thanks |
Hi, guys. Workaround above #48 (comment) for
So, is there way to apply logic for assigning |
Any update on this problem??? |
I'm submitting a...
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
The text was updated successfully, but these errors were encountered: