Como funcionam as regras de segurança do Firebase na Realtime Database?
Parte 1 — Leituras e Escritas
Quando desenvolvemos aplicações, um aspeto importante a ter em conta é a segurança. Ninguém gostaria de ouvir utilizadores a reclamarem por terem perdido dados na sua aplicação, nem gostaria de acordar um dia e descobrir que a aplicação que levou meses para desenvolver simplesmente não existe mais.
E o Firebase é uma plataforma que permite desenvolver aplicações de forma fácil e rápida. Esta plataforma resolve maior parte das preocupações de um desenvolvedor, mas feliz ou infelizmente a segurança fica por nossa conta.
Uso Firebase já há quase 2 anos e até alguns dias atrás ainda deixava as minhas security rules no modo pré-definido (auth!=null
), pois achava que isto deixava os meus dados seguros. Mas não é bem assim, e é isso que eu vou explicar neste artigo.
Vamos começar por entender o que significa o auth!=null
e porquê não é seguro: esta regra indica que a base de dados só pode ser acedida por utilizadores autenticados, o que significa que é preciso ter uma conta na sua aplicação para poder aceder os dados. Assim os utilizadores podem manipular dados de outros utilizadores, e até dos administradores (caso existam na aplicação). E é por isso que temos que criar regras mais restritivas para impedir tais ações.
Conhecendo as Security Rules
As regras de segurança não são nada mais que uma árvore JSON. E em árvores, temos 3 conceitos conhecidos: nó, chave e valor.
Todas as nossas regras ficam em um nó denominado “rules”. E como deu para notar, nesse nó temos 2 chaves: “.read” e “.write”. Nas nossas regras, os valores destas 2 chaves têm de ser do tipo booleano para indicar as condições de acesso à base de dados(true
para permitir o acesso e/ou false
para negar o acesso).
As chaves
Dentro de cada nó, podemos ter no máximo 4 chaves:
“.read”
— a condição para que possam ser feitas operações de leitura na base de dados.“.write”
— a condição para que possam ser feitas operações de escrita na base de dados.“.validate”
— não é exatamente uma condição, mas se o retorno forfalse
impede a escrita na base de dados. Então o retorno deve ser sempretrue
, depois de validar o valor. (mais detalhes neste mesmo artigo)“.indexOn”
- não é uma condição: indica ao firebase que índice utilizar para ordenar dados na database.
Nota: Neste artigo irei focar mais nas 2 primeiras chaves, deixando as outras 2 para o próximo artigo.
Os nós
Como já vimos, a base de dados começa com o nó “rules”. Mas podemos definir sub-nós para cada nó da Realtime Database. (Meio confuso, eu sei 😁)
Vamos ver um exemplo. Se a nossa Realtime Database tiver 2 nós: utilizadores e mensagens.
{
“utilizadores”:{
//…lista de utilizadores
},
“mensagens”:{
//…lista de mensagens
}
}
Podemos definir regras diferentes para estes nós, basta criar 2 sub-nós em “rules”:
{
“rules”:{
“utilizadores”:{
“.write”:”auth!=uid”,
“.read”:”auth!=uid”
},
“mensagens”:{
“.write”:”auth!=uid”,
“.read”: true
}
}
}
Assim, qualquer um pode ler as mensagens, mas apenas utilizadores da aplicação podem escrever mensagens e escrever/ler dados dos utilizadores. (Atenção que isto continua não sendo seguro, apenas utilizei como exemplo para demonstrar a funcionalidade dos sub-nós).
Os valores
Como já tinha dito, os valores de “.read” e de “.write” devem sempre retornar um boolean
. E para fazer isso, o Firebase dá-nos acesso à mais algumas variáveis:
Já conhecíamos esta variável, ela representa um utilizador autenticado. Através dela podemos acessar 3 outras variáveis: uid(id do utilizador autenticado), provider(método de login utilizado, pode ser “password”, “facebook”, “google”, “twitter”, “github” ou “anonymous”) e token que contém mais variáveis (como por exemplo o nome e email do utilizador autenticado);
Podemos utilizar esta variável, por exemplo, para que os utilizadores autenticados acessem apenas os seus próprios dados:
"utilizadores":{
"$uid":{
".write":"auth!=null && $uid == auth.uid",
".read":"auth!=null && $uid == auth.uid"
}
}
Com estas regras, o utilizador Paulo não tem permissão para aceder aos dados do João.
$ (cifrão)
De certeza que você viu este símbolo no exemplo anterior. Tal como no PHP, ele serve para indicar que trata-se de uma variável. A ideia de declarar estas variáveis não é atribuir valores à elas, mas sim utilizar elas para comparar os seus valores com outros, tal como no exemplo anterior onde comparamos se o uid
do utilizador que está na base de dados (variável $uid
) é igual ao uid do utilizador que fez o login no firebase auth (variável auth.uid
).
root
Tal como o nome indica, esta variável corresponde ao nó raiz da nossa base de dados. Através dela podemos aceder à outros dados que estão fora do nó que estamos a tentar ler/escrever.
Por exemplo, supondo que temos mais um nó “grupos”:
Podemos escrever uma regra de segurança para que o utilizador só possa ler/escrever mensagens em grupos que ele faz parte:
data
Esta variável contem os dados que estão atualmente nesse nó onde pretendemos ler/escrever.
newData
Diferentemente da variável data
, esta variável só pode ser acedida quando estamos a fazer uma escrita (.write) ou validação (.validate). Ela contém os dados que serão escritos nesse nó. Podemos utilizar em conjunto com a variável data
, por exemplo, quando estamos a atualizar a data em que um utilizador esteve online pela última vez:
“uid1”:{
“nome”:”Rosário”,
“visto”:”201802282030" //timestamp da data em que o utilizador foi visto pela última vez
}
Métodos/Funções adicionais
As variáveis root
, data
e newData
são como se fossem os DataSnapshot
que nós conhecemos (sim, aqueles que utilizamos quando lemos dados da Realtime Database). Isso significa que eles têm os mesmos métodos que existem em um DataSnapshot
:
val()
child()
parent()
hasChild()
exists()
getPriority()
Mas para além destes métodos, existem mais 3 métodos para verificar o tipo de dado:
isString()
isNumber()
isBoolean()
E é tudo para este artigo. Espero que você tenha aprendido a utilizar as regras de segurança e que comece a implementar nos seus projetos (se ainda não o fazia) para termos aplicações mais seguras. :)
No próximo artigo explico as regras .validate
e .indexOn
:
Caso tenha alguma dúvida ou sugestão, pode me contactar pelo email rosariofernandes51@gmail.com ou pelo Telegram. Será um prazer conversar com você. 🙂
*Thanks for the review Paulo Enoque