how to i access the request.user nestjs microservice? - scope

I have a microservice setup with an api gateway accepting http requests and passing them to miscroservices (media-service, payment-service, etc.) using message pattern.
I can access the request.user in the api-gateway which gives me the authenticated user from the decoded jwt.
What i would like is, to pass this to the media microservice and access it within the functions in media service.
How do i achieve this?
controller in api-gateway
export class MediasController {
constructor(#Inject('MEDIA_SERVICE') private client: ClientProxy) { }
#Get()
#UseGuards(AuthGuard('jwt'))
async find(#Query() query: QueryArgs): Promise<any> {
const pattern = { cmd: 'media-find' };
return await this.client.send<any>(pattern, query)
}
}
controller & service in media microservice
medias.controller.ts
#Controller({ path: 'medias', scope: Scope.REQUEST })
export class MediasController {
constructor(private readonly mediasService: MediasService) { }
#MessagePattern({ cmd: 'media-find' })
async find(query: any): Promise<any> {
return await this.mediasService.find(query)
}
}
medias.service.ts
export class MediasService {
constructor(#InjectRepository(Media) private readonly mediaRepository: Repository<Media>) {
}
async find(query): Promise<Media[]> {
// access request.user here
return await this.mediaRepository.find(query);
}
}

Related

Nestjs Passport google Oauth2 with custom JWT

What I need to achieve -
I need to have a dynamic redirect URL (not google's refer Current Flow last step) based on the query param sent by Frontend.
I need to send my custom JWT token instead of google token which can have roles and permission in it. (Not sure if we can add claims to google token as well)
In my app, I have 2 roles - candidate, and recruiter. I need to use Gmail auth and create a user in my DB according to roles, which again I could achieve via query param pass by Frontend.
Current Flow -
Frontend calls google/login -> GoogleAuthGaurd -> GoogleStrategy -> google/redirect -> Generate custom JWT token -> redirect to frontend with access token and refresh token in URL.
Problem -
In Passport, we have GoogleAuthGaurd, and GoogleStrategy. I have read somewhere that Auth Gaurd decides which strategy to be used and it internally calls the strategy and further execution.
If I pass query param to google/login it totally ignores it and redirects to strategy. We can access contecxt (ExecutionContext) in AuthGaurd, so we can get query param there but how to pass it to strategy? or may be invoke custom strategy from auth guard not sure if we can.
Is there any way I could pass the query param to strategy then I could write a logic to update the redirect URI or roles?
import { TokenResponsePayload } from '#identity/payloads/responses/token-response.payload';
import { Controller, Get, Inject, Req, Res, UseGuards } from '#nestjs/common';
import { ApiTags } from '#nestjs/swagger';
import { Request, Response } from 'express';
import {
AuthServiceInterface,
AuthServiceSymbol,
} from '../interfaces/services/auth-service.interface';
import { AccessTokenGaurd } from '../utils/access-token.guard';
import { GoogleAuthGaurd } from '../utils/google-auth-guard';
import { RefreshTokenGuard } from '../utils/refresh-token.guard';
#ApiTags('Auth')
#Controller('auth')
export class AuthController {
constructor(
#Inject(AuthServiceSymbol)
private authService: AuthServiceInterface,
) {}
#Get('google/login')
#UseGuards(GoogleAuthGaurd)
handleGoogleLogin() {}
#Get('google/redirect')
#UseGuards(GoogleAuthGaurd)
async handleGoogleRedirect(#Req() req, #Res() res: Response) {
const tokens = await this.authService.signInWithGoogle(req);
res.redirect(302,`http://127.0.0.1:4200?access_token=${tokens.accessToken}&refresh_token=${tokens.refreshToken}`)
}
#Get('logout')
#UseGuards(AccessTokenGaurd)
async remove(#Req() req: Request): Promise<void> {
return this.authService.removeSession(req.user['sessionId']);
}
#UseGuards(RefreshTokenGuard)
#Get('refresh')
async refreshToken(#Req() req: Request): Promise<TokenResponsePayload> {
const sessionId = req.user['sessionId'];
const refreshToken = req.user['refreshToken'];
return this.authService.refreshTokens(sessionId, refreshToken);
}
}
import { Injectable } from '#nestjs/common'; import { AuthGuard } from '#nestjs/passport';
#Injectable() export class GoogleAuthGaurd extends AuthGuard('google') {}
import { CalConfigService, ConfigEnum } from '#cawstudios/calibrate.common';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile, Strategy } from 'passport-google-oauth20';
import { VerifiedCallback } from 'passport-jwt';
const configService = new CalConfigService();
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
clientID: configService.get(ConfigEnum.CLIENT_ID),
clientSecret: configService.get(ConfigEnum.CLIENT_SECRET),
callbackURL: configService.get('CALLBACK_URL'),
scope: ['profile', 'email'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifiedCallback,
): Promise<any> {
const email = profile.emails[0].value;
done(null, email);
}
}

Implement keycloak connect in express application

Im using Keycloak server for authorization(microservice).
I cloned this project https://github.com/w3tecch/express-typescript-boilerplate/tree/master
In my express project I'm using keycloak-connect(keycloak adapter) library
I want to implement code below in my new express-nodejs project.
reference: https://github.com/keycloak/keycloak-quickstarts/blob/latest/service-nodejs/app.js
app.get('/service/secured', keycloak.protect(), function (req, res) {
res.json({message: 'secured'});
});
I'm using routing-controllers library for express. I don't know how to use keycloak.protect() in this project. any suggestions?
So far what I managed to do(Im not sure if this is the right way):
authorizationChecker.ts
export function authorizationChecker(connection: Connection): (action: Action, roles: any[]) => Promise<boolean> | boolean {
return async function innerAuthorizationChecker(action: Action, roles: string[]): Promise<any> {
try {
const grant = await KeycloakService.keycloak.getGrant(action.request, action.response);
action.request.auth = await KeycloakService.keycloak.grantManager.userInfo(grant.access_token);
return true;
} catch (e) {
console.log(e)
return false;
}
};
}
and than I use annotation #Authorized() in controller:
#Authorized()
#JsonController('/users')
export class UserController {
}
Im using postman to test api. Im able to obtain token and i can make few requests on my api before i got authentication error.
Expire time on token is not set.

Nestjs + Passport: Prevent user 1 to access information of user 2

How can I prevent user 1 to access information of user 2 using passport in a Nesjs app ?
I already have 2 strategies:
the local strategy which validate a user with email/password. The route protected by this strategy return a jwt token.
the jwt strategy which validate the given jwt token.
Now, I want to restrict access to routes such as users/:id to jwt token which actually have the same userId encrypted.
How to do that ?
EDIT
I was mixing Authentication and Authorization: what I want to achieve is about authorization, once the user has been authenticated.
I had to use Guard:
own.guard.ts
#Injectable()
export class OwnGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
return req.user.id === req.params.id;
}
}
Then use it in my route:
#Get(':id')
#UseGuards(OwnGuard)
async get(#Param('id') id: string) {
return await this.usersService.get(id);
}
ORIGINAL ANSWER
What I did was to create a third strategy based on the jwt one:
#Injectable()
export class OwnStrategy extends PassportStrategy(Strategy, 'own') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: SECRET,
passReqToCallback: true
});
}
async validate(req: Request, payload: { sub: string }) {
if (req.params.id !== payload.sub) {
throw new UnauthorizedException();
}
return { userId: payload.sub };
}
}
Note how I pass the custom name 'own' as second parameter of PassportStrategy to differentiate it from the 'jwt' one. Its guard:
#Injectable()
export class OwnAuthGuard extends AuthGuard('own') {}
This works but I wonder if it is the good way of doing it...
What if later I want to able user modification for admin users ?
Should I create a forth strategy which check if role === Role.ADMIN || req.params.id === payload.sub ?
I think I'm missing something. There should be a way to create a strategy which validate only the jwt, another one only the userId, another one only the role, and combine them as I want when applying guards to my routes.
same case. you can use handleRequest method in guard.
here you can access user auth and req, then doing validation for resource appropriate. check out my code
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
handleRequest(err, user, info, context: ExecutionContext) {
const request = context.switchToHttp().getRequest<Request>();
const params = request.params;
if (user.id !== +params.id) {
throw new ForbiddenException();
}
return user;
}
}
look more here https://docs.nestjs.com/security/authentication#extending-guards

I'm using a passport-jwt auth strategy in my nestJS app (with authGuard), how to get access to the token payload in my controller?

I'm trying to get access to the jwt payload in a route that is protected by an AuthGuard.
I'm using passport-jwt and the token payload is the email of the user.
I could achieve this by runing the code bellow:
import {
Controller,
Headers,
Post,
UseGuards,
} from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { AuthGuard } from '#nestjs/passport';
#Post()
#UseGuards(AuthGuard())
async create(#Headers() headers: any) {
Logger.log(this.jwtService.decode(headers.authorization.split(' ')[1]));
}
I want to know if there's a better way to do it?
Your JwtStrategy has a validate method. Here you have access to the JwtPayload. The return value of this method will be attached to the request (by default under the property user). So you can return whatever you need from the payload here:
async validate(payload: JwtPayload) {
// You can fetch additional information if needed
const user = await this.userService.findUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return {user, email: payload.email};
}
And then access it in you controller by injecting the request:
#Post()
#UseGuards(AuthGuard())
async create(#Req() request) {
Logger.log(req.user.email);
}
You can make this more convenient by creating a custom decorator:
import { createParamDecorator } from '#nestjs/common';
export const User = createParamDecorator((data, req) => {
return req.user;
});
and then inject #User instead of #Req.

Inject HttpContext into InversifyJS middleware

I have the following controller.
#controller('/users')
class UsersController {
#httpGet('/', authMiddleware({ role: 'ADMIN' }))
public get() { ... }
}
I have implemented a custom AuthenticationProvider, which returns a principal containing details about the currently authenticated user, including the user's roles.
....
return new Principal({
firstName: "John",
lastName: "Smit",
roles: ["ADMIN"]
});
...
This all works fine, but I am wondering how I can retrieve the principal from the authMiddleware which is used by the above GET route.
For now I have an ugly hack which uses internals of InversifyJS.
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext: interfaces.HttpContext =
Reflect.getMetadata(
"inversify-express-utils:httpcontext",
req
);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
The custom authentication provider uses the authorization header to authenticate the user and returns a principal. I don't want to do this work again in the middleware, I just want to retrieve the principal.
This hack works, but I was wondering if someone knows a cleaner way of obtaining the HttpContext in this middleware.
I know you can access the HttpContext and thus the principal (user) if you extend from the BaseMiddleware, but then it's not clear to me how you pass configuration (parameters) to it, such as the desired role. Related to the following issue on InversifyJS.
https://github.com/inversify/InversifyJS/issues/673
This is not supported, but I can see why it is needed. We cannot pass the httpContext to the middleware as an argument because we want to keep the standard Express middleware compatible. This means that the only option is doing something like what you have done but ideally we should encapsulate it using some helper.
We need to implement something like the following getHttpContext function:
import * as express from "express";
import { getHttpContext } from "inversify-express-utils";
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext = getHttpContext(req);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
Until this is implemented I don't see any problems with your implementation other than the information leakage of the inversify internals.

Resources