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.
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.

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.
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:
{ "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:
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.
Trancar a porta não faz nada com as pessoas que já estão na sala.
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:
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.
// 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 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.
{ "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.