Greetings, all.
In brief, I’m attempting to develop a GraphQL API that is guarded by an access token generated upon successful login. I’m copying and pasting the token into Postman and sending it with requests to a guarded resolver endpoint as an authorization header. I’ve verified that the token is indeed reaching the server as expected. The problem is that I seem to be unable to actually verify the token. Note that interpolation of things like environment variables, etc., is working and the application is compiling successfully. It’s just that I’m not sure how to validate the access token provided by Auth0. I could really use some help with this and would even be willing to offer a reward to anyone who could help me solve this matter. Thanks!
My setup looks like this:
app.module.ts
src/authorization
- auth.guard.ts
- authorization.module.ts
- jwt.strategy.ts
src/products
- products.resolver.ts
That’s it. Pretty simple. As to the files’ contents:
// src/app.module.ts
/* istanbul ignore file */
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ConfigModule } from '@nestjs/config';
import { ProductsModule, ProductsService, ProductsResolver } from './products';
import { PrismaService } from './prisma.service';
import { AuthorizationModule } from './authorization/authorization.module';
@Module({
imports: [
GraphQLModule.forRoot({
debug: false,
playground: true,
typePaths: ['./**/*.graphql'],
definitions: { path: [process.cwd(), 'src/graphql.ts'].join() },
}),
PrismaService,
ConfigModule.forRoot({
isGlobal: false,
envFilePath: ['.env.development.local', '.env.test'],
}),
AuthorizationModule,
],
providers: [ProductsModule, ProductsResolver, ProductsService, PrismaService],
})
export class AppModule {}
// src/authorization/auth.guard.ts
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';
// I'm not sure what to do here!
export class Auth0Guard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
try {
const graphqlContext = GqlExecutionContext.create(context);
console.log(graphqlContext.getContext().req);
const [_, token] = graphqlContext
.getContext()
.req.headers.authorization.split(' ');
if (token) return true;
} catch (error) {
throw new UnauthorizedException();
}
}
}
// src/authorization/authorization.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule } from '@nestjs/config';
import { Auth0Guard } from './auth.guard';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
PassportModule.register({
defaultStrategy: 'jwt'
}),
ConfigModule.forRoot({
envFilePath: ['.env.development.local', '.env.test'],
}),
],
providers: [JwtStrategy, Auth0Guard],
exports: [PassportModule, Auth0Guard],
})
export class AuthorizationModule {}
// src/authorization/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { passportJwtSecret } from 'jwks-rsa';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
secretOrKeyProvider: passportJwtSecret({
cache: false,
rateLimit: true,
jwksRequestsPerMinute: 100,
jwksUri: configService.get('AUTH0_JWKS'),
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: configService.get('AUTH0_AUDIENCE'),
issuer: configService.get('AUTH0_ISSUER'),
passReqToCallback: false,
algorithms: ['RS256'],
});
}
// This is never envoked!
async validate(req: any, payload: any) {
console.log('Inside validate method', req, payload);
return payload;
}
}
// src/products/products.resolver.ts
import {
Query,
Mutation,
Resolver,
Args,
ArgsType,
Field,
} from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common';
import { Auth0Guard } from '../authorization/auth.guard';
import { ProductsService } from './products.service';
@ArgsType()
@Resolver('Product')
export class ProductsResolver {
constructor(private productsService: ProductsService) {}
@Query('products')
// I use the guard I've created instead of the AuthGuard('jwt') because mine
// extends AuthGuard('jwt'); the problem is: how do I now *verify/validate*
// the JWT itself? That's been the missing piece of the puzzle for some time.
@UseGuards(Auth0Guard)
async getProducts() {
return await this.productsService.getAllProducts();
}
}