For fun programmer
For fun programmer

Reputation: 77

Bypassing BearerStrategy in Nestjs Guard and Strategy

I have the following Nestjs global guard for my app:

import { ExecutionContext, Injectable } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class LoginGuard extends AuthGuard(
    ["azure-ad"]
) {
    public constructor(private readonly reflector: Reflector) {
        super();
    }

    async canActivate(context: ExecutionContext) {
        const isPublic = this.reflector.get<boolean>(
            "isPublic",
            context.getHandler()
        );

        if (isPublic) {
            return true;
        }
        
        const result = (await super.canActivate(context)) as boolean;
        const request = context.switchToHttp().getRequest();
        await super.logIn(request);

        return result;
    }
}

And the following strategy for auth:

import {
  BearerStrategy,
  IBearerStrategyOption,
  ITokenPayload,
  VerifyCallback,
} from "passport-azure-ad"
import {
  Inject,
  Injectable,
  OnModuleInit,
  UnauthorizedException,
} from "@nestjs/common"
import passport = require("passport")
import env from "../../../config"
import { User } from "modules/users/users.interface"
import { UsersService } from "modules/users/users.service"

const tenantId = env.TENANT_ID
const clientID = env.CLIENT_ID || ""

const azureCredentials: IBearerStrategyOption = {
  identityMetadata: `https://login.microsoftonline.com/${tenantId}/v2.0/.well-known/openid-configuration`,
  clientID,
  validateIssuer: true,
  issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
  audience: clientID,
}

@Injectable()
export class AzureAdStrategy extends BearerStrategy implements OnModuleInit {
  onModuleInit() {
    passport.use("azure-ad", this)
  }

  constructor(@Inject(UsersService) private usersService: UsersService) {
    super(
      azureCredentials,
      async (token: ITokenPayload, done: VerifyCallback) => {
        if (Date.now() / 1000 > token.exp) {
          return done(new UnauthorizedException("access token is expired"))
        }

        const tokenUsername = token?.preferred_username?.slice(0, 9)
        const tokenAppId = !tokenUsername && token?.azp

        if (!tokenUsername && !tokenAppId) {
          return done(new UnauthorizedException("Missing User"))
        }

        let user: User

        if (tokenUsername) {
          try {
            user = await this.usersService.getUser(tokenUsername)
            if (!user) {
              return done(new UnauthorizedException("User is not recognized"))
            }
          } catch (err) {
            return done(err)
          }
        }
        return done(null, user, token)
      },
    )
  }
}

This works great. I need to have a backdoor logic for my application, so it bypasses the azureAD authorization if a certain header is sent in the request, lets say req.is_bypass

What's the best way to achive that? is it even possible or do i need to use a different logic for backdoor auth?

Thanks!

Upvotes: 0

Views: 536

Answers (1)

Jay McDoniel
Jay McDoniel

Reputation: 70510

In your LoginGuard's canActivate you have access to the ExecutionContext object, which can be used to get the current request. After your check of the isPublic metadata you can add the check you're looking to do

import { ExecutionContext, Injectable } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class LoginGuard extends AuthGuard(
    ["azure-ad"]
) {
    public constructor(private readonly reflector: Reflector) {
        super();
    }

    async canActivate(context: ExecutionContext) {
        const isPublic = this.reflector.get<boolean>(
            "isPublic",
            context.getHandler()
        );

        if (isPublic) {
            return true;
        }

        const req = context.switchToHttp().getRequest()
        if (req.is_bypass) {
          return true;
        }
        
        const result = (await super.canActivate(context)) as boolean;
        const request = context.switchToHttp().getRequest();
        await super.logIn(request);

        return result;
    }
}

Upvotes: 1

Related Questions