Create a multi-tenancy application in Nest.js Part 4 (Authentication and Authorization setup)
Tags: nestjs, microservices, security, typescript
Published: 2022-03-02 17:05:53

## Introduction 
Authentication & Authorization are essential parts of most applications. There are many different approaches and strategies to handle authentication. This article presents the same like production that can be adapted.
 
we will use Nest.js to build the following :
- user need login using /login endpoint
- after login will get token.
- any user has roles and privileges.
- users can just access endpoints allowed their roles and privileges


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61vb2aoq4zvrl952bvpb.jpg)

we will follow these steps :
 - we need multiple databases one of them is the default database what we will do all authentication in it and then thw database for every tenant and will get  Authorization privileges and policies from it
 - in the default database we need to create the `Users` table and `Organization` table that present tenants.
 - in tenant databases will add privileg_group table containing all tenant users' policies.
 - will add an endpoint in the user-service app to manage users and privileges.
 - create Auth module and export it in an external library
 - add guards for JWT, Roles and policies 
  
## Authentication
to add  we will start from models and edit user entity and create needed service methods and controllers
### Create User & Login

#### In user-service app
here in the default database we need a table to save tenants and their Organizations information so create an Organizations entity
```ts
@Table({tableName:'Organizations'})
export class Organizations extends Model<Organizations> {

  @Column({
    type: DataType.STRING,
    allowNull: false,
    unique: true,
  })
  name: string
  @Column({
    type: DataType.STRING,
    allowNull: false,
    unique: true,
  })
  domain: string

  @HasMany(()=>Users,'organizationId')
  users: Users[]
  
}
```

and create a relation in users table by edit user entity
```ts

@Table({tableName:'Users'})
export class Users extends Model<Users> {
    /*....*/
    @ForeignKey(() => Organizations)
    @Column
    organizationId: number;
}
```

now we can create usermodule `users.module.ts` and work in it to export related providers and imports from main user app microservice. 
> when I try to do that by simply removing `SequelizeModule.forFeature` from main module and adding it in  UsersModule I get an error in dependency injection so I need to remove a name from SequelizeModuleAsyncOptions `name: 'development'` from SequelizeModule in the main module.

> for testing in local will edit database connection in sequelize-config-service.ts but in production will implement another story
```ts
/**/
if(this.request.data ){
        const host=this.request.data['host']
        update domain=host?host.split('.'):"127.0.0.1".split('.');
        //...
}
/*...*/
```

now users.module.ts will be
```ts
@Module({
  imports: [
    SequelizeModule.forFeature([Users,Organizations]),
  ],
  controllers: [],
  providers: [],
  exports: [],
})
export class UsersModule {}
```
and edit user-service module

```ts
@Module({
   imports: [
  UsersModule,
  SequelizeModule.forRootAsync({
    imports:[UserConfigModule],
    useExisting: SequelizeConfigService,
  })],
  controllers: [UserServiceController],
  providers: [UserServiceService],
})

export class UserServiceModule {}
```
and now create UserService to add all service methods we will use in endpoints.
UserService contains: 
- dependency inject User and Organizations repository
- method findOne to return username in our case we can use just email 
- methods to create and update Users and Organizations.
- use bcrypt to save passwords
```ts
@Injectable()
export class UsersService {
    constructor(
        @InjectModel(Users)    private readonly userModel: typeof Users,
        @InjectModel(Organizations) private readonly organizationsModel:typeof Organizations) {
    }

    async findOne(username: string): Promise<Users> {
    
      return this.userModel.findOne({where:{email:username},nest: true}) ;
      }    
    
    async create( createUserDto:CreateUserDto):Promise<Users> {
      const hashpassword= await bcrypt.hash(createUserDto.password,10)
      createUserDto.password=hashpassword;
      return this.userModel.create(<Users>createUserDto)
    }

      async findId(id): Promise<Users> {
        return this.userModel.findOne({where:{id:id},raw: true,nest: true}) ;
      }
    
      async createOrg(createAccountDto: CreateAccountDto){
        let {owner,...orgDto} = createAccountDto
        return this.organizationsModel.create(<Organizations>orgDto);
      }
      async getOrg(name: string){
        return this.organizationsModel.findOne({where:{name:name}})
      }  
      async getOrgById(id: any){
        return this.organizationsModel.findByPk(id)
      }
      async findUserById(id: any): Promise<Users> {
        return this.userModel.findOne({
          attributes: {exclude: ['password']},
          where: { id: id },
          nest: true,
        });
      }
      async findUserAll(query:any): Promise<Users[]> {
        if(query.type && query.type=='admin'){
          query.type={[Op.in]:['tenant','admin']}
        }
        console.log('users where',query)
        return this.userModel.findAll({ attributes: {exclude: ['password']}, where:query, nest: true });
      }
      
      async update( id:any,userDto:any) {
        return this.userModel.update(userDto,{where:{id:id}})
      }
}
```

create user controller and will use normal REST controller and also nest microservice pattern @MessagePattern for controllers needed by other microservice like `@MessagePattern('findOne')` 
```ts
@Controller()
export class UsersController {
    constructor(private readonly usersService:UsersService) {}
  @Get('user/:id')
  async getUserById(@Param('id')id:any){
    return this.usersService.findUserById(id);
  }
  
  @Get('user/name/:name')
  async eventGetUserById(@Param('name') username:any):Promise<any>{
    console.log("Logger we arrive",username)
    return this.usersService.findOne(username);
  }
  
  @MessagePattern('findOne')
  async getUserByName(data:any){
    console.log("Logger we arrive",data.username)
    return this.usersService.findOne(data.username);
  }
  @Get('user')
  async getUsers(@Query() query:any){
    return this.usersService.findUserAll(query);
  }
  
  @Put('user/:id')
  async updateUser(@Param('id') id:any,@Body() dto:any){
    return this.usersService.update(id,dto);
  }

  @Post('user')
  async createUser(@Body() userDto:CreateUserDto) {
    return  this.usersService.create(userDto)
   }
}
```

#### New auth-service app
this new service (app) will contain a login endpoint and also will test all authentication & authorization in its controller endpoint

now we create  auth-service 
```bash
nest g app auth-service
```


in the main service will add two methods `login` will just return request body and `getHello` 
> getHello will use it in testing 

```ts
@Injectable()
export class AuthServiceService {
  getHello(): string {
    return 'Hello World!';
  }

  login(user:UserDto):UserDto {
    return user;
  }
}
```
create an endpoint for login in the controller and will let Post request in  path "/login"
```ts

@Controller()
export class AuthServiceController {
  constructor(private readonly authService: AuthServiceService) {}

  @Post('login')
  async login(@Body() user) {
    return this.authService.login(user);
  }
}
```


now edit port in main.ts to available port in my case 3001 and test login 
`npm run start:dev auth-service`

`curl -X post  http://127.0.0.1:3003/user -d '{"firstName":"mhd","lastName":"shajar", "email":"mhd@shajar.com","password":"123456","type":"admin"}'`

`curl -X post  http://127.0.0.1:3001/login -d '{    "username":"mhd@shajar.com",   "password":"123456"}'`

#### Guard, Password and Local Strategy
as we said we will use `password` library to implement authentication so  need install some devendency to use them 
```bash
$ npm i --save @nestjs/passport passport-jwt passport-local
$ npm i --save-dev @types/passport-local
$ npm i --save bcrypt
```

and to implement we create a validation service that has single method called `validateUser` that will return true if the login credential is valid and false else.
to implement, this function we need to call controllers from user-service and will use ClientProxy to send a request to user-service `findOne` endpoint, after return user data will check password and return  true if a valid password for user.
```ts
@Injectable()
export class AuthValidateService {
    constructor(
        @Inject('User_SERVICE') private readonly userServiceClient: ClientProxy
      ) {}

    validateUser(username: string, pass: string): Observable<UserDto>|null {

        return this.userServiceClient.send('findOne',
        {username:username})
        .pipe(map((user:UserDto)=>{ 
          
          if (user && bcrypt.compareSync(pass, user.password)) {
            const { password, ...result } = user;
            return result;
          }
          return null;}),
          catchError(err => {
            console.log('caught  error and return null ', err);
            return  of(null);
        })
          )
      } 
}

```

to use ClientProxy will edit auth-service module and add ClientsModule to access other microservices also will add AuthValidateService as providers 
```ts
@Module({
  imports: [PassportModule,
    ClientsModule.register([
      {
        name: 'User_SERVICE',
        transport: Transport.REDIS,
        options: {
          url: 'redis://localhost:6379',
        },
      },
    ])
  ],
  controllers: [AuthServiceController],
  providers: [AuthServiceService,
    {provide:'AUTH_SERVICE',
    useClass:AuthValidateService
  },
    LocalStrategy, JwtStrategy],
})
export class AuthServiceModule {}
```

now add guards localguards which is just AuthGuard('local') from nest.js password library
```ts
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
}
```


and about adding strategy will use PassportStrategy in our  LocalStrategy and use validateUser from AuthValidateService to validate.

```ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
 
  constructor(@Inject('AUTH_SERVICE') private authService ) {
    super({
      passReqToCallback: true,
    });
  }

  async validate(  request: Request,username: string, password: string): Promise<any> {
    const user = await firstValueFrom(this.authService.validateUser(username, password),{defaultValue:null});
    console.log('username',user);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

```


last edit in "/login" endpoint will add Guards LocalAuthGuard and nest will inject LocalStrategy using names.
```ts
  @UseGuards(LocalAuthGuard)
  @Post('login')
  async login(@Request() req) {
    return this.authService.login(req.user);
  }
```


done and now can test

`curl -X post  http://127.0.0.1:3003/user -d '{"firstName":"ismaeil","lastName":"shajar", "email":"ismaeil@shajar.com","password":"123456","type":"admin"}'`

`curl -X post  http://127.0.0.1:3001/login -d '{    "username":"ismaeil@shajar.com",   "password":"123456"}'`


#### JWT token
and to create and decode token will install ready nest.js library @nestjs/jwt
```bash
$ npm i --save @nestjs/jwt
```


add jwt in main auth-service module
```ts
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: { expiresIn: '6000s' },
    })
```

in auth service update login to return access token
```ts
@Injectable()
export class AuthServiceService {
  constructor(
    private readonly jwtService: JwtService,
  ) {}
  login(user:UserDto):AccessToken {
    const payload = { username: user.firstName,orgId:user.organizationId,sub: user.id};
    return {
      access_token: this.jwtService.sign(payload),
    }
  }
}
```

same as local guard will create JwtAuthGuard which just extends AuthGuard('jwt') from `password` library

```ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
}
```

and also create JwtStrategy extends from PassportStrategy to implement validate function from it.
for more details can check [nest Doc](https://docs.nestjs.com/security/authentication)
```ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
      passReqToCallback: true
    });
  }


  async validate( request: Request,payload: any) {
    const user={ userId: payload.sub, username: payload.username,roles:['admin'] }
    return user;
  }
}
```

finally, add jwtGuard in "/hello" endpoint

```ts
  @Get('hello')
  @UseGuards(JwtAuthGuard)
  getHello(@Request() req){
    console.log("hello",req.user);
    return "Hello";
  }
```

now we can test get http://127.0.0.1:3001/hello and use header Authorization with "Bearer ${token}"


#### Microservice Auth
I try to secure connections between microservices we can add a new function where we call Redis.


now AuthValidateService will be 

```ts
@Injectable()
export class AuthValidateService {
    constructor(
        @Inject('User_SERVICE') private readonly userServiceClient: ClientProxy
      ) {}

    validateUser(username: string, pass: string): Observable<UserDto>|null {

        return this.userServiceClient.send('findOne',
       this.addMicroserviceToken({username:username}))
        .pipe(map((user:UserDto)=>{ 
          
          if (user && bcrypt.compareSync(pass, user.password)) {
            const { password, ...result } = user;
            return result;
          }
          return null;}),
          catchError(err => {
            console.log('caught  error and return null ', err);
            return  of(null);
        })
          )
      
      } 
      
    private addMicroserviceToken(data:any): any{
      data.token=bcrypt.hashSync("secret",10) ;
      return data;
  }
}
```


### Export to the custom library
to use JWT Guard in all our app and not need to rewrite it we can create Library that manages authentication implementation 
#### Collect to AuthModule
we start by creating AuthModule and every module or provider that is just used for authentication will remove it from the main module to the new AuthModule.
now create AuthModule and will use the dynamic module to make it configurable to receive  Validation Service , redis url , jwt security key  and ClientsModule name.
so will import all PassportModule, ClientsModule, JwtModule
and export LocalStrategy, JwtStrategy,JwtModule to use them outside module

```ts
@Module({})
export class AuthModule {
  static register(validationService,options?:{jwtsecret:string,
    userServiceClientProvider:string,
    redisUrl:string

  }): DynamicModule {
    return {
      module: AuthModule,
      imports:[ 
        PassportModule,
        ClientsModule.register([
          {
            name: options.userServiceClientProvider,
            transport: Transport.REDIS,
            options: {
              url: options.redisUrl,
            },
          },
          ]),        
        JwtModule.register({
          secret: options?options.jwtsecret:"secretKey",
          signOptions: { expiresIn: '6000s' },
          })
      ],
      providers: [
        {
        provide:'JWTSECRET',
        useValue:options?options.jwtsecret:"secretKey"
        }
        ,validationService ,LocalStrategy, JwtStrategy],
      exports: [LocalStrategy, JwtStrategy,JwtModule],
    };
  }
}
```

no will edit jwtStratigy to pass security key from outside module

```ts
  constructor(@Inject('JWTSECRET') private jwtsecrit) {
    
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtsecrit,
      passReqToCallback: true
    });
  }
```

#### Clean App Module
remove all not needed modules and export them from AuthModule

module will be 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vb4rkmj8sa5azch49gtw.png)

remove all from auth-service module and will be 
```ts
@Module({
  imports: [
  AuthModule.register(
    {
    provide: 'AUTH_SERVICE',
    useClass:AuthValidateService
    }
  ,
    {
    userServiceClientProvider:'User_SERVICE',
    redisUrl:'redis://localhost:6379',
    jwtsecret:jwtConstants.secret
    }
  )],
  controllers: [AuthServiceController],
  providers: [AuthServiceService],
})
export class AuthServiceModule {}
```
and test again to check every function works.

#### Create Library
create library
`nest g library auth-lib`

and now move all AuthModule to lib and we just edit auth-lib.module.ts and will add will be same like authModule

```ts
@Module({})
export class AuthLibModule {
  static register(validationService,options?:{jwtsecret:string,
    userServiceClientProvider:string,
    redisUrl:string

  }): DynamicModule {
    return {
      module: AuthLibModule,
      imports:[ 
        PassportModule,
        ClientsModule.register([
          {
            name: options.userServiceClientProvider,
            transport: Transport.REDIS,
            options: {
              url: options.redisUrl,
            },
          },
          ]),        
        JwtModule.register({
          secret: options?options.jwtsecret:"secretKey",
          signOptions: { expiresIn: '6000s' },
          })
      ],
      providers: [
        {
        provide:'JWTSECRET',
        useValue:options?options.jwtsecret:"secretKey"
        }
        ,validationService ,LocalStrategy, JwtStrategy],
      exports: [LocalStrategy, JwtStrategy,JwtModule],
    };
  }
}
```

last steps export guards in index.ts

```ts
export * from './auth-lib.module';
export {JwtAuthGuard} from './auth/guards/jwt-auth.guard';
export {LocalAuthGuard} from './auth/guards/local-auth.guard';
```

#### Clean all modules
I remove all authModule, strategies, and guards 
and replace them by the custom library and test everything work 

now everything works 
the library will be 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xlsnav24cxi8fgk68jt5.png) 


## Authentication
I will write nest.js definition "Authorization refers to the process that determines what a user can do. For example, an administrative user is allowed to create, edit, and delete posts. A non-administrative user is only authorized to read the posts."

### RolesGuard / user type
now will start create check for Roles or user types .
I will start directly write needed in lib 
start from basic roles and will use Enum to create .
```ts
export enum Role {
    User = 'user',
    Admin = 'admin',
  }
```
and to pass roles in method will use anotation by create Roles decorator 
```ts
import { SetMetadata } from '@nestjs/common';
import { Role } from './role.enum';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
```
and finally, create RouleGuard with basic verification by implementing CanActivate and using refactor to get @Role annotation set value from method to check user data and required method role 

```ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>(ROLES_KEY, context.getHandler());
    if (!roles) {
         return true;
    }
    const {user} = context.switchToHttp().getRequest();
    return this.matchRoles(roles,user['roles'])
  }
  matchRoles(roles:string[],userRoles:Role[]){
      return roles.filter(role=>!userRoles.includes).length==0;
  }
}
```


### Policies and Privilege groups
Here we have multiple levels of users and we need more advanced RBAC implementation, to do that we need to edit user-service by adding entity save every user policy
First, create privileg-group table in a database 
 ```ts
 @Table({tableName:'Privilege-group'})
export class PrivilegeGroup extends Model<PrivilegeGroup> {

    @Column( {
      unique: true,
        allowNull: false
      })
    groupName: string;

    @Column(DataType.STRING)
    roles: string;//Policy[]

    @BelongsToMany(() => Users, () => GroupUser)
    users: Users[] 

}
 ```
and will use many to many relation by connect two table using groupUser table
 ```ts
 @Table
export class GroupUser extends Model {
  @ForeignKey(() => Users)
  @Column
  userId: number

  @ForeignKey(() => PrivilegeGroup)
  @Column
  privilegeGroupId: number
}
 ```

will create type Policy to recieve authorities json
```ts
import { ActionEnum } from "./action-enum";

export class Policy{
    action: ActionEnum[];
    resources: string;
}
```

and we have Action enum contain all actions like read, write, and delete 
```ts
export enum ActionEnum {
    Manage = 'manage',
    Create = 'create',
    Read = 'read',
    Update = 'update',
    Delete = 'delete',
  }
```

will add new decorator PrivilegService to access privileg-group repository to create and get privilegs 
```ts
@Injectable()
export class PrivilegeService {
    constructor(
        @InjectModel(PrivilegeGroup)    private readonly privilegeModel: typeof PrivilegeGroup,
        @InjectModel(GroupUser) private readonly groupUserModel: typeof GroupUser
    ){}

    getGroupList() { return this.privilegeModel.findAll(); }

    getGroup(id: any) { return this.privilegeModel.findOne({where:{id:id}});}

    async createGroup(createGroup:CreateGroup):Promise<PrivilegeGroup>{
        const createGroupDto=new CreateGroupDto();
        createGroupDto.groupName=createGroup.groupName;
        createGroupDto.roles=JSON.stringify(createGroup.roles);
        return this.privilegeModel.create(<PrivilegeGroup>createGroupDto)
    }

    async addUserGroup(createGroupUserDto: CreateGroupUserDto[]):Promise<GroupUser[]>{
        return this.groupUserModel.bulkCreate(createGroupUserDto);
    }

    async deleteUserGroup(userId: any,privilegeGroupId:any) {
        return this.groupUserModel.destroy({where:{userId:userId,privilegeGroupId:privilegeGroupId}})
      }
    getUserGroup(id: any) {
        return this.groupUserModel.findAll({where:{userId:id}})
      }

}

```
and create endpoints in privileg controller
```ts
@Controller("privilege")
export class PrivilegeController {
    constructor(private readonly privilegeService:PrivilegeService){}

    @Get('/')
    getGroupList() { return this.privilegeService.getGroupList()}
    @Get('/:id')
    getGroup(@Param('id')id: number) { return this.privilegeService.getGroup(id);}

    @Post('/add')
    async createGroup(@Body() createGroup:CreateGroup){
        return this.privilegeService.createGroup(createGroup)
    }
    @Post('/group-users/save')
    async addUserGroup(@Body() createGroupUserDtoList: CreateGroupUserDto[]){
        return this.privilegeService.addUserGroup(createGroupUserDtoList);
    }
    @Delete("/group-users/delete")
    async deleteUserGroup(@Query('userId') userId: number,@Query('privilegeGroupId') privilegeGroupId:number) {
        return this.privilegeService.deleteUserGroup(userId,privilegeGroupId);
      }
    

    @Get('/group-users/user/:userId')
    getUserGroup(@Param('userId')userId: number) {
        return this.privilegeService.getUserGroup(userId);
      }
}
```

last will edit user-service to get user will privileges 
```ts
@Injectable()
export class UsersService {
/// the rest..
    async findOne(username: string): Promise<Users> {
      return this.userModel.findOne({include:[{model:PrivilegeGroup , attributes:["roles"],through: {attributes: []}}],where:{email:username},nest: true}) ;
    }    
  /// the rest..
  }
```

#### Edit JWT Token 
 and roles in token  add authorities in token
##### use decorator and guard
custom decorator 

```ts
import { SetMetadata } from "@nestjs/common";
import { Policy } from "./policy";

export const CHECK_POLICIES_KEY = 'check_policy';
export const CheckPolicies = (...policy:Policy[]) =>
  SetMetadata(CHECK_POLICIES_KEY, policy);
```

policies Guards

```ts
@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private reflector: Reflector
  ) {}
  canActivate(context: ExecutionContext): boolean {

    const policies =this.reflector.getAllAndOverride<Policy[]>(
      CHECK_POLICIES_KEY,
      [context.getHandler(),context.getClass()]) || [];

  const {user} = context.switchToHttp().getRequest();
    
  try{
      const isAuthUser = this.checkPolicies(user['policies'].map(policy => JSON.parse(policy)), policies);
      return isAuthUser;
    }catch(err){
      return false;
    }
  }


  private checkPolicies(userPolicies: Policy[], policies: Policy[]) {
    const isAuthUser = policies.filter(resourcePolicy => {
      return userPolicies.filter(
        policy => this.isAutherize(resourcePolicy, policy)
      ).length >= 1;
    }).length;
    return isAuthUser>=1;
  }

  private isAutherize(resourcePolicy: Policy,  policy: Policy): unknown {
    return this.hasResource(resourcePolicy, policy.resources) && this.hasAction(resourcePolicy, policy);
  }

  private hasResource(resourcePolicy: Policy, resource: string) {
    return resource=='*' || resourcePolicy.resources.indexOf(resource) == 0;
  }

  private hasAction(resourcePolicy: Policy, policy: Policy): unknown {
    return resourcePolicy.action.filter(action => !policy.action.includes(action)).length == 0;
  }
}

```







### Test
simple test in auth-service controller will add endpoint called hello
```ts
  @Get('hello')
  @Roles(Role.Admin)
  @UseGuards(JwtAuthGuard,RolesGuard,PoliciesGuard)
  @CheckPolicies({action:[ActionEnum.Read],resources:'hello'})
  getHello(@Request() req){
    return "Hello";
  }
```
in UseGuards we can add multiple guards to execute them in orders so we can pass variables between guards using context request
 not to test we run /login then call get request /hello

## Conclusion
final version is [Github](https://github.com/ismaeil-shajar/multitenancy.git) roles-policies branch