Configurando AuthGuard com escopos de usuários em aplicações com NestJS
Esse projeto serve para ilustrar como utilizar a configuração de AuthGuard para cenários de escopo de usuários.
O escopo de usuários, nesse contexto, refere-se às permissões que um usuário tem para acessar determinado recurso. As permissões para as operações CRUD podem ser classificadas em:
Sigla | Descrição |
---|---|
- | Nenhuma permissão necessária |
x | Não possui permissão |
create | Permissão de criação de um recurso |
create_own | Permissão de criação de um recurso que perterncerá ao requerinte |
read | Permissão de de leitura de um ou mais recursos |
read_own | Permissão de leitura de um ou mais recursos que pertençam ao requerinte |
update | Permissão de atualização de um recurso |
update_own | Permissão de atualização de um recurso que pertença ao requerinte |
delete | Permissão de deleção de um recurso |
delete_own | Permissão de deleção de um recurso que pertença ao requerinte |
Nesse projeto, foi desenvolvida uma API bem simples, sem conexão com banco de dados, apenas simulando como seria o gerenciamento de permissões para consumir os endpoints com base nas permissões do usuário. Os endpoints são:
método | endpoint |
---|---|
POST |
/auth |
POST |
/users |
GET |
/users |
GET |
/users/{user_id} |
PUT |
/users/{user_id} |
DELETE |
/users/{user_id} |
As premissas de funcionamento da API são:
- As requisições
POST /auth
ePOST /users
são públicas, ou seja, não precisam de token - A requisição
GET /users
só é permitida para usuário do tipoadmin
, ou seja, usuário do tipouser
não pode acessar - As requisições
(GET|PUT|DELETE) /users/{user_id}
são permitidas para usuários do tipoadmin
e para usuários do tipouser
, porém usuários do tipouser
só podem acessá-las se o acesso for de suas próprias informações.
Logo, as definições de endpoints e permissões são:
path | método | endpoint | user | admin |
---|---|---|---|---|
auth |
POST |
/auth |
- | - |
users |
POST |
/users |
- | - |
users |
GET |
/users |
x | read |
users |
GET |
/users/{user_id} |
read_own | read |
users |
PUT |
/users/{user_id} |
update_own | update |
users |
DELETE |
/users/{user_id} |
delete_own | delete |
A identificação do requerinte no consumo da API é feita através do Bearer Token
. Para obedecer as
premissas dos endpoints e as permissões dos usuários, foram definidos os escopos para cada usuário. Considerando que o
único recurso presente na aplicação é user
, os escopos de cada usuário são:
{
"admin": [
"user:read",
"user:update",
"user:delete"
],
"user": [
"user:read_own",
"user:update_own",
"user:delete_own"
]
}
Logo, o payload do token precisa conter duas informações: o tipo do usuário e o escopo do mesmo. Com isso, o payload do token deve ser:
tipo | payload |
---|---|
admin |
{ "type": "admin", "scopes": ["user:read", "user:update", "user:delete"] } |
user |
{ "type: "user", "scopes": ["user:read_own", "user:update_own", "user:delete_own"] } |
Na API, para o acesso de recursos próprios do usuário do tipo user
, a identificação é feita através do path
do
endpoint e do sub
do token. Ou seja, o user_id
do path
deve ser igual ao sub
do token.
No NestJS, existe um recurso chamado Guards
, que permite com que você realize configurações
de autorização do usuário. Segundo a documentação do NestJS, os Guards
possuem "a responsabilidade de determinar se uma solicitação será tratada ou não pelo handler do endpoint em tempo de
execução, baseado em condições pré-definidas". (Guards | NestJS, disponível em: https://docs.nestjs.com/guards).
A implementação da API foi realizada criando dois recursos: auth
e user
. O recurso de auth
serve para simular
a autenticação de usuário, e o recurso user
serve para simular as requisições de usuários. Os endpoints foram
implementados conforme descrito nas definições citadas anteriormente. Porém:
- O endpoint
POST /auth
, que gera oBearer Token
, apenas recebe o tipo de usuário no body, e retorna o payload utilizado para gerar o token e o token propriamente dito. O payload é composto por três informações:- sub: o id do usuário, que pode ser informado no body ou pode ser gerado automaticamente
- type: o tipo do usuário, obrigatório
- scopes: os escopos do usuário, conforme definido anteriormente
- O endpoint
POST /users
não recebe nenhuma informação no body, apenas retorna um campo id e nome gerados automaticamente - O endpoint
GET /users
recebe apenas um campo de query chamadosize
, que informa a quantidade de elementos a serem retornados. O size deve ser maior que 0 e menor que 10 e, caso não seja informado, tem o valor default 2. É retornada uma lista de objetos do tamanho definido, com id e nomes gerados automaticamente. - Os endpoints
(GET | PUT) /users/{user_id}
retornam um objeto com o id informado nos parâmetros e um nome gerado automaticamente. - O endpoint
DELETE /users/{user_id}
retorna um campodeletedId
com o id informado nos parâmetros
Para configurar o Guard na API, é necessário:
- Definir os escopos de autorização de cada endpoint
- Implementar o Guard de autorização
Para definir os escopos de autorização dos endpoints, iremos utilizar
um Decorator, denominado @AuthScope()
que recebe 1..n
escopos e os
associam ao endpoint em questão. Já a implementaçã do Guard, denominado @AuthorizationGuard()
deverá realizar as
seguintes verificações:
- Se o endpoint possui escopos definidos
- Caso não tenha, significa que a rota é pública, logo nenhuma ação deverá ser feita
- Se a requisição possui token no header
- Caso não tenha, um erro deverá ser retornado para usuário, informado que o token é necessário
- Se o token é válido
- Caso o token seja inválido, um erro deverá ser retornado para usuário, informado que o token não é válido
- Se o usuário possui o escopo requerido para acessar o recurso
- Caso não possua o escopo, um erro deverá ser retornado para o usuário, informando que o mesmo não possui autorização para consumir o endpoint
- Se o endpoint possua algum escopo de acesso próprio (
resource:(create|read|update|delete)_own
)- Se o usuário for do tipo
admin
, o usuário pode consumir o endpoint - Se o usuário for do tipo
user
:- Se o recurso não pertencer ao usuário, um erro deverá ser retornado para o usuário, informando que o mesmo não possui autorização para consumir um endpoint
- Se o usuário for do tipo
Após definir o @AuthScope()
e o @AuthorizationGuard()
, basta:
- Adicionar o
@UseGuards(AuthorizationGuard)
logo após a definição do@Controller()
do recurso que você deseja utilizar osGuards
- Adicionar o
@AuthScope()
em cada método do controller, logo após a deifinção do método HTTP do endpoint (Get(), Post(), Put(), Patch(), Delete(), etc.
)
Os detalhes da implementação do @AuthScope()
e do @AuthorizationGuard()
podem ser verificados nos
arquivos authorization.decorator.ts
e authorization.guard.ts
respectivamente. Para ver a utilização desses recursos no @Controller()
e em seus métodos, acesse o arquivo
do UserController.
Caso queira executar esse projeto em sua máquina, siga os seguintes passos:
Utilize o comando a seguir para clonar o projeto em sua máquina local utilizando o Git:
$ git clone git@github.com:lucasrochagit/permission-check.git
Faça uma cópia do arquivo .env.example
chamada .env
, e defina os valores conforme desejar.
Utilize o comando a seguir para instalar as dependências do projeto:
$ npm install
Para executar a aplicação, utilize um dos comandos a seguir:
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
Para executar os testes da aplicação, utilize um dos comandos a seguir:
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
Para consumir a API com o Postman, importe a collection
e do environment
localizados no diretório.postman
do
projeto.