Search code examples
nestjspassport.jspassport-local

Is it possible to have multiple local strategies in passport implemented with NestJS


I have a scenario where I need to implement an authentication mechanism for admin and for normal users in my application using the Passport local strategy. I implemented the strategy for the normal users as described here. It is working perfectly fine.

However, now I need to implement the same local strategy for Admin login. I feel like it would have been much easier if both the type of users(admin and normal user) are on the same entity/table because a single validate function would be capable enough to handle the case but my application design has separate entities for Admins and normal users and hence are the separate services.

My local strategy looks something like this:

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
    constructor(private userService: UserService) {
        super();
    }

    async validate(username: string, password: string): Promise<any> {
        const user = await this.userService.validateUser(username, password);
        if (!user) {
            throw new UnauthorizedException("Incorrect credentials!");
        }
        return user;
    }
}

As I went through the documentation, it is said that a Local Strategy can have only one validate function(that works as a verify callback), if this is the case how do I differentiate a logic inside this single validate function to behave differently for the requests coming in from the normal user controller and from the admin controller? Because in the admin login case, I'll be using a different route something like(admin/login), and for the user, it could be something like(user/login).

What's the best approach for this? Do I need to create a separate local strategy for admin? If yes, any hints will be appreciated. Otherwise, how can I incorporate logic inside this single validate function?

One of the alternatives could be checking if data exists in both the tables for every login payload each time. This approach doesn't look quite right to me.

If this provides more insight, the auth guard is simple as this:

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') { 
}

Solution

  • You could make a second strategy based on passport-local but give it a specified name like admin by following the named strategies part of the docs. Something like

    @Injectable()
    export class LocalAdminStrategy extends PassportStrategy(Strategy, 'admin') {
      validate(username: string, password: string) {
        return validateAdminInfo({ username, password });
      }
    }
    

    And now you can use this strategy by using @UseGuards(AuthGuard('admin')) and add several strategies by giving an array of strategies names to AuthGuard like:

    @UseGuards(AuthGuard(['admin', 'user']))
    

    If one of the strategy is passed, the route access will be accepted.