# Como banir e desbanir usuários do seu app, e fazer o banimento valer

Bloquear o login é a metade fácil. Um banimento que não encerra as sessões já abertas, ou que a pessoa banida consegue desfazer, não é um banimento.

URL: https://www.back4app.com/blog/pt/como-banir-e-desbanir-usuarios-do-seu-app
Publicado: 2026-11-01 · Testado: 2026-09-24
Testado em: Backend: Back4app, plano Free, USA East · Cliente: REST API só com a application key e a REST key · Terminal: curl e node no macOS
Publisher: Back4app Engineering

Alguém está abusando do seu app e precisa sair. Não advertido, não limitado por rate limiting: fora, hoje, sem apagar a conta e sem esperar um deploy.

A resposta usual é uma flag no registro do usuário e uma verificação no login, e esse é o formato certo. Também é, sozinha, mais ou menos metade de um banimento: a pessoa que você acabou de banir muito provavelmente continua logada no celular, e nada do que você fez afeta isso.

Este guia constrói a flag e a verificação de login e depois testa as duas coisas que decidem se o banimento é real: o que acontece com uma sessão que já existe e se o usuário banido consegue simplesmente virar a flag de volta. As duas foram medidas em 24 de setembro de 2026, com chaves de cliente, do jeito que um dispositivo real faria.

![Arquitetura: as três rotas que uma conta banida pode usar para entrar no backend, e o trigger de Cloud Code que fecha cada uma](/blog/blog-assets/ban-and-unban-users-from-your-app/architecture-diagram.svg)

## Onde fica a flag de banimento?

No registro do usuário, como um booleano. Abra a classe `_User` no *Database Browser*, adicione uma coluna chamada `isBanned` do tipo **Boolean** e deixe o padrão em false, para que contas novas comecem permitidas.

Esse é o modelo de dados inteiro. Nenhuma tabela separada de banimentos, nenhum join: a flag viaja com a conta, então toda verificação que tem o usuário tem a resposta.

![A tabela _User com uma coluna isBanned lendo True para uma conta, undefined para outra e False para a terceira](/blog/blog-assets/ban-and-unban-users-from-your-app/hero.jpg)

## Como bloquear o login?

Com um trigger `beforeLogin`, que roda *antes* de o session token ser emitido. Esse timing é o ponto: uma conta banida nunca recebe um token, então não há nada para revogar.

```js
Parse.Cloud.beforeLogin(async (request) => {
  if (request.object.get('isBanned')) {
    throw new Error('This user has been banned');
  }
});
```

Faça o deploy disso e a flag passa a funcionar na hora. O login de uma conta marcada volta recusado, com a mensagem do trigger:

```json
{ "code": 141, "error": "This user has been banned" }
```

Isso precisa viver no Cloud Code, não no seu app. Uma verificação no seu próprio código roda num dispositivo que o usuário controla, e qualquer pessoa pode chamar a REST API diretamente com as mesmas chaves de aplicação e de cliente. O trigger roda no servidor, seja qual for o cliente.

## Bloquear o login realmente remove o usuário?

Não, e essa brecha é o motivo de este guia continuar. Um banimento que só roda no login não faz nada com as pessoas que já estão dentro.

Eis o teste. Faça login como o usuário e guarde o session token. Depois defina `isBanned` como true a partir do servidor e use o token de novo:

```text
GET /users/me   with the token issued before the ban
→ 200   still valid, as alex_k
```

A conta banida continua autenticada, lendo e escrevendo, até que por acaso faça logout. Num app mobile, onde as sessões são longas por design, isso pode levar semanas.

Como medimos: um session token foi obtido fazendo login antes do banimento, a flag foi então definida pelo servidor, e o mesmo token foi reenviado contra `/users/me` só com o application id e a REST key. Sem master key, porque a master key ignora triggers e não provaria nada.

Fica pior. O Parse deixa um usuário logado escrever no próprio registro (é assim que "mudar meu nome de exibição" funciona), e `isBanned` é um campo desse registro como qualquer outro. Com a mesma sessão anterior ao banimento:

```text
PUT /users/<id>   { "isBanned": false }
→ 200   updated

GET /login        same credentials
→ 200   OK
```

O usuário banido desbaniu a si mesmo. Não por uma falha do Parse, mas porque a flag foi deixada como dado comum, gravável pelo usuário.

## Como fazer o banimento valer?

Três triggers em vez de um. A verificação de login fica; dois a mais fecham o que ela deixa aberto.

```js
// 1. The door. beforeLogin runs before a session token is issued.
Parse.Cloud.beforeLogin(async (request) => {
  if (request.object.get('isBanned')) {
    throw new Error('This user has been banned');
  }
});

// 2. The flag. Without this, a signed-in user can clear their own ban.
Parse.Cloud.beforeSave(Parse.User, async (request) => {
  if (request.master) return;              // server-side and dashboard writes
  const original = request.original;
  if (!original) return;                   // sign-up, nothing to compare
  if (request.object.get('isBanned') !== original.get('isBanned')) {
    throw new Error('isBanned can only be changed by the server');
  }
});

// 3. The sessions already out there.
Parse.Cloud.afterSave(Parse.User, async (request) => {
  const original = request.original;
  if (!original) return;
  const nowBanned = request.object.get('isBanned') === true;
  const wasBanned = original.get('isBanned') === true;
  if (!nowBanned || wasBanned) return;

  const query = new Parse.Query(Parse.Session);
  query.equalTo('user', request.object);
  const sessions = await query.find({ useMasterKey: true });
  await Parse.Object.destroyAll(sessions, { useMasterKey: true });
});
```

O trigger 2 compara o valor recebido com `request.original` e recusa só quando o campo realmente muda, então atualizações comuns de perfil continuam funcionando. `request.master` deixa o servidor e a dashboard passarem; sem isso, você não conseguiria banir ninguém.

O trigger 3 dispara só na transição para banido, não em todo save de um usuário banido, o que evita consultar as sessões de novo em escritas sem relação.

![Os três triggers no editor de Cloud Code, prontos para o deploy](/blog/blog-assets/ban-and-unban-users-from-your-app/01-triggers.jpg)

## Os três triggers fecham as duas brechas?

Sim, nas duas, e os mesmos testes dizem isso. Com os três no ar, uma sessão nova foi obtida antes do banimento, o caso mais favorável a um atacante.

| Teste | Um trigger | Três triggers |
| --- | --- | --- |
| Login com a conta banida | 400, código 141 | 400, código 141 |
| Sessão anterior ao banimento, `GET /users/me` | **200 — ainda válida** | **400, código 209 — session token inválido** |
| Cliente alterando `isBanned` | **200 — gravado** | **400 — recusado** |

O código 209 é o erro de session token inválido do Parse: o token que o cliente segura não existe mais, então o app vê um usuário deslogado e o manda para a tela de login, onde o trigger 1 o barra.

Como medimos: cada linha é uma requisição por versão do Cloud Code (o trigger único do vídeo, depois os três), contra a mesma conta no mesmo backend em 24 de setembro de 2026, sempre com chaves de cliente.

## Como desbanir alguém?

Coloque a flag de volta em false de qualquer lugar onde a master key se aplica: a dashboard, uma Cloud Function, sua ferramenta de admin. O trigger 2 deixa passar porque `request.master` é true; o trigger 3 ignora porque só age na transição para banido.

As sessões antigas do usuário continuam destruídas, e isso é o correto: desbanir devolve o direito de entrar, não as sessões que foram canceladas. A pessoa faz login de novo e recebe uma sessão nova.

Se você quer banimentos que expiram, guarde uma data em vez de um booleano e compare no `beforeLogin`. O resto da estrutura não muda, e as mesmas duas brechas precisam dos mesmos dois triggers.

## Quais erros você vai ver, e o que diz o corpo deles?

Três, e todos chegam como HTTP 400 com um código do Parse no corpo. O código é o que seu cliente deve usar para decidir; a mensagem é sua, lançada pelo trigger.

```json
{ "code": 141, "error": "This user has been banned" }
{ "code": 141, "error": "isBanned can only be changed by the server" }
{ "code": 209, "error": "Invalid session token" }
```

O código **141** é o erro genérico de "uma Cloud Code function falhou", então as suas duas rejeições o compartilham; a mensagem é a única coisa que as distingue. Se você precisa que o cliente reaja de forma diferente a cada uma, lance uma mensagem estruturada e faça o parse dela, em vez de casar com um texto em inglês que talvez você queira traduzir depois.

O código **209** é o que seu app já conhece: é o que o Parse devolve para qualquer sessão morta. A maioria dos clientes trata isso como "deslogado" e manda o usuário para a tela de login, onde o primeiro trigger o recebe com a mensagem de banimento. Essa é a sequência que um usuário banido de fato vive.

Como medimos: cada linha acima é o corpo verbatim da resposta a uma requisição feita só com o application id e a REST key, capturada em 24 de setembro de 2026.

## Quando a flag de banimento é a ferramenta errada?

Quando o problema é a pessoa, não a conta. A flag vive num único registro de usuário, então quem cria outra conta com um e-mail novo entra de volta direto, e nada neste guia atrapalha isso.

Se você está lidando com abuso persistente, e não com alguém que quebrou uma regra e que você consegue nomear, precisa de sinais que sobrevivam à conta, e esse é um design diferente e bem mais difícil.

Também é o formato errado para qualquer coisa temporária. Um booleano não tem data de fim, então uma suspensão de sete dias vira uma anotação na agenda e a tarefa de alguém lembrar. Guarde um timestamp em vez disso e compare no `beforeLogin`; os outros dois triggers ficam exatamente como estão.

E é bruta demais quando o problema é um recurso só. Se alguém está abusando dos comentários, banir a conta tira tudo o mais que a pessoa faz legitimamente. Uma flag por recurso, verificada no Cloud Code que é dono daquele recurso, mantém a punição do tamanho da infração.

## O que você acrescentaria ao sistema de banimento em seguida?

Registre quem baniu quem e por quê. Um booleano não diz nada daqui a três meses, e decisões de moderação recebem recurso: um `bannedAt`, `bannedBy` e `banReason` no mesmo registro não custam nada e respondem à pergunta depois.

Pense no que uma sessão banida deve ver no momento em que morre. O cliente recebe o código 209 e, se o seu app trata isso como "deslogado", o usuário simplesmente cai na tela de login sem explicação. Capturar esse código e mostrar a mensagem de banimento é um fim mais gentil do que um logout silencioso.

E aproveite para conferir as permissões por classe (CLP) de `_User`. Os triggers protegem um campo; eles não impedem um usuário de ler os registros de outras pessoas se as permissões permitirem.
