Ir para o console

Como funcionam as regras de segurança

A segurança pode ser uma das partes mais complexas do quebra-cabeças de desenvolvimento de aplicativos. Na maioria dos apps, os desenvolvedores precisam criar e executar um servidor que realize a autenticação (identificar quem é o usuário) e a autorização (definir o que um usuário pode fazer).

As regras de segurança do Firebase removem a camada do meio (servidor). Assim, é possível especificar permissões baseadas em caminho para clientes que se conectam diretamente aos seus dados. Veja neste guia mais sobre como as regras são aplicadas às solicitações recebidas.

Selecione um produto para saber mais sobre as regras dele.

Cloud Firestore

Estrutura básica

As regras de segurança do Firebase no Cloud Firestore e no Storage usam as seguintes estrutura e sintaxe:

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

Veja abaixo os principais conceitos que você precisa conhecer antes de criar as regras:

  • Solicitação: o método ou os métodos invocados na instrução allow que você permite serem executados. Os métodos padrão são: get, list, create, update e delete. Os métodos de conveniência read e write permitem amplo acesso de leitura e gravação ao banco de dados ou ao caminho de armazenamento especificado.
  • Caminho: o banco de dados ou o local de armazenamento, representado como um caminho de URI.
  • Regra: a instrução allow com uma condição que permitirá a ocorrência de uma solicitação se ela for avaliada como verdadeira.

Caminhos correspondentes

Todas as instruções de correspondência precisam se referir a documentos, e não a conjuntos. Uma instrução de correspondência pode se referir a um documento específico, como em match /cities/SF ou usar caracteres curingas para se referir a qualquer documento no caminho especificado, como em match /cities/{city}.

No exemplo acima, a instrução de correspondência usa a sintaxe com caractere curinga {city}. Isso significa que a regra se aplica a qualquer documento na coleção cities, como /cities/SF ou /cities/NYC. Quando as expressões allow na instrução de correspondência são avaliadas, a variável city resolverá o nome do documento da cidade, como SF ou NYC.

Subconjuntos correspondentes

Os dados no Cloud Firestore são organizados em conjuntos de documentos, e cada documento pode estender a hierarquia por meio de subconjuntos. É importante entender como as regras de segurança interagem com dados hierárquicos.

Considere a situação em que cada documento na coleção cities contenha uma subcoleção landmarks. As regras de segurança se aplicam apenas ao caminho correspondente, de modo que os controles de acesso definidos na coleção cities não se aplicam à subcoleção landmarks. Em vez disso, escreva regras explícitas para controlar o acesso a subcoleções:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read, write: if <condition>;

        // Explicitly define rules for the 'landmarks' subcollection
        match /landmarks/{landmark} {
          allow read, write: if <condition>;
        }
    }
  }
}

Ao executar o aninhamento de instruções match, o caminho da instrução match interna estará sempre relacionado ao caminho da instrução match externa. Os seguintes conjuntos de regras são equivalentes:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city}/landmarks/{landmark} {
      allow read, write: if <condition>;
    }
  }
}

Para que as regras se apliquem a uma hierarquia arbitrariamente profunda, use a sintaxe com caractere curinga recorrente, {name=**}:

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

Ao usar a sintaxe com caractere curinga recorrente, a variável do caractere curinga terá todo o segmento do caminho de correspondência, mesmo que o documento esteja localizado em uma subcoleção aninhada em vários níveis. Por exemplo, as regras listadas acima corresponderiam a um documento localizado em /cities/SF/landmarks/coit_tower. O valor da variável document seria SF/landmarks/coit_tower.

Os caracteres curingas recorrentes não podem corresponder a um caminho vazio. Assim, match /cities/{city}/{document=**} corresponderá a documentos em subcoleções, mas não na coleção cities. O caractere coringa match /cities/{document=**} corresponderá a documentos na coleção e nas subcoleções cities.

É possível que um documento corresponda a mais de uma instrução match. Quando várias expressões allow corresponderem a uma solicitação, o acesso será permitido se qualquer uma das condições for true:

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the 'cities' collection.
    match /cities/{city} {
      allow read, write: if false;
    }

    // Matches any document in the 'cities' collection or subcollections.
    match /cities/{document=**} {
      allow read, write: if true;
    }
  }
}

No exemplo acima, todas as leituras e gravações da coleção cities serão permitidas porque a segunda regra será sempre true, mesmo que a primeira regra seja sempre false.

Limites da regra de segurança

Ao trabalhar com regras de segurança, observe os seguintes limites:

Limite Detalhes
Número máximo de chamadas exists(), get() e getAfter() por solicitação
  • 10 para solicitações de documento único e solicitações de consulta.
  • 20 para leituras de vários documentos, transações e gravações em lote. O limite anterior de 10 também se aplica a cada operação.

    Por exemplo, imagine que você crie uma solicitação de gravação em lote com três operações de gravação e que suas regras de segurança usem duas chamadas de acesso a documentos para validar cada gravação. Nesse caso, cada gravação usa 2 das 10 chamadas de acesso, e a solicitação de gravação em lote usa 6 das 20 chamadas de acesso.

Ao exceder qualquer um desses limites, ocorrerá um erro de permissão negada.

Algumas chamadas de acesso a documentos podem ser armazenadas em cache, e essas chamadas não entram na conta dos limites.

Profundidade máxima da chamada da função 20
Número máximo de chamadas de função recorrentes ou cíclicas 0 &lpar;not permitted&rpar;
Número máximo de expressões avaliadas por solicitação 1.000
Tamanho máximo de um conjunto de regras 64 KB

Cloud Storage

Estrutura básica

As regras de segurança do Firebase no Cloud Firestore e no Storage usam as seguintes estrutura e sintaxe:

service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

Veja abaixo os principais conceitos que você precisa conhecer antes de criar as regras:

  • Solicitação: o método ou os métodos invocados na instrução allow que você permite serem executados. Os métodos padrão são: get, list, create, update e delete. Os métodos de conveniência read e write permitem amplo acesso de leitura e gravação ao banco de dados ou ao caminho de armazenamento especificado.
  • Caminho: o banco de dados ou o local de armazenamento, representado como um caminho de URI.
  • Regra: a instrução allow com uma condição que permitirá a ocorrência de uma solicitação se ela for avaliada como verdadeira.

Caminhos correspondentes

Com as regras de segurança do Storage match, você verifica a correspondência dos caminhos usados para acessar os arquivos do Cloud Storage. As regras match verificam caminhos exatos ou curinga e também podem ser aninhadas. Se nenhuma regra de correspondência permite um método de solicitação ou se a condição é avaliada como false, a solicitação é negada.

Correspondências exatas

// Exact match for "images/profilePhoto.png"
match /images/profilePhoto.png {
  allow write: if <condition>;
}

// Exact match for "images/croppedProfilePhoto.png"
match /images/croppedProfilePhoto.png {
  allow write: if <other_condition>;
}

Correspondências aninhadas

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/profilePhoto.png"
  match /profilePhoto.png {
    allow write: if <condition>;
  }

  // Exact match for "images/croppedProfilePhoto.png"
  match /croppedProfilePhoto.png {
    allow write: if <other_condition>;
  }
}

Correspondências de caractere curinga

Use regras match para definir um padrão correspondente com caracteres curinga. Um caractere curinga é uma variável nomeada que representa uma string individual, como profilePhoto.png ou vários segmentos de caminhos, como images/profilePhoto.png.

Para criar um caractere curinga, basta colocar o nome do curinga entre chaves, como {string}. Para declarar um caractere curinga com vários segmentos, adicione =** ao nome, como {path=**}:

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/*"
  // e.g. images/profilePhoto.png is matched
  match /{imageId} {
    // This rule only matches a single path segment (*)
    // imageId is a string that contains the specific segment matched
    allow read: if <condition>;
  }

  // Exact match for "images/**"
  // e.g. images/users/user:12345/profilePhoto.png is matched
  // images/profilePhoto.png is also matched!
  match /{allImages=**} {
    // This rule matches one or more path segments (**)
    // allImages is a path that contains all segments matched
    allow read: if <other_condition>;
  }
}

Se um arquivo atende a várias regras de correspondência, o OR do resultado de todas as avaliações é fornecido. Ou seja, se qualquer regra aplicada ao arquivo é avaliada como true, o resultado é true.

Nas regras acima, o arquivo "images/profilePhoto.png" pode ser lido se condition ou other_condition são avaliados como true, e o arquivo "images/users/user:12345/profilePhoto.png" está sujeito apenas ao resultado de other_condition.

Uma variável curinga pode ser referenciada de dentro do match, basta fornecer a autorização do nome do arquivo ou caminho:

// Another way to restrict the name of a file
match /images/{imageId} {
  allow read: if imageId == "profilePhoto.png";
}

As regras de segurança do Storage não são aplicadas em cascata e só são avaliadas se o caminho da solicitação corresponde a um caminho com regras especificadas.

Avaliação de solicitação

Uploads, downloads, alterações em metadados e exclusões são avaliados com a request enviada ao Cloud Storage. No caso de uma solicitação de gravação, a variável request contém o caminho do arquivo, o horário de recebimento e o novo valor do resource. Os cabeçalhos HTTP e o estado de autenticação também são incluídos.

O objeto request também contém o código exclusivo do usuário e o payload do Firebase Authentication no objeto request.auth. Essa informação está detalhada na seção Segurança baseada no usuário da documentação.

Veja uma lista completa das propriedades do objeto request abaixo:

Propriedade Tipo Descrição
auth map<string, string> Quando um usuário está conectado, são fornecidos o uid, ID exclusivo do usuário, e o token, um mapa de declarações de JWT do Firebase Authentication. Caso contrário, o valor é null.
params map<string, string> Mapa contendo os parâmetros de consulta da solicitação.
path path Um path representando o caminho no qual a solicitação é executada.
resource map<string, string> O novo valor do recurso, presente apenas nas solicitações de write.
time timestamp Carimbo de data/hora que representa o horário do servidor em que a solicitação é avaliada.

Avaliação de recurso

Durante a avaliação de regras, é possível também examinar os metadados do arquivo que está sendo modificado, excluído ou transferido por upload ou download. Crie regras complexas e eficazes como, por exemplo, permitir somente o upload de arquivos com um conteúdo específico ou apenas a exclusão de arquivos acima de um determinado tamanho.

As regras de segurança do Firebase para Cloud Storage fornecem metadados de arquivo no objeto resource. Ele contém pares de chave/valor dos metadados expostos em um objeto do Cloud Storage. Inspecione essas propriedades nas solicitações read ou write para garantir a integridade dos dados.

Além do objeto resource, que contém metadados do arquivo existente no caminho da solicitação, use o objeto request.resource nas solicitações write, como upload, atualização de metadados e exclusão. Ele contém um subconjunto dos metadados do arquivo a ser gravado, se permitido. Use esses dois valores para garantir a integridade dos dados ou para aplicar restrições de aplicativo, como tipo ou tamanho de arquivo.

Veja uma lista completa de propriedades do objeto resource abaixo:

Propriedade Tipo Descrição
name string Nome completo do objeto.
bucket string Nome do repositório do objeto.
generation int Geração de objeto GCS do objeto.
metageneration int Metageração de objeto GCS do objeto.
size int Tamanho do objeto em bytes.
timeCreated timestamp Timestamp que indica o horário em que um objeto foi criado.
updated timestamp Timestamp que indica o horário em que um objeto foi atualizado pela última vez.
md5Hash string Hash MD5 do objeto.
crc32c string Hash crc32c do objeto.
etag string Etag associada ao objeto.
contentDisposition string Disposição do conteúdo associado ao objeto.
contentEncoding string Codificação do conteúdo associado ao objeto.
contentLanguage string Idioma do conteúdo associado ao objeto.
contentType string Tipo do conteúdo associado ao objeto.
metadata map Outros pares de chave/valor de metadados personalizados especificados pelo desenvolvedor.

O request.resource contém tudo isso, exceto generation, metageneration, etag, timeCreated e updated.

Exemplo completo

Reúna todos esses recursos e crie um exemplo completo de regras para uma solução de armazenamento de imagens:

service firebase.storage {
 match /b/{bucket}/o {
   match /images {
     // Cascade read to any image type at any path
     match /{allImages=**} {
       allow read;
     }

     // Allow write files to the path "images/*", subject to the constraints:
     // 1) File is less than 5MB
     // 2) Content type is an image
     // 3) Uploaded content type matches existing content type
     // 4) File name (stored in imageId wildcard variable) is less than 32 characters
     match /{imageId} {
       allow write: if request.resource.size < 5 * 1024 * 1024
                    && request.resource.contentType.matches('image/.*')
                    && request.resource.contentType == resource.contentType
                    && imageId.size() < 32
     }
   }
 }
}

Realtime Database

Estrutura básica

No Realtime Database, as regras de segurança do Firebase consistem em expressões semelhantes a JavaScript contidas em um documento JSON.

Elas usam a seguinte sintaxe:

{
  "rules": {
    "<<path>>": {
    // Allow the request if the condition for each method is true.
      ".read": <<condition>>,
      ".write": <<condition>>,
      ".validate": <<condition>>
    }
  }
}

Existem três elementos básicos na regra:

  • Caminho: o local do banco de dados. Ele corresponde à estrutura JSON do seu banco de dados.
  • Solicitação: são os métodos que a regra usa para conceder acesso. As regras read e write concedem acesso amplo de leitura e gravação. Já as regras validate atuam como uma verificação secundária para conceder acesso com base nos dados novos ou nos existentes.
  • Condição: a condição que permitirá uma solicitação se ela for avaliada como verdadeira.

Como as regras se aplicam aos caminhos

No Realtime Database, as regras se aplicam atomicamente, o que significa que as regras em nós pais de nível superior modificam aquelas em nós filhos, e as regras mais detalhadas em um nó mais profundo não concedem acesso a um caminho pai. Caso você já tenha concedido acesso para um dos caminhos pais, não será possível refinar ou revogar o acesso aos dados em um caminho mais profundo na sua estrutura de banco de dados.

Suponha as seguintes regras:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          // ignored, since read was allowed already
          ".read": false
        }
     }
  }
}

Essa estrutura de segurança permite que /bar/ seja lido por /foo/ sempre que ele tiver um baz filho com o valor true. A regra ".read": false em /foo/bar/ não tem nenhum efeito, já que um caminho filho não tem permissão de revogar o acesso.

Mesmo que isso não pareça intuitivo de imediato, essa é uma parte eficiente da linguagem das regras e permite implementar privilégios de acesso muito complexos com o mínimo esforço. Isso é particularmente útil para a segurança com base no usuário.

No entanto, as regras .validate não são aplicadas em cascata. Todas as regras de validação precisam ser atendidas em todos os níveis da hierarquia para que uma gravação seja permitida.

Além disso, como as regras não se aplicam novamente a um caminho pai, a operação de leitura ou gravação falhará se não houver uma regra no local solicitado ou em um local pai que conceda acesso. Mesmo que cada caminho filho afetado seja acessível, a leitura no local pai falhará totalmente. Suponha esta estrutura:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

Sem entender que as regras são avaliadas atomicamente, pode parecer que buscar o caminho /records/ retornaria rec1, mas não rec2. O resultado real, no entanto, é um erro:

JavaScript
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
Swift
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
REST
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

Como a operação de leitura em /records/ é atômica e não existe nenhuma regra de leitura que conceda acesso a todos os dados em /records/, isso retornará um erro PERMISSION_DENIED. Se avaliarmos essa regra no simulador de segurança do Console do Firebase, podemos ver que a operação de leitura foi negada:

Attempt to read /records with auth=Success(null)
    /
    /records

No .read rule allowed the operation.
Read was denied.

A operação foi negada porque nenhuma regra de leitura permitia o acesso ao caminho /records/, mas observe que a regra de rec1 nunca foi avaliada porque não estava no caminho solicitado. Para buscar rec1, precisaríamos acessá-lo diretamente:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

Variável de local

As regras do Realtime Database são compatíveis com uma variável $location para corresponder aos segmentos de caminho. Use o prefixo $ na frente do seu segmento de caminho para corresponder sua regra a qualquer nó filho no caminho.

  {
    "rules": {
      "rooms": {
        // This rule applies to any child of /rooms/, the key for each room id
        // is stored inside $room_id variable for reference
        "$room_id": {
          "topic": {
            // The room's topic can be changed if the room id has "public" in it
            ".write": "$room_id.contains('public')"
          }
        }
      }
    }
  }

Também é possível usar a $variable em paralelo com nomes de caminho constantes.

  {
    "rules": {
      "widget": {
        // a widget can have a title or color attribute
        "title": { ".validate": true },
        "color": { ".validate": true },

        // but no other child paths are allowed
        // in this case, $other means any key excluding "title" and "color"
        "$other": { ".validate": false }
      }
    }
  }