I have been following this tutorial and trying to convert it to GraphQL. I am trying to implement a NestJS + GraphQL + Passport + Express-Session solution but I am having problems with the AuthGuard. Sadly there is only little information on this available online. My AuthGuard looks like this
import { ExecutionContext, Injectable } 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 LocalGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
const result = (await super.canActivate(
new ExecutionContextHost([req]),
)) as boolean;
console.log(result);
await super.logIn(req);
return result;
}
}
Unfortunately, I always receive a Unauthorized response when trying to do a login request. I think the problem comes from my super.canActivate call.
Update: This is my AuthGuard Implementation that finally worked. Overwriting getRequest and adding my input to the body fixes the problem.
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext): Promise<boolean> {
const result = (await super.canActivate(context)) as boolean;
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
await super.logIn(req);
return result;
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const gqlReq = ctx.getContext().req;
if (gqlReq) {
const { authCredentialsInput } = ctx.getArgs();
gqlReq.body = authCredentialsInput;
return gqlReq;
}
}
}
I'm using NestJS as a Service then I've got this problem. I don't usually deal with sessions and tokens.
I've been able to implement the session via passport-local, but I'm having a problem regarding Facebook and Google OAuth login it's not saving the session in the database, while the local guard does.
Below are the codes for the strategies and guards.
File: local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { AuthService } from '../auth.service';
import { VerifyUserDto } from 'src/user/dto/VerifyUser.dto';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
usernameField: 'UserID',
passwordField: 'UserPass',
});
}
async validate(UserID: string, UserPass: string): Promise<any> {
const ToBeVerified = new VerifyUserDto();
ToBeVerified.UserID = UserID;
ToBeVerified.UserPass = UserPass;
return await this.authService.ValidateUser(ToBeVerified);
}
}
File: google.strategy.ts
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor() {
super({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_SECRET,
callbackURL: '/api/v1/google/redirect',
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
const data = {
accessToken,
profile,
// refreshToken,
};
done(null, data);
}
}
File: facebook.strategy.ts
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile, Strategy } from 'passport-facebook';
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
constructor() {
super({
clientID: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_SECRET,
callbackURL: '/api/v1/facebook/redirect',
scope: ['email', 'profile'],
// profileFields: ['emails', 'name'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: (err: any, user: any, info?: any) => void,
): Promise<any> {
const data = {
accessToken,
profile,
};
done(null, data);
}
}
File: Local Guard
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
export class Authenticated implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.isAuthenticated();
}
}
File: Google Guard
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class GoogleGuard extends AuthGuard('google') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
export class Authenticated implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.isAuthenticated();
}
}
File: Facebook Guard
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class FacebookGuard extends AuthGuard('facebook') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
export class Authenticated implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.isAuthenticated();
}
}
I will share with You my code for google autorization
import { Injectable } from '#nestjs/common';
import { ModuleRef } from '#nestjs/core';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { AuthService } from '../services/auth.service';
export interface StrategyProfile<STRATEGY extends string> {
id: string;
displayName: string;
name: {
familyName: string; givenName: string;
};
emails: { value: string; verified: boolean; }[];
photos: { value: string; }[];
provider: STRATEGY;
}
export interface UserExternalAuthorization {
providerId: string;
providerName: string;
eMail: string;
name: string;
avatar: string;
}
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private authService: AuthService, private moduleRef: ModuleRef) {
super({
clientID: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
callbackURL: 'http://localhost:3000/auth/redirect/google',
scope: [ 'email', 'profile', ],
// scope: [ 'email', 'profile', 'https://www.googleapis.com/auth/user.birthday.read' ], // not reading the date
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: StrategyProfile<'google'>,
done: VerifyCallback,
): Promise<any> {
const email = profile.emails.find((x) => x.verified);
if (!email) {
throw new Error('No verified email returned from Google Authorization!');
}
const user: UserExternalAuthorization = {
providerId: profile.id,
providerName: profile.provider,
eMail: email.value,
name: profile.displayName,
avatar: profile?.photos[0]?.value,
};
const u = await this.authService.externalUser(user);
done(null, u);
}
}
Function return await this.authService.ValidateUser(ToBeVerified); from local strategy, check against password (if its correct) and then returns user object im using for jwt (userId, loginType(google, local etc), userType(admin or not) and some other simple data - not many).
Function await this.authService.externalUser(user); from google-strategy returns same object, but also
creates user in the database (by email) if there was no such user before
do not check password (just is user was already there and was removed/banned)
So in every part of the system tokens im using have the same structure and i dont care from where user came).
As for Guard, im using one for each user (as i dont want to distinguish them on this level yet). AuthService is service for logging users and for changing password (even if you are not from local, your account is created and you would be able to log in using password unless you check different option in settings)
import { ExecutionContext, Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(
protected readonly dbService: AuthService,
) {super();}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const can = super.canActivate(context);
if (can instanceof Promise) {
return new Promise(async (resolve, reject) => {
can.then((c) => {
// c !== true -> log notLogged
resolve(c);
}).catch((e) => {
// logged denied, e.message
reject(e);
});
});
} else {
return can;
}
}
}
Also i made decorator for using in controllers
import { applyDecorators, UseGuards } from '#nestjs/common';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '#nestjs/swagger';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
export function IsLogged() {
return applyDecorators(
UseGuards(JwtAuthGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({description: 'Unauthorized'}),
);
}
So in controllers you can have something like that
everything in controller locked for logged users
#IsLogged()
#Controller()
export class name {}
just end point locked only for logged users
#Controller()
export class name {
#IsLogged()
#Get('/')
async fName() {}
}
I wish this helps and leverage some overburden of using many AuthGuard types
I have a nestjs-graphql project. I use class-validator and nestjs-i18n module.
I can use i18nService when injected in a service as intended. What I'm struggling to do however is to use i18n in my ExceptionFilter to return translated message from the ValidationPipe handled by class-validator
What I currently have
//app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ItemsModule } from './items/items.module';
import { GraphQLModule } from '#nestjs/graphql';
import { MongooseModule } from '#nestjs/mongoose';
import { ConfigModule } from '#nestjs/config';
import { I18nModule, I18nJsonParser } from 'nestjs-i18n';
import configuration from './config/configuration';
import * as path from 'path';
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
MongooseModule.forRoot(process.env.MONGO_URI),
I18nModule.forRoot({
fallbackLanguage: 'en',
parser: I18nJsonParser,
parserOptions: { path: path.join(__dirname, '/i18n/') },
}),
GraphQLModule.forRoot({
autoSchemaFile: 'schema.gql',
playground: true,
introspection: true,
context: ({ req, connection }) =>
connection ? { req: connection.context } : { req },
}),
ItemsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
//main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common';
import { AllExceptionsFilter } from './utils/exceptions.filters';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.useGlobalFilters(new AllExceptionsFilter(new Logger('Exceptions')));
app.enableCors();
const port = process.env.PORT || 3000;
await app.listen(port);
}
bootstrap();
//AllExceptionFilters.ts
import {
ExceptionFilter,
Catch,
HttpException,
HttpStatus,
Logger,
} from '#nestjs/common';
import { ApolloError } from 'apollo-server-errors';
import { MongoError } from 'mongodb';
#Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private logger: Logger) {}
async catch(exception: HttpException) {
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const exceptionMessage = (exception) =>
exception instanceof MongoError
? exception?.message
: exception?.response?.message;
this.logger.error(exceptionMessage(exception), exception.stack);
throw new ApolloError(exceptionMessage(exception), status.toString());
}
}
My idea was to pass the i18n key to the class to validate :
import { Field, InputType, } from '#nestjs/graphql';
import { Length } from 'class-validator';
#InputType()
export class ItemToValidate {
#Length(5, 30, { message: 'global.length' }) //i18n Key
#Field()
readonly title: string;
}
... to use it in AllExceptionsFilter as I would in a service:
#Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private logger: Logger, private i18n: I18nService) {}
async catch(exception: HttpException) {
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const exceptionMessage = (exception) =>
exception instanceof MongoError
? exception?.message
: exception?.response?.message;
const translatedMessage = await this.i18n.translate(
exceptionMessage(exception),
);
...
}
}
However, I have a logical error instantiating the Filter class in the boostrap function as I don't know how to access I18nService and inject it from there :
async function bootstrap() {
const logger = new Logger('bootstrap');
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.useGlobalFilters(new AllExceptionsFilter(new Logger('Exceptions'), /*I18nService ?*/ ));
}
bootstrap();
What's the best way to go to achieve this?
As shown in the docs you cannot have dependency injection if you register your filter with useGlobalFilters().
Instead you have to do something like this:
import { Module } from '#nestjs/common';
import { APP_FILTER } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
],
})
export class AppModule {}
I installed React Flux. I am getting the following error:
TypeError: Class constructor AccessManagement cannot be invoked without 'new'
What needs to be instanciated? This is my class.
class Reports extends React.Component {
constructor(props) {
super(props);
}
static getStores() {
return [ReportsStore];
}
static calculateState(prevState, props) {
const rep = ReportsStore.getState();
return {
isLoading: rep.isLoading,
reportRequests: rep.ReportRequests
};
}
render() {
return (<dvi></div>
);
}
componentDidMount() {
}
}
export default Container.create(Reports);
The flux version is: "flux": "^3.1.3",
Any idea how to solve this?
I have a service that retrieves data from a GET request and stores this data into two variables that I want to pass to my component - I can see all the data within the service I am just unable to retrieve this data once in my component.
When I call this.sidebarService.getMenuItems() in the console.log() it returns undefined - any ideas
// sidebar service
import { Injectable } from '#angular/core';
import { HttpClient, HttpParams } from '#angular/common/http';
import { UserService } from '../../services/user.service';
#Injectable()
export class SidebarService {
menu: any;
menuItems: any;
menuCategories: any;
constructor(private http: HttpClient, private userService: UserService) {
getMenuItems() {
if (this.userService.getAuth() != null) {
this.http.get('https://dev.mysite.com/api/calendar/auth',
{ params: new HttpParams()
.set('token', this.userService.getAuth().__token)})
.subscribe(data => {
console.log('data', data);
if (data !== undefined) {
this.menuItems = (data.menuItems !== undefined) ? data.menuItems : [];
this.menuCategories = (data.menuCategories !== undefined) ? data.menuCategories : [];
}
});
}
}
}
// sidebar component
import { Component, OnInit } from '#angular/core';
import { HttpClient, HttpParams } from '#angular/common/http';
import { UserService } from '../../services/user.service';
import { SidebarService } from './sidebar.service';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss'],
})
export class SidebarComponent implements OnInit {
menu: any;
menuItems: any;
menuCategories: any;
constructor(private http: HttpClient, private userService: UserService, private sidebarService: SidebarService) {
console.log('menuService', this.sidebarService.getMenuItems());
}
ngOnInit() {
}
objectKeys = function(obj) {
return Object.keys(obj);
}
}
You are not returning anything from thegetMenuItems() method
You should return an Observable :
...
return this.http.get('...