Our c# .net wpf application connects to Google Cloud Firestore using custom tokens. The custom token contains embedded metadata that our Cloud Firestore Rules use to restrict access. The token is generated by a node.js Firebase Function on the same server.
This was working 100% for the past 18 months. But in the last week our users have started receiving an Unauthenticated error about 40% of the time when reading/writing data. It still works about 60% of the time.
E.G. The line await batch.CommitAsync();
will generate the error. The error never occurs while authenticating or creating the token or FirestoreDb, only when using the FirestoreDb to perform actions, and only sometimes.
The error is always the same: Grpc.Core.RpcException: Status(StatusCode="Unauthenticated", Detail="Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.") at Google.Cloud.Firestore.WriteBatch.<CommitAsync>d__15.MoveNext() in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/WriteBatch.cs:line 226"
If the user immediately reconnects, the data can be read/written about 90% of the time. 10% of the time the same error will then thrown again.
It seems like something has recently changed at Google that is stopping this from working. Yet it still works 60% of the time, which is very strange...
The relevant c# code is:
using Firebase.Auth;
using Flurl.Http;
using Google.Cloud.Firestore;
using Google.Cloud.Firestore.V1;
using Grpc.Core;
dynamic response = await GetAuthenticationToken(requestToken);
FirestoreDb db = CreateFirestoreDbWithToken(response.token, FIREBASE_WEB_API, FIREBASE_PROJECT_ID);
private FirestoreDb CreateFirestoreDbWithToken(string token, string firebaseApiKey, string firebaseProjectId)
{
FirebaseAuthProvider authProvider = new FirebaseAuthProvider(new FirebaseConfig(firebaseApiKey));
firebaseAuth = authProvider.SignInWithCustomTokenAsync(token).Result;
CallCredentials callCredentials = CallCredentials.FromInterceptor(async (context, metadata) =>
{
if (firebaseAuth.IsExpired())
firebaseAuth = await firebaseAuth.GetFreshAuthAsync();
if (string.IsNullOrEmpty(firebaseAuth.FirebaseToken))
return;
metadata.Clear();
metadata.Add("authorization", $"Bearer {firebaseAuth.FirebaseToken}");
});
ChannelCredentials credentials = ChannelCredentials.Create(new SslCredentials(), callCredentials);
FirestoreClientBuilder builder = new FirestoreClientBuilder { ChannelCredentials = credentials };
return FirestoreDb.Create(firebaseProjectId, builder.Build());
}
public async Task<dynamic> GetAuthenticationToken(string requestToken)
{
var functionPath = FIREBASE_FUNCTION_NAME + ".cloudfunctions.net/verifyClient";
var response = await functionPath.WithHeader("Content-Type", "application/json; charset=utf-8")
.PostJsonAsync
(
new
{
requestToken,
}
)
.ReceiveJson();
return response;
}
public WriteData()
{
...
WriteBatch batch = db.StartBatch();
batch.Set(dataRef, dataObject);
await batch.CommitAsync();
...
}
Our node.js Firebase Function which verifies the client and returns the access code is:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase)
exports.verifyClient = functions
.https.onRequest((request, response) => {
...
// BUSINESS LOGIC
...
return createAuthToken(requestEmail, groupID).then(authToken => {
response.send({ result: "OK", token: authToken });
return;
})
});
function createAuthToken(requestEmail, groupID) {
return admin.auth()
.createCustomToken(requestEmail, { adminID: groupID })
.then(customToken => {
return customToken;
})
.catch(err => {
console.error('createAuthToken Error: ' + err);
return "";
});
}
Does anyone have any ideas what the problem could be? Or how we can fix?