Impersonation with Nestjs
Tags: nestjs
Published: 2022-01-03 23:58:16

---
title: Impersonation with Nestjs
published: true
date: 2021-07-07 20:29:41 UTC
tags: nestjs
canonical_url: 
---

 ![Impersonation with Nestjs](https://images.unsplash.com/photo-1554497342-902a4f8da8ed?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHdob3xlbnwwfHx8fDE2MjU2ODk2NTU&ixlib=rb-1.2.1&q=80&w=2000)

Originally posted on my blog: https://blog.merckx.fr/impersonation-with-nestjs/

Impersonation is the ability of users to act as other users. Usually it consists in administrators of a system having the ability to interact as standard users on the same system. Gitlab, for instance, provides this feature. It is also part of the feature to provide a way to revert the impersonation.

In this post, we present one approach to implement this feature and we illustrate principles with code from Nestjs.

## Assumptions

We assume you already have some sort of user session implementation: a cookie that either directly contains information (client-side cookie, signed with a secret) about the current user or an identifier that links to such information in a store.

We also assume that a REST API that filters in data that belong to the user.

With Nestjs, we will assume that the request object contains a property called `user` that contains the current information about the user. We will also assume that this property is filled in by a guard, based on the session. We will also assume that the session is implement with a client-side cookie that contains the current user identifier. We will also assume that TypeOrm is used to provide CRUD endpoints and that resources are filtered by user.

```ts
interface CustomRequest extends express.Request {
  user: User;
}


interface CustomSession {
  user {
    id: string;
  }
}


@Crud({
  model: {
    type: ...,
  },
})
@CrudAuth({
  property: 'user',
  filter: (user: User) => {
    return {
      userId: user.id,
    };
  },
})
@Controller('entity')
export class MyEntityController implements CrudController<...> {
 …
}
```

The last assumption is that the Nestjs application has a guard that checks whether the user is authenticated and that adds a `user` property to the request object.

```ts
@Injectable()
export class IsAuthenticatedGuard implements CanActivate {
  constructor(
    @InjectRepository(User) private users: Repository<User>,
  ) {}

  async canActivate(
    context: ExecutionContext,
  ): Promise<boolean> {
     const request = context.switchToHttp().getRequest();
    const session = request.session as CustomSession;
    if (!session.user) {
      return false;
    }
    request.user = await this.users.findOne({ where: { id: session.user.id }});
    return true;
  }
}

```

## Principles

We are going to change as less code as possible but still make pretty explicit what happens. The main idea is to store two pieces of information in the user session:

- the actual logged in user
- the impersonated user

Therefore, we change the session structure to the following:

```ts
interface CustomSession {
  loggedInUser: {
    id: string;
  }
  impersonatedUser: {
    id: string
  }
}
```

We must know provide two API endpoints that enable the user to impersonate and to stop the impersonation.

_The impersonation endpoints_
```ts
  @Get('impersonate')
  removeImpersonation(@Session() session: CustomSession) {
    session.impersonatedUser = session.loggedInUser;
  }

  @Get('users/:id/impersonate')
  async impersonate(@Param('id') id: number, @Session() session: CustomSession) {
    const user = await this.users.findOne({ where: { id }});
    if (!user) {
      throw new NotFoundException();
    }
    session.impersonatedUser = user;
  }
```

Now, when logging in, we must fill in the two properties `session.impersonatedUser` and `session.loggedInUser`. Here is an example:

_The modified login route_
```ts
  @Post('/login')
  async login(@Session() session: CustomSession, @Body('username') username: string, @Body('password') password: string) {
    session.impersonatedUser = null;
    session.loggedInUser = null;
    const user = await this.authService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    session.impersonatedUser = session.loggedInUser = this.serializeUser(user);
    return session.loggedInUser;
  }

```

Our authentication guard must fill in the `request.user` with the impersonated user.  Here is an example:

_The modified authentication guard_
```
  async canActivate(
    context: ExecutionContext,
  ): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const session = request.session as CustomSession;
    if (!session.impersonatedUser) {
      return false;
    }
    request.user = await this.users.findOne({ where: { id: session.impersonatedUser.id }});
    return true;
  }
```

Our authentication fills in the session with the logged in user and the impersonated user information. Our guard fills in the user property with the impersonated user from the user session. Our CRUD controllers can now filter data by user.

Let's walk through the mechanism.

1. The user logs in. A `POST` request is sent to the `login` endpoint. The session is filled in with `impersonatedUser` and `loggedInUser` properties.
2. The user starts impersonating by reaching the `/admin/users/1234/impersonate` endpoint. Once reached, the session is modified: the `impersonatedUserId` is filled in with information about user `1234`.
3. The user reaches a CRUD endpoint, let's say `/entity`. The authentication guard gets the `impersonatedUserId`property from the session and adds a `user` property to the request object based on it.
4. TypeOrm filters the `entity` by the information stored in the `user`property of the request object.
5. The user stops the impersonation by reaching `/admin/impersonate` endpoint. The session is modified: the `impersonatedUserId` is filled in with the `loggedInUserId`.

Do not forget to protect the impersonation endpoint!

That's it! With this approach, we make minimal changes to the code base and we enable a great feature: Impersonation.

* * *

KM

Photo by [Felicia Buitenwerf](https://unsplash.com/@iamfelicia?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/who?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)