Firebase user's context.auth is present still allAuthenticatedUsers secured Google function error with UNAUTHENTICATED
The cloud configuration for allAuthenticatedUsers is not in any way related to Firebase, and what it does for authentication for callable type functions. Firebase callable functions handle their own authentication separately from the configuration you changed. You should change it back if you want callables to work properly.
The allUsers permission that you removed was responsible for making your function accessible to the public, including your app. When you removed that, you effectively removed the ability for anyone on the internet to be able to invoke the function (which is very much required for callable functions to operate as designed).
When you added allAuthenticatedUsers, what you did was require only callers that authenticate themselves with a Google service account. Again, this is not related to Firebase or Firebase Auth. It's a Google Cloud IAM concept. It's not applicable for callable functions, so you shouldn't use it. It's not clear to me why you thought this was a good configuration to make.
If you want Firebase callable functions to be invoked normally from apps, you should leave the permission at the default allUsers, and let the Firebase SDK handle the authentication of the end user.
Read more about Google Cloud access control lists to understand the cloud configurations that you were changing.

Admin
Updated on December 17, 2022Comments
-
Admin 1 minute
I wrote a simple cloud function:
import * as functions from 'firebase-functions'; export const repeat = functions.https.onCall( function (data, context) { // Authentication user information is automatically added to the request. if (context.auth) { console.log(' context.auth is defined '); console.log(' uid is ' + context.auth.uid); } else { console.log(' context.auth undefine. '); } if (context.auth) { return { repeat_message: context.auth.uid + ' ' + data.message, repeat_count: data.count + 1, }; } else { return { repeat_message: ' noUID ' + data.message, repeat_count: data.count + 1, }; } } );
And a corresponding Flutter Client app:
import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:cloud_functions/cloud_functions.dart'; final FirebaseAuth _fAuth = FirebaseAuth.instance; final GoogleSignIn _googleSignIn = GoogleSignIn(scopes: ['email'], signInOption: SignInOption.standard); FirebaseUser _firebaseUser; GoogleSignInAccount _googleSignInAccount; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( textTheme: TextTheme( caption: TextStyle( fontSize: 20.0 ), body1: TextStyle( fontSize: 20.0 ), ), ), title: 'calling function', debugShowCheckedModeBanner: false, home: LaunchScreen(), ); } } class LaunchScreen extends StatefulWidget { @override _LaunchScreenState createState() { return _LaunchScreenState(); } } // LoggingOptions to _LaunchScreenState class _LaunchScreenState extends State<LaunchScreen> { String _response = 'no response'; int _responseCount = 1; final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'repeat') ..timeout = const Duration(seconds: 90); Future<FirebaseUser> _handleSignIn() async { try { GoogleSignInAccount googleSignInAccount = await _googleSignIn.signIn(); _googleSignInAccount = googleSignInAccount; GoogleSignInAuthentication authentication = await googleSignInAccount.authentication; final AuthCredential credential = GoogleAuthProvider.getCredential( accessToken: authentication.accessToken, idToken: authentication.idToken, ); AuthResult authResult; authResult = await _fAuth.signInWithCredential( credential); _firebaseUser = authResult.user; setState(() {}); return _firebaseUser; } catch (e) { print(e.toString()); } return null; } Future<void> _handleSignOut() async { FirebaseAuth.instance.signOut(); _googleSignIn.signOut(); setState(() { _firebaseUser =null; _googleSignInAccount= null; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample Code'),), body: Center( child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ MaterialButton( child: const Text( 'Sign in with Google', style: TextStyle(fontSize: 16.0),), onPressed: () { _handleSignIn().then((user) { // logggedIn debugPrint('user ' + user.toString()); } ); }, ), MaterialButton( child: const Text( 'Sign out with Google', style: TextStyle(fontSize: 16.0),), onPressed: () { _handleSignOut(); }, ), Text( _firebaseUser != null ? _firebaseUser.uid : 'user logged off'), Text('FromServer $_responseCount: $_response'), MaterialButton( child: const Text('SEND REQUEST', style: TextStyle(fontSize: 18.0),), onPressed: () async { try { final HttpsCallableResult result = await callable.call( <String, dynamic>{ 'message': 'hello', 'count': _responseCount, } , ); print(result.data); setState(() { _response = result.data['repeat_message']; _responseCount = result.data['repeat_count']; }); } on CloudFunctionsException catch (e) { print('caught Firebase functions exception'); print(e.code); print(e.message); print(e.details); } catch (e) { print('caught generic exception'); print(e); } }, ), ], ), )); } }
and I can see in both server logs and client side that
Auth
is passed correctly.But when I add
allAuthenticatedUsers
and removeallUsers
to 'Cloud Functions Invoker' Role from the function, the App starts gettingPlatformException
with UNAUTHENTICATED code.Here is how setting looks before the change:
And after adding
allAuthenticatedUsers
:After removing
allUsers
:Then when Flutter invokes the function (when the user is logged in), Flutter errors with
PlatformException(functionsError, Cloud function failed with exception., {message: UNAUTHENTICATED, details: null, code: UNAUTHENTICATED})
It should show an error only when the user is not logged in, but it is showing in either cases.
-
Doug Stevenson almost 3 yearsIt's working as designed. If you don't want unauthenticated Firebase Auth users to invoke it, you should return early out of the function if
context.auth
is not defined. This is your only option. You cannot stop the function from being invoked from anyone on the internet, but you can stop them from running your code without auth. -
Admin almost 3 yearsthis callable can be called curl -X POST -d '{ "data": {"count":2,"message":"any message"}}' 'us-central1-xyz.cloudfunctions.net/repeat' -H "Content-Type:application/json" and that is what I wanted to avoid
-
Doug Stevenson almost 3 yearsI know - I understood the last comment that you deleted, and I'm saying that it's not possible to fully cut off access from unauthenticated users. You have to check in your code if the user is authenticated and return early if they're not. Contact Firebase support directly to file a feature request if you'd like different behavior. support.google.com/firebase/contact/support
-
genericUser 12 monthsUsing allUsers is a BAD solution. What is the solution for firebase express functions? (can't
context.auth
). I'm using SMS verification via Flutter