I am working on a multi-tenant app using NestJS and I store the tenantId in the token using Jwt, I need to create a database tenant connection before I do database operations but the provider(code below) is being executed before the JwtAuthGuard
but I need the guard to be executed first, Is there a way to change the order of execution?
Controller method (uses JwtAuthGuard):
@Post()
@UsePipes(new ValidationPipe())
@UseGuards(JwtAuthGuard)
create(@Body() createUserDto: CreateFruitDto) {
return this.fruitsService.create(createUserDto);
}
Passport strategy (JwtAuthGuard):
export class JwtStrategy extends PassportStrategy(Strategy) {
private logger = new Logger('JwtStrategy');
constructor(private configService: JwtConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: configService.ignoreExpiration,
secretOrKey: configService.options.secret,
});
}
async validate(payload: any) {
//injects user into req
return {
userId: payload.sub,
email: payload.email,
tenantId: payload.tenant,
};
}
}
Provider being injected into FruitsModule:
provide: 'TENANT_CONTEXT',
scope: Scope.REQUEST,
inject: [REQUEST],
useFactory: (req: Request): ITenantContext => {
const { user } = req as any;
Logger.log(user); // is undefined
const tenantContext: ITenantContext = {
user.tenantId,
};
return tenantContext;
},
IMHO best to avoid request scoped providers. That should have never been introduced in Nest. That scope bubbles up and makes everything above it request scoped as well.
You could introduce middleware to work around this. Middlewares are executed before guards. The auth guard validates and extracts data from the JWT token and stores it on req.user
. Configure a middleware to prepare a user
property on the request. Its setter will be executed when the auth guard sets the user property on the request and it will extract the tenant ID for you.
interface ExecutionMetadata {
tenantId?: number;
}
export class TenantContextMiddleware implements NestMiddleware {
public async use(req: Request, res: Response, next: NextFunction) {
this.metadata: ExecutionMetadata = { tenantId: req.user?.tenantId };
Object.definePropery(req, 'user', {
set(user) {
this._user = user;
this.metadata.tenantId = user?.tenantId;
},
get() {
return this._user;
}
});
next();
}
}
Here I extract the tenant ID from the req.user
and store it on the req.metadata
property.
Using the createParamdecorator()
function from NestJS you could then write a simple parameter decorator to inject this metadata.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Metadata = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.metadata;
},
);
You can then use this decorator to inject this metadata into your controller.
@Controller('cats')
export class CatsController {
@Get()
findAll(@Metadata() metadata: ExecutionMedata): string {
...
}
}
Remark: This decorator will only work for controller methods! NestJS is able to resolve the value for you at that stage of the request. Similar to the @Body()
, @Param()
, @Query()
...decorators. Then you can pass this metadata down as an argument. Or you could do something fancy and setup asynchronous context tracking.