Netstjs - multiple global guards with user access - nestjs

I want to use the guard which will check the database if current user is active on each request. From the research I have done, I can use request.user only if the the guard is not global.
Options I have are:
Register guards on each controller/resolver (downside: duplicate code)
Extract and decode jwt from request.headers (jwt is then decoded twice: once from me and once under the hood (I am using AuthGuard('jwt')))
What is the best (and cleanest) solution for doing the needed filtering? Is there any better option available?
#Injectable()
export class IsUserActiveGuard implements CanActivate {
constructor(
private userService: UsersService) { }
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().req;
const user = request.user;
if (user)
return await this.userService.checkActiveUser(user.id);
return true;
}
}

Move the checkActiveUser method to a new service, maybe some called GuardService and create a global module with GuardService as a provider, then import that global module to the root module (often called AppModule) and that's it. You can use your Guard on your application without importing the Module again.
Inside the guard you can have this code to get the token and payload
const req = context.switchToHttp().getRequest();
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) throw new UnauthorizedException('Null Access Token');
try {
verify(
token,
`${this.config.get<string>('JWT_ACCESS_KEY')}`,
) as IAccess;
req.session = user; //User Session
/* Some extra validation here */
} catch (error) {
throw new ForbiddenException('Invalid Access Token');
}
return true;

Related

Nestjs Interceptor how to catch http 401 error and resubmit original request

I need to write an http header interceptor to add Authorization header, if there is a 401 error, submit another request for a new token, then resubmit the original request with the new token.
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
return next.handle().pipe(
catchError(async error => {
if (error.response.status === 401) {
const originalRequest = error.config;
var authRes = await this.authenticationService.getAccessToken();
this.authenticationService.accessTokenSubject.next(authRes.access_token);
// I need to resubmit the original request with the new token from here
// but return next.handle(originalRequest) doesn't work
}
return throwError(error);
}),
);
}
But next.handle(originalRequest) doesn't work. How to resubmit the original request in the interceptor? Thank you very much in advance for your help.
I just encountered a similar problem, where I can catch the exception from exception filter but can't do so in interception layer.
So I looked up the manual and found it says:
Any exception thrown by a guard will be handled by the exceptions layer
(global exceptions filter and any exceptions filters that are applied to the current context).
So, if the exception is thrown from AuthGuard context(including the validate method in your AuthService), probably better to move the additional logic by extending the Authguard
like this:
export class CustomizedAuthGuard extends AuthGuard('strategy') {
handleRequest(err, user, info, context, status) {
if (err || !user) {
// your logic here
throw err || new UnauthorizedException();
}
return user;
}
}
or simply using customized exception filter.
It's been a while since the question but maybe it will help someone.
Ok, suppose that we need handle unauthorize exception out of route and guards, maybe service to service. So you can implement a interceptor like that and add some logic to get some data if needed, Ex: inject some Service in the interceptor.
So, throw an unauthorize exception and we are going to intercept it:
#Injectable()
export class UnauthorizedInterceptor implements NestInterceptor {
constructor(
private readonly authService: AuthService,
private readonly httpService: HttpService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError((err) => {
const {
response: { status, config },
} = err;
// assuming we have a request body
const jsonData = JSON.parse(config.data);
if (status === HttpStatus.UNAUTHORIZED) {
// We can use some data in payload to find user data
// here for example the user email
if (jsonData?.email) {
return
from(this.authService.getByUserEmail(jsonData.email)).pipe(
switchMap((user: User) => {
if (user) {
// Ex: we can have stored token info in user entity.
// call function to refresh access token and update user data
// with new tokens
return from(this.authService.refreshToken(user)).pipe(
switchMap((updatedUser: User) => {
// now updatedUser have the new accessToken
const { accessToken } = updatedUser;
// set the new token to config (original request)
config.headers['Authorization'] = `Bearer ${accessToken}`;
// and use the underlying Axios instance created by #nestjs/axios
// to resubmit the original request
return of(this.httpService.axiosRef(config));
}),
);
}
}),
);
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
}),
);
}
}

NestJS - how to check if JWT can access specific endpoint

Let's say I have 3 endpoints: A is for generating JWT token, B for accessing data of user X and C for accessing data of user Y. Now, what I want to do is, that I can from recieved token somehow in controller guards figure out, if user can access endpoint.
So, token generated for user X can only access endpoint B, token generated for user Y can only access endpoint C.
Token has to be generated at endpoint A, since users sign in at same form.
If question is unclear ask in comment.
You Can do that by specifying in the payload a role, by this role you set a guard on each endpoint which role has the access to it. let me give an example:
I believe that you have a function where you fill you payload kind of this function :
createJwtPayload(user){
let data: JwtPayload = {
userData: user,
companyId : user.company.id,
role:user.role.name, // for us this where we specify the role for our User
};
......
}
Now We have to create guards we need to specify access for x endpoints
let start with Admin Guard:
#Injectable()
export class AdminGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
if (!request.headers.authorization) {
return false;
}
request.user = await this.validateToken(request.headers.authorization);
if( request.user.role == ROLES.SUPER_ADMIN) {
return true;
}
return false;
}
async validateToken(auth: string) {
......
}
lets make the second guard we call it EmployeGuard :
....
#Injectable()
export class EmployeGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
if (!request.headers.authorization) {
return false;
}
request.user = await this.validateToken(request.headers.authorization);
if( request.user.role == ROLES.COMPANY_ADMIN || request.user.role == ROLES.USER) {
return true;
}
return false;
}
async validateToken(auth: string) {
......
}
Now to use these guards we just need to use #UseGuards() in our endpoint :
#Post()
#UseGuards(AdminGuard)
async addCompany(#Res() res, #Body() createDto: CompanyDto) {
........
}
#Get(':companyID')
#UseGuards(EmployeGuard)
async getcompany(#Res() res, #Param('companyID') companyID) {
....
}
Bonus: you can #useGuards on the controller to make sure the all endpoints use it

How to accomplish HTTP Basic Authentication with NestJS

I am wondering how one can accomplish HTTP Basic Authentication with common NestJS Auth practices.
For example if I use an AuthGuard like this, I get the error
(node:336) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { compareSync } from 'bcrypt';
import { User } from 'src/user/user.entity';
import { Repository } from 'typeorm';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
#InjectRepository(User) private readonly userRepository: Repository<User>,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const b64auth = (request.headers.authorization || '').split(' ')[1] || '';
const [username, password] = Buffer.from(b64auth, 'base64')
.toString()
.split(':');
const user = await this.userRepository.findOne({
where: { username },
});
if (user && compareSync(password, user.password) !== false) {
request.user = user;
return true;
}
const response = context.switchToHttp().getResponse();
response.set('WWW-Authenticate', 'Basic realm="Authentication required."'); // change this
response.status(401).send();
return false;
}
}
I suspect, that returning false (and letting Nest handle the reponse) doesn't play with "manually" setting the response status code to 401 and sending the response.
How can I protect certain routes with this ancient http authorization mechanism?
I would suggest implementing an ExceptionFilter that listens for Exception thrown by the guard specifically (UnauthorizedException). Then, in the filter, set the response as you would like to, that way the guard doesn't try to send multiple responses and you can set up the response as you'd like.

How to execute pipe(ValidateObjectId) before guard(ResourceOwnerGuard)?

Im playing around with nestjs and mongoose.
The code:
class BrevesController {
constructor(private readonly brevesService: BrevesService) { }
// Here is used BreveOwnerGuard(1)
#UseGuards(JwtAuthGuard, BreveOwnerGuard)
#Get(':breveId')
// Here is used ValidateObjectId(3)
async getById(#Param('breveId', ValidateObjectId) id: string) {
return await this.brevesService.getById(id)
}
}
class BreveOwnerGuard {
constructor(private readonly brevesService: BrevesService) { }
async canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest()
const {user, params} = req
const {breveId} = params
// This is executed before ValidateObjectId in getById
// route handler and unknown error is thrown but we
// have pipe for this.(2)
const breve = await this.brevesService.getById(breveId)
const breveCreatorId = breve.creatorId.toString()
const userId = user.id
return breveCreatorId === userId
}
}
So after request /breves/:breveId with invalid object id, the BreveOwnerGuard is executed before ValidateObjectId and unknown error is thrown.
Is there a way for this flow to validate the ObjectId before BreveOwnerGuard ?
Or what should I do in this case? What is expected ?
Guards are executed after each middleware, but before any interceptor or pipe.
Source: Guard Docs (emphasis by me)
Not much you can do other than change the ResourceOwnerGuard to a pipe or the ValidateObjectId into a Guard.

FeathersJS authentication deactivate user

I am using FeathersJS and been happy with authentication it provides. I this case it is local JWT. A client requested user management with an ability to disable some. There is field isDisabled in Users model, but it's hard to figure out where the check should be performed and how to set it up.
"#feathersjs/feathers": "^3.0.2",
"#feathersjs/authentication": "^2.1.0",
"#feathersjs/authentication-jwt": "^1.0.1",
"#feathersjs/authentication-local": "^1.0.2",
It depends where you want to check. You can either customize the JWT verifier or create a hook on the users service for the get method:
app.service('users').hooks({
after: {
get(context) {
const user = context.result;
if(user.isDisabled) {
throw new Error('This user has been disabled');
}
}
}
});
I did this directly in my authenticate hook:
const { authenticate } = require('#feathersjs/authentication').hooks
const { NotAuthenticated } = require('#feathersjs/errors')
const verifyIdentity = authenticate('jwt')
function hasToken(hook) {
if (hook.params.headers == undefined) return false
if (hook.data.accessToken == undefined) return false
return hook.params.headers.authorization || hook.data.accessToken
}
module.exports = async function authenticate(context) {
try {
await verifyIdentity(context)
} catch (error) {
if (error instanceof NotAuthenticated && !hasToken(context)) {
return context
}
}
if (context.params.user && context.params.user.disabled) {
throw new Error('This user has been disabled')
}
return context
}
You see I did check the just loaded user record and throw an error in case. And as this hook is called in before:all the user is rejected before any action is done.
As for feathers 4 you can extend your auth strategies very easily. For example if we want to user only be able to login and verify their JWT we would do the following in authentication.ts (Typescript):
import { Id, Query, ServiceAddons } from '#feathersjs/feathers';
import { AuthenticationService, JWTStrategy } from '#feathersjs/authentication';
import { LocalStrategy } from '#feathersjs/authentication-local';
import { expressOauth } from '#feathersjs/authentication-oauth';
import { Application } from './declarations';
declare module './declarations' {
interface ServiceTypes {
'authentication': AuthenticationService & ServiceAddons<any>;
}
}
Extend the local strategy by alter getEntityQuery to only inlcude users which are active.
class CustomLocalStrategy extends LocalStrategy {
async getEntityQuery(query: Query) {
return {
...query,
active: true,
$limit: 1
};
}
}
Extend the JWT strategy by alter getEntity() to return null if the user is inactive
class CustomJWTStrategy extends JWTStrategy {
async getEntity(id: Id) {
const entity = await this.entityService.get(id);
if (!entity.active) {
return null;
}
return entity;
}
}
export default function(app: Application): void {
const authentication = new AuthenticationService(app);
authentication.register('jwt', new CustomJWTStrategy());
authentication.register('local', new CustomLocalStrategy());
app.use('/authentication', authentication);
app.configure(expressOauth());
}

Resources