Environment
- Android device: Google Pixel 2
- Android OS version: 9
- Google Play Services version: 14.3.66 (100400-213742215)
- Firebase/Play Services SDK version: 16.0.1
- FirebaseUI version: ui-firestore:4.2.0, ui-auth:4.1.0.
Issue
request.auth.uid != null
is always null when using Firestore security rules.
When the uid is null the user on the Android client is both logged in via the Firebase UI and showing in the Firestore Authentication console.
Implementation
Sample Firestore Security Rule
service cloud.firestore {
function signedInOrPublic() {
return request.auth.uid != null;
}
match /databases/{database}/documents {
match /{document=**} {
allow read;
}
}
// Add to user collection.
match /databases/{database}/documents {
match /users/{userId}/{collectionOrAction}/{content} {
allow create: if signedInOrPublic();
}
}
Android Call to Firestore Collection
The call works as expected without any stringent rules set.
usersCollection.document(userId).collection(actionCollection)
.document(content.id)
.set(ContentAction(Date(), content.id, content.title, content.creator,
content.qualityScore)).addOnSuccessListener {
updateUserActionCounter(userId, countType)
}.addOnFailureListener {
Log.w(LOG_TAG, "User content action update FAIL.")
}
Firebase Libraries
implementation 'com.firebase:firebase-client-android:2.5.2'
implementation 'com.google.firebase:firebase-core:16.0.3'
implementation 'com.google.firebase:firebase-firestore:17.1.0'
implementation 'com.firebaseui:firebase-ui-firestore:4.1.0'
implementation 'com.firebaseui:firebase-ui-auth:4.0.0'
google-services.json Configuration
I have a version of the google-services.json for my debug and release builds in order to keep a separate Firestore project / environment / analytics for the production version of the app. I'm wondering if this could be effecting Firestore rules even though this setup is working as expected for every other part of Firebase so far (Firestore db, Analytics, etc.).
app>src>debug>google-services.json
app>src>release>google-services.json
Attempted Solutions
- I tried checking if
request.auth
was not null in the Firestore Rules file. - I attempted to use
request.auth.uid == userId
in the rule above comparing therequest.auth.uid
to theuserId
in the path route. - I attempted to use
request.auth.token.email == userEmail
in a rule which fails. - Adding additional Firebase Auth library even though I already have the Firebase UI Auth library which is already as seen above:
implementation 'com.google.firebase:firebase-auth:16.0.4
- Adding
implementation 'com.google.android.gms:play-services-auth:16.0.1'
- Upgrading Firestore libraries to
firebase-core:16.0.4
,firebase-firestore:17.1.1
,firebase-ui-firestore:4.2.0
,firebase-ui-auth:4.1.0
. - I built a simple sample app in order to see if the security rules worked with a straightforward implementation.
Sample App
In the sample app there is a Sign in /out button which signs the user in and out depending on their current sign in state. The sign in state is displayed in the top left corner. The Get data button retrieves a String field from Firestore database based on the Firestore Security Rules.
Error
2018-10-14 15:21:59.123 24192-24192/firebase_security_sample.adamhurwitz.firebasesecuritysample
E/AndroidRuntime: FATAL EXCEPTION: main
Process: firebase_security_sample.adamhurwitz.firebasesecuritysample, PID: 24192
com.google.android.gms.tasks.RuntimeExecutionException: com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.
at com.google.android.gms.tasks.zzu.getResult(Unknown Source:15)
at firebase_security_sample.adamhurwitz.firebasesecuritysample.MainActivity$onCreate$2$1.onComplete(MainActivity.kt:63)
at com.google.android.gms.tasks.zzj.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.
at com.google.firebase.firestore.obfuscated.zzhc.zza(com.google.firebase:firebase-firestore@@17.1.1:119)
at com.google.firebase.firestore.obfuscated.zzk.zza(com.google.firebase:firebase-firestore@@17.1.1:134)
at com.google.firebase.firestore.obfuscated.zzak.zza(com.google.firebase:firebase-firestore@@17.1.1:384)
at com.google.firebase.firestore.obfuscated.zzm.zza(com.google.firebase:firebase-firestore@@17.1.1:235)
at com.google.firebase.firestore.obfuscated.zzfv.zza(com.google.firebase:firebase-firestore@@17.1.1:7521)
at com.google.firebase.firestore.obfuscated.zzfv$1.zza(com.google.firebase:firebase-firestore@@17.1.1:172)
at com.google.firebase.firestore.obfuscated.zzgc.zzb(com.google.firebase:firebase-firestore@@17.1.1:3109)
at com.google.firebase.firestore.obfuscated.zzfh.run(com.google.firebase:firebase-firestore@@17.1.1:1117)
at com.google.firebase.firestore.obfuscated.zzfc$zza.zza(com.google.firebase:firebase-firestore@@17.1.1:67)
at com.google.firebase.firestore.obfuscated.zzfc$zzc.zza(com.google.firebase:firebase-firestore@@17.1.1:110)
at com.google.firebase.firestore.obfuscated.zzgv$1.onMessage(com.google.firebase:firebase-firestore@@17.1.1:107)
at io.grpc.ForwardingClientCallListener.onMessage(ForwardingClientCallListener.java:33)
at io.grpc.ForwardingClientCallListener.onMessage(ForwardingClientCallListener.java:33)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInContext(ClientCallImpl.java:526)
at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at com.google.firebase.firestore.obfuscated.zzgf$zza.run(com.google.firebase:firebase-firestore@@17.1.1:203)
at java.lang.Thread.run(Thread.java:764)
Caused by: io.grpc.StatusException: PERMISSION_DENIED: Missing or insufficient permissions.
at io.grpc.Status.asException(Status.java:534)
at com.google.firebase.firestore.obfuscated.zzhc.zza(com.google.firebase:firebase-firestore@@17.1.1:117)
at com.google.firebase.firestore.obfuscated.zzk.zza(com.google.firebase:firebase-firestore@@17.1.1:134)
at com.google.firebase.firestore.obfuscated.zzak.zza(com.google.firebase:firebase-firestore@@17.1.1:384)
at com.google.firebase.firestore.obfuscated.zzm.zza(com.google.firebase:firebase-firestore@@17.1.1:235)
at com.google.firebase.firestore.obfuscated.zzfv.zza(com.google.firebase:firebase-firestore@@17.1.1:7521)
at com.google.firebase.firestore.obfuscated.zzfv$1.zza(com.google.firebase:firebase-firestore@@17.1.1:172)
at com.google.firebase.firestore.obfuscated.zzgc.zzb(com.google.firebase:firebase-firestore@@17.1.1:3109)
at com.google.firebase.firestore.obfuscated.zzfh.run(com.google.firebase:firebase-firestore@@17.1.1:1117)
at com.google.firebase.firestore.obfuscated.zzfc$zza.zza(com.google.firebase:firebase-firestore@@17.1.1:67)
at com.google.firebase.firestore.obfuscated.zzfc$zzc.zza(com.google.firebase:firebase-firestore@@17.1.1:110)
at com.google.firebase.firestore.obfuscated.zzgv$1.onMessage(com.google.firebase:firebase-firestore@@17.1.1:107)
at io.grpc.ForwardingClientCallListener.onMessage(ForwardingClientCallListener.java:33)
at io.grpc.ForwardingClientCallListener.onMessage(ForwardingClientCallListener.java:33)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInContext(ClientCallImpl.java:526)
at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at com.google.firebase.firestore.obfuscated.zzgf$zza.run(com.google.firebase:firebase-firestore@@17.1.1:203)
at java.lang.Thread.run(Thread.java:764)
Firebase Security Rule
service cloud.firestore {
match /databases/{database}/documents {
match /testCollection/testDoc {
allow read: if request.auth.uid != null
}
}
}
build.gradle (app)
buildscript {
ext.kotlin_version = '1.2.71'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
build.gradle (project)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "firebase_security_sample.adamhurwitz.firebasesecuritysample"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.firebase:firebase-core:16.0.4'
implementation 'com.google.firebase:firebase-auth:16.0.4'
implementation 'com.google.firebase:firebase-firestore:17.1.1'
implementation 'com.firebaseui:firebase-ui-firestore:4.2.0'
implementation 'com.firebaseui:firebase-ui-auth:4.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply plugin: 'com.google.gms.google-services'
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var signInStatus: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
FirebaseApp.initializeApp(
this,
FirebaseOptions.Builder()
.setApplicationId("1:63924060965:android:c387085babd1a8a4") // Required for Analytics.
.setApiKey("AIzaSyCi4h6WBX495xmzaRsLYro2_Vd9UcB3bpg") // Required for Auth.
.setDatabaseUrl("https://security-rules-sample.firebaseio.com") // Required for RTDB.
.setProjectId("security-rules-sample")
.build(),
"firestoreSecuritySample")
signInStatus = findViewById(R.id.signInStatus) as TextView
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
signInStatus.text = user.displayName
} else {
signInStatus.text = "logged out"
}
signInButton.setOnClickListener {
if (FirebaseAuth.getInstance().currentUser == null) {
val providers = Arrays.asList<AuthUI.IdpConfig>(
AuthUI.IdpConfig.GoogleBuilder().build())
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(providers)
.build(),
123)
} else {
AuthUI.getInstance().signOut(this).addOnCompleteListener {
signInStatus.text = "logged out"
}
}
}
getDataButton.setOnClickListener {
FirebaseFirestore.getInstance(FirebaseApp.getInstance("firestoreSecuritySample"))
.collection("testCollection").document("testDoc").get().addOnCompleteListener {
if (FirebaseAuth.getInstance().currentUser != null) {
firestoreResult.text = it.result!!.get("testField").toString()
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 123) {
if (resultCode == Activity.RESULT_OK) {
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
signInStatus.text = user.displayName
}
}
}
}