jsonrestfirebase-realtime-databasefirebase-security

Why are my Firebase RTDB Rules Always Returning Unauthorized


Rules

{
  "rules": {
    "Accounts": {
      ".read": "true",
      "$user_id": {
        // Check if object has all required values after operation
        ".validate": "newData.child('Username').isString() && 
                      newData.child('Password').isString() && 
                      newData.child('Salt').isString() && 
                      newData.child('ExpirationDate').isString()",
        // Only allow username update if:
        //  * There is a provided username AND
        //  * There is no existing username (new account) OR
        //  * It is the same as the existing username (patches)
        "Username": {
          ".write": "(!data.exists() || data.val() === newData.val())"
        },
        "Password":         {".write": "newData.exists()"},
        "Salt":             {".write": "newData.exists()"},
        "ExpirationDate":   {".write": "newData.exists()", ".validate": "newData.isString()"},
        "IsVerified":       {".write": "newData.exists()", ".validate": "newData.isBoolean()"},
        "Domains":          {".write": "true"},
        "ActiveDomain":     {".write": "true"},
        "VerificationCode": {".write": "!data.exists() || newData.exists()", ".validate": "newData.isString()"}
      },
    }
  }
}

The POST Sent

Sent to [FIREBASEURL]/Accounts/blmvyubcyhislpquhj@ytnhy,com.json

{
  "Username": "blmvyubcyhislpquhj@ytnhy,com",
  "Password": "lR3lh7BcXU0=",
  "Salt": "0sFEiXUsUpQ=",
  "ExpirationDate": "8/1/2024",
  "ActiveDomain": null,
  "Domains": null,
  "IsVerified": false,
  "VerificationCode": "327761"
}

This POST always returns Unauthorized.

From what I've found, the rules work if I remove all the "Username" level rules and replace it with a dummy ".write":"true".

I've tried with lowercase field names. I've tried just the username rule at that level. The playground seems to show that the request is valid.

I'd like to avoid using Firebase Authentication if possible. I realize it'll be less secure this way.


Solution

  • Sending a POST request creates a new child under the path where you call it. So you're writing the JSON to [FIREBASEURL]/Accounts/blmvyubcyhislpquhj@ytnhy,com/newpushID, which your rules don't allow.

    To fix this, use PUT rather than POST.

    These are normal RESTful semantics btw, so I recommend reading up on those.


    You're also not granting anyone write access to [FIREBASEURL]/Accounts/blmvyubcyhislpquhj@ytnhy,com, but only to [FIREBASEURL]/Accounts/blmvyubcyhislpquhj@ytnhy,com/Username. So writing on the [FIREBASEURL]/Accounts/blmvyubcyhislpquhj@ytnhy,com gets rejected.

    Fix this by having a .write rule on the [FIREBASEURL]/Accounts/blmvyubcyhislpquhj@ytnhy,com level in your rules that evaluates to true for the PUT request. The .write rules you currently have on levels below that should probably be .validate rules.