There was no answer to the question I left before, so I'm writing again.
Please understand.
I am making a project using nestjs.
I want to prevent users with a specific role in all controllers except for one controller.
I know, add #UseGuard(RoleGuard) #Role(UserRole.Guest) to each controller.
But I have a lot of controllers, and more will be added over and over again.
Is there a way to do it all at once like middleware?
thank you!
NestJS authentication provides different mechanisms like:
Extending guards
Enable authentication globally
For your specific use case, with the first mechanism, you can extend your auth guard to check if the user has a specific role.
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
handleRequest(err, user, info) {
if (err || !user) { <------------ You can check not only the user's existence but also the role here.
throw err || new UnauthorizedException();
}
return user;
}
}
And with the second mechanism, you can define the auth guard globally in any one of your modules so that you don't have to add it over and over again in controllers.
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
Note that if you expect the different controllers to be accessed with different user roles then you have to define the specific roles guard in the controllers themselves.
Related
I'm learning NestJS and now I'm working in a simple authentication application, at this point I configured global pipes to validations and I'm using dto classes for example to validate #Body() fields. I don't know if I can use DTO to validate #Request fields sent from login endpoint.
import { Controller, Post, Request, UseGuards } from '#nestjs/common';
import { AuthService } from './auth.service';
import { AuthGuard } from '#nestjs/passport';
#Controller()
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(AuthGuard('local'))
#Post('auth/login')
async login(#Request() req: reqDto /* I would like to use DTO for validations*/) {
return this.authService.login(req.user);
}
}
PS: I'm using DTO to validate SingUp body In UserController.
#Post('/signup')
createUser(#Body() createUserDto: CreateUserDto) {
return this.userService.createUser(createUserDto);
}
#Request() and #Headers() are two decorators that skip validation via pipes. You can make a custom request decorator that does get called via pipes, but annotating the request object would be a lot of work. What would be better is to create a decorator that gets just the data you need off the request, and then make a DTO for that object and validate that as necessary
I have a UseGuard in my WebSocket. Actually, this guard is a JwtAuthGuard that extends AuthGuard('jwt'). The JwtAuthGuard has a Strategy class called JwtStrategy. In this class, I have a validate method. In HTTP-based requests I return payload in this method. Then nestjs attach the payload to the req. Here is my Strategy class:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private authConfigService: AuthConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: authConfigService.JWT_SECRET,
});
}
async validate(payload: any) {
return payload;
}
}
I want to have access to context within validate method in order to attach the payload to the WebSocket's body (or anything that I can have access to the payload). Any idea?
You don't need to make any modifications to your strategy class. Instead, you should modify your JwtAuthGuard's getRequest method (if you don't have one then you should make one) that returns an object that has a headers proeprty that is an object with a authorization property that is a string. Something like
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ws = context.switchToWs().getClient(); // possibly `getData()` instead.
return {
headers: {
authorization: valueFromWs(ws),
}
}
}
}
You can also make this work across different context types by using an if/else or a switch statement and returning the correct object based on the contextType from context.getType(). Whatever is returned from this getRequest method is where passport will end up attaching the user property, so it may make sense to return the entire client with these extra values.
I want to save each request (path, method, and userId) that comes to the server without having to hit the database twice, and also without messing up the main logic in services files with transactions.
Initially, I was trying to use an interceptor because it gets invoked after auth guards "which attaches the user to request" and before request handlers, but I faced two issues.
first, the fact that the interceptor will call the database to save a new record and then forward the request to handlers which will again hit DB again to handle the request. Secondly, It didn't work anyway because of dependancy injection problems.
code below is not working due to dependency errors as I mentioned, but It will give u an idea about what I need to acheive.
import { Injectable,
NestInterceptor,
Inject,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus } from '#nestjs/common';
import { Observable } from 'rxjs';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { HistoryEntity } from '../../modules/history/history.entity';
#Injectable()
export class HistoryInterceptor implements NestInterceptor {
constructor(
#Inject(getRepositoryToken(HistoryEntity))
private readonly historyRepo: Repository<HistoryEntity>
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { user, path, method } = request
if (!user) {
throw new HttpException('something terrible happened!', HttpStatus.BAD_GATEWAY);
}
const history = this.historyRepo.create({
path,
userId: user.id,
});
this.historyRepo.save(history);
return next.handle();
}
}
PS. from a performance point of view, It would also be great to not halt the request execution to save these info in db, in other words, Is it ok to NOT use await in this particular situation? because essecntially this is a system related operation and so, Node [rocess shouldn't wait for this step to process and return response to client.
Thanks in advance.
I downloaded the 19-auth sample and add some console.log debug code to it, then found some problems.
The code in JwtAuthGuard is never executed: '2222222' was not printed to the console in the code below:
canActivate(context: ExecutionContext) {
console.log('22222222222');
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
When I changed the guard to JwtAuthGuard in the AuthController:
#get('data')
#UseGuards(JwtAuthGuard)
findAll(#Req() req) {
return req.user;
// this route is restricted by AuthGuard
// JWT strategy
}
the code in JwtAuthGuard was invoked, but in the canActivate function, I can't get the user info from request. and the canActivate function was called before the JwtStrategy?
Can someone explain how the code executing for the auth module, and how to get the user info in the JwtAuthGuard?
paste the latest code and console log here:
JwtStrategy
/**
* jwt passport 调用validate方法来判断是否授权用户进行接口调用
* #param payload
*/
async validate(payload: AuthPayload) {
Logger.log(`payload is ${JSON.stringify(payload)}`, 'JwtStrategy');
const user = await this.authService.validateUser(payload.id);
if (!user) {
throw new UnauthorizedException('不存在的用户信息');
}
return user;
}
JwtAuthGuard
canActivate(context: ExecutionContext) {
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
// this.accessPriv = this.reflector.get<string>('accessPriv', context.getHandler());
console.log('canActivate executed 111111111111111111');
return super.canActivate(context);
}
and the console log as below:
canActivate executed 111111111111111111
[Nest] 14080 - 2019-04-01 11:19 [JwtStrategy] payload is {"userName":"fanliang","id":"1","iat":1553772641,"exp":1554377441} +2286ms
it seems that the canActivate() function of JwtAuthGuard executed before the validate() function of JwtStrategy, but the user info was attached to the request after JwtStrategy validate().
what I want is to get the user info from request in the canActivate() of custom AuthGuard such like JwtAuthGuard
I have a somewhat solution that works for me.
Calling the super.canActivate before my own logic.
seems like the population of req.user triggered by it.
An example:
import { ExecutionContext, Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
import { Request } from "express";
#Injectable()
export class AuthGuardWithAllowSentry extends AuthGuard("jwt") {
public async canActivate(context: ExecutionContext) {
// that code will call the passport jwt
const origCanActivate = await super.canActivate(context);
// now we have request.user!
const http = context.switchToHttp();
const request = http.getRequest<Request>();
console.log(request.user)
if (request.header("X-Sentry-Token") === "blablabla") {
if (request.method === "GET" && request.path.endsWith(".map")) {
return true;
}
}
// some random logic
return request.user.roles.includes("admin")
}
}
it feels for me more like a workaround than a real thing.
I agree that the 19-auth sample is a little bit confusing to follow. This is mainly because it includes the JWTAuthGuard (as a reference for building custom guards) but it is never actually used. Instead, the original use of plain AuthGuard is already set up to provide JWT functionality. However, both guards leverage the JWTStrategy. If you want to understand this better, you could try updating your AuthController:
#Get('data')
#UseGuards(AuthGuard())
findAll() {
// this route is restricted by AuthGuard
// JWT strategy
return {
message: 'Successfully passed AuthGuard',
};
}
#Get('custom-jwt')
#UseGuards(new JwtAuthGuard())
// this route is restricted by JWTAuthGuard custom
// JWT strategy
customJwt() {
return {
message: 'Successfully passed JWTAuthGuard',
};
}
The important part is that in order to get past either guard, you must send the request with the Authorization header properly set to the token that's returned from the token endpoint.
For example: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIiwiaWF0IjoxNTU0MDUyNDczLCJleHAiOjE1NTQwNTYwNzN9.3Q8_FC-qFXk1F4KmMrHVSmmNGPAyHdt2myr5c18_E-U
I find it easiest to use a tool like Postman or Insomnia for constructing requests and setting Headers, etc but you could also use CURL. Once you've set the Authorization header with a valid token you'll be able to hit both of the guarded endpoints. If you put a console.log in the JWTStrategy you'll see that both guards end up using the validate method to retrieve the user correctly.
For my next project I would like to use Graphql inside the FrontEnd. Furthermore this project should also offer a Rest-Api.
Now I have discovered this extremely great framework "nestjs", where it is theoretically possible to combine a Graphql endpoint and a rest endpoint.
Unfortunately I can't find anything in the documentation if this can lead to problems. Is the following code usable without problems?
Artikel controller:
#Controller('article')
#Resolver('Article')
export class ArticleController {
constructor(private articleService: ArticleService){}
#Get()
#Query(returns => CArticle)
async Article() {
const dbElement=await this.articleService.getById("xy");
return dbElement;
}
}
Article module:
#Module({
controllers:[ArticleController],
providers:[ArticleService,ArticleController]
})
export class ArticleModule {}
As this would work in this specific example, it may not work well in other use cases you may reach.
In my experience with using both Rest and gRPC - there are eventually different things that the controllers need to take care of.
I would highly recommend having dedicated controllers/resolvers for each API - with these taking care of API entry (i.e. authenticating, taking care of context) and having the Business Logic in a separate Provider.
So your example would look like so:
Article.controller.ts:
#Controller('article')
export class ArticleController {
constructor(private articleService: ArticleService){}
#Get()
async Article() {
return this.articleService.getById("xy");
}
}
Article.resolver.ts:
#Resolver('Article')
export class ArticleResolver {
constructor(private articleService: ArticleService){}
#Query(returns => CArticle)
async Article() {
return this.articleService.getById("xy");
}
}
Article.module.ts:
#Module({
controllers:[ArticleController, ArticleResolver],
providers:[ArticleService]
})
export class ArticleModule {}
As I understand nestjs now, there should be no problems, as the decorators do not change the code but only create new code.