How can we access decorator from a service class in NestJS - authorization

I am new in NestJS and trying to do auth system. I was able to do. So here is what I am doing to get access to auth.
In my controller I have
#Get('/user')
async getUser(#AuthUser() token: string) : Promise<Object> {
return this.authService.getUser(token)
return token
}
Here I am passing a AuthUser decorator I want to avoid passing in controllers.
In the authService.getUser method I have something like this
async getUser(token: string): Promise<Object> {
try {
const user = await this.jwtService.verifyAsync(token)
return user
} catch (error) {
return false
}
}
and my decorator looks like this
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const AuthUser = createParamDecorator(
(data = 'u_ses', ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return data ? request.cookies?.[data] : request.cookies;
},
);
I don't like code. If I need to know the user id from a service class or anywhere I would need to pass the token and to get token I need use #AuthUser() token: string)
So I want to do something like this
this.authService.getUser(), here I don't want to pass token or anything and should be able to access this getUser method from anywhere. Since it's a service class, I can inject and use it but I won't have the token.
I tried injecting the decorator inside the service class, but this doesn't work.
One best solution I would prefer is to use the JWT things inside the decorator, so I don't need the service class' method :)
I am looking for a nicer solutions from you :)
Thank you.

Nestjs has NestMiddleware. Here, you can authorize before access to controller like this:
import { Injectable, NestMiddleware, UnauthorizedException } from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const headerAuthentication = req.headers.authorization;
if(!headerAuthentication) throw new UnauthorizedException('Authorization failed!');
const token = req.headers.authorization.split(' ')[1];
if(token) {
next();
}else {
throw new UnauthorizedException('Authorization failed!');
}
}
}
and in AppModule implement it
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthenticationMiddleware).forRoutes('/');
}
}

Related

How can I return UUID using RabbitMQ

I would like to return the UUID(v4) created for each request in nestjs as a response to the request.
I want to return the UUID(v4) that nestjs creates for each request as the response to the request.
However, I am using rabbitmq.
Is there any other way to return the UUID after going through rabbitmq for each request?
With this method, I am creating a connection for each request.
I want to create the connection only once after nestjs is started.
Also, any other method using other libraries would be good.
import { Controller, Get } from '#nestjs/common';
import { v4 as uuidv4 } from 'uuid';
#Controller('cats')
export class CatsController {
#Get()
findAll(): string {
const sequence = uuidv4(); // I want to return this.
return 'This action returns all cats';
}
}
...
await channel.consume(queueName, async (data: any) => {
if (queueName === 'testQueue') {
// do something.
}
});

How are Guards and Strategies exactly working under the hood in Nestjs?

I am new to Nestjs and I am using guards, strategies and passport for authentification.
I don't understand what's going on under the hood.
I have a guard for a refreshToken mutation:
import { AuthGuard } from '#nestjs/passport';
import { ExecutionContext } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
export class RtGuard extends AuthGuard('jwt-refresh') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
console.log('REFRESH');
return ctx.getContext().req;
}
}
What does this guard exactly do? Somehow it calls my strategy right? But it only does it, if I provide a correct refreshToken.
This is my Strategy:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, ForbiddenException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { JwtPayloadWithRt } from '../types/jwtPayloadWithRt.type';
import { JwtPayload } from 'src/auth/types/jwtPayload.type';
import { Request } from 'express';
#Injectable()
export class RefreshTokenStrategy extends PassportStrategy(
Strategy,
'jwt-refresh',
) {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.get<string>('REFRESH_TOKEN_SECRET'),
passReqToCallback: true,
});
}
validate(req: Request, payload: JwtPayload): JwtPayloadWithRt {
const refreshToken = req.get('authorization')?.replace('Bearer', '').trim();
if (!refreshToken) throw new ForbiddenException('Refresh token malformed');
return {
...payload,
refreshToken,
};
}
}
How is the guard able to decide whether my refresh token is the one in my database and if so, then it calls my strategy?
If I use a wrong refreshToken, not the one I got when I signed in, I get this error:
when providing the correct key, I get this:
Using console.log, I can see that my strategy is not called, whenever the refreshtoken is invalid.
How does the validation work exactly? How do guard and strategy work together under the hood?
Thanks a lot for help!
Named strategies
When implementing a strategy, you can provide a name for it by passing a second argument to the PassportStrategy function. If you don't do this, each strategy will have a default name
(e.g., 'jwt' for jwt-strategy):
export class JwtStrategy extends PassportStrategy (Strategy, 'myjwt')
Default name !== class name. The default name is imposed by the strategy you use.
For example, the default strategy for passport-jwt https://github.com/mikenicholson/passport-jwt is jwt
Source:
https://github.com/nestjs/nest/issues/4753#issuecomment-625675342
About how the guard is able to decide whether the token is in the database:
It doesn't. It just verifies that is valid, it is done using the secret key, which has to be the same that you signed the token with in the beggining. If it isn't valid it will throw a ForbiddenException thus the application never reaches the console.log('REFRESH') part of your code.
You could validate that it is in the db by your self, that's what the validate() method could be used for.
A quote from the nestjs docs:
It's also worth pointing out that this approach leaves us room
('hooks' as it were) to inject other business logic into the process.
For example, we could do a database lookup in our validate() method to
extract more information about the user, resulting in a more enriched
user object being available in our Request. This is also the place we
may decide to do further token validation, such as looking up the
userId in a list of revoked tokens, enabling us to perform token
revocation. The model we've implemented here in our sample code is a
fast, "stateless JWT" model, where each API call is immediately
authorized based on the presence of a valid JWT, and a small bit of
information about the requester (its userId and username) is available
in our Request pipeline.
Source: https://docs.nestjs.com/security/authentication#implementing-passport-jwt
validate(req: Request, payload: JwtPayload): JwtPayloadWithRt {
const refreshToken = req.get('authorization')?.replace('Bearer', '').trim();
if (!refreshToken) throw new ForbiddenException('Refresh token malformed');
/*
Perform database checks in this part of your code
*/
//Whatever you return here gets attached to the Request object as `req.user`,
//you can change it to whatever you want
return {
...payload,
refreshToken,
};
}

Get user data in RoleGuard with passport JWT authentication

I've managed to get JWT authentication in my nestJS application.
Now I want to implement the role guard and have therefore to check the role of the authenticated user.
Therefore, I thought of requesting the respective user role from the database. But this call is async and this is not doable within the guard.
My question is:
How can I get the user role information within the Guard?
I could put the information in the JWT token, but this seems not right to me, or am I wrong?
Here, Implementing Passport JWT you can put your findUser in the validate function that is async. And then create a decorator to return the user Auth JWT in decorator in NESTJS
So you need to do some things like this
//jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { jwtConstants } from './constants';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
// Your JWT payload
// Insert here the findOne from you're BDD
return { userId: payload.sub, username: payload.username };
}
}
And then
//user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator((data: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
});
And in your controller juste use
//user.controller.ts
import { User } from './user.decorator';
#Get()
async getUser(#User() user) {
//console.log(user);
}

i18n-backend with oauth2 authorization in spartacus

We want to use a backend for i18n in spartacus. Unfortunately this backend needs an oauth2 authentication but spartacus does not send a bearer token when trying to access this webservice endpoint and we get a 401 error. Is there anything we can do?
Right now we try to solve this problem in this way:
What we need to have is implemented in ClientTokenInterceptor, so we adapted this interceptor, changed the if-clause a little bit so it fits to the backend-url for this webservices and provide the interceptor via app.module.ts which works so far. Unfortunately calling this.authService.getClientToken() in our Interceptor returns no token.
constructor(
private authService: AuthService,
private occEndpoints: OccEndpointsService
) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return this.getClientToken(request).pipe(
take(1),
switchMap((token: ClientToken) => {
if (
token &&
request.url.includes("i18n")
) {
request = request.clone({
setHeaders: {
Authorization: `${token.token_type} ${token.access_token}`,
},
});
}
return next.handle(request);
})
);
}
private getClientToken(request: HttpRequest<any>): Observable<ClientToken> {
if (
InterceptorUtil.getInterceptorParam(USE_CLIENT_TOKEN, request.headers)
) {
return this.authService.getClientToken();
}
return of(null);
}
What do we miss?
Actually there are couple of things not needed in your solution.
I pasted below what I did instead and tested that it is working correctly (and you can see authorization data in the translation files requests).
First issue:
InterceptorUtil.getInterceptorParam(USE_CLIENT_TOKEN, request.headers) you don't need to check that. If you always need the auth data for translation requests just use return this.authService.getClientToken();
Second issue:
In intercept method you didn't cover cases for any other request than translation. Because of that the request for the client token would hang here, because it would wait for token and so on. If you add option for any other case than i18n it starts working as you intend.
Working solution:
#Injectable({ providedIn: 'root' })
export class TranslationsInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (request?.url?.includes('i18n')) {
return this.getClientToken().pipe(
take(1),
switchMap((token: ClientToken) => {
if (token) {
request = request.clone({
setHeaders: {
Authorization: `${token.token_type} ${token.access_token}`,
},
});
}
return next.handle(request);
})
);
} else {
return next.handle(request);
}
}
private getClientToken(): Observable<ClientToken> {
return this.authService.getClientToken();
}
}

Customise the response on verification failure for a jwt Strategy NestJs

I successfully implemented a jwt strategy for authentication using nestJs.
Below is the code for the jwt strategy
import { ServerResponse } from './../helpers/serverResponse.helper';
import { Injectable, UnauthorizedException, HttpStatus } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { config as env } from 'dotenv';
import { Bugsnag } from '../helpers/bugsnag.helper';
env();
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
private readonly logger: Bugsnag,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET_KEY,
passReqToCallback: true,
});
}
async validate(payload, done: Function) {
try {
const validClaims = await this.authService.verifyTokenClaims(payload);
if (!validClaims)
return done(new UnauthorizedException('invalid token claims'), false);
done(null, payload);
} catch (err) {
this.logger.notify(err);
return ServerResponse.throwError({
success: false,
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: 'JwtStrategy class, validate function',
errors: [err],
});
}
}
}
I saw here that the validate function will be called only when a valid token was provided in the request headers and I'm okay with that. However, I would like to know if it is possible to customize the response object which is sent in that case (invalid token provided).
If yes, how do I do that ?
You can use a exception filter to catch UnauthorizedExceptions and modify the response there if you'd like. The other option would be extending the AuthGuard('jwt') mixin class and adding in some logic around a try/catch for the super.canActivate(context), then in the error read what the reason is and throw a specific UnauthorizedException with your custom message
You can use the AuthGuard('jwt')'s handleRequest method to throw any exception on JWT Validation failure.
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err: any, user: any, info: any, context: any, status: any) {
if (info instanceof JsonWebTokenError) {
throw new UnauthorizedException('Invalid Token!');
}
return super.handleRequest(err, user, info, context, status);
}
}
JsonWebTokenError comes from jsonwebtoken library, which is used internally by passport.