firebase-ios-sdk icon indicating copy to clipboard operation
firebase-ios-sdk copied to clipboard

Firebase Function unauthorized with iOS, works on emulator

Open reedm121 opened this issue 1 year ago • 1 comments

Description

Expected

I expect my helloWorld firebase function to execute when called from functions.httpsCallable("helloWorld").call() in my Swift code. I only want people who install my app to be able to use my function, so I'm not looking for the authenticate allUsers option.

It works as expected if I use firebase emulator and point my Swift code to the emulator and port: Simulator Screen Recording - iPhone 16 - 2024-11-27 at 01 20 05

Actual result (using deployed Firebase function)

Whenever I move off the functions emulator to try and run it via the deployed Firebase functions, I get denied with a 401 Error and the following is the most informative part of the Google Cloud logs: textPayload:"The request was not authorized to invoke this service. Read more at https://cloud.google.com/run/docs/securing/authenticating Additional troubleshooting documentation can be found at: https://cloud.google.com/run/docs/troubleshooting#401"

Here is how it looks running it on the simulator: Simulator Screen Recording - iPhone 16 - 2024-11-27 at 01 29 03

My Code

helloWorld function

//index.js 
// containing my deployed Firebase function

const { onCall, onRequest } = require("firebase-functions/v2/https");
const logger = require("firebase-functions/logger");

exports.helloWorld = onCall((data, context) => {
    // Log the user's authentication status
    if (context.auth) {
        logger.info("User is authenticated:", context.auth.uid);
    } else {
        logger.info("User is not authenticated.");
    }

    return { message: "Hello World from Firebase function!" };
});

Swift files

//ContentView.swift
struct ContentView: View {
    @StateObject var firebaseFunctionsManager: FirebaseFunctionsManager = FirebaseFunctionsManager()
    
    @State var helloWorldMsg: String = "function not called yet"
    
    var body: some View {
        VStack {
            Button("Call hello world firebase function"){
                Task{
                    do{
                        print("⚪️ Attempting to call helloWorld...")
                        helloWorldMsg = try await firebaseFunctionsManager.callHelloWorld()
                    }
                    catch{
                        print("🔴 Error: \(error.localizedDescription)")
                        helloWorldMsg = error.localizedDescription
                    }
                }
            }.buttonStyle(.bordered)
            Text(helloWorldMsg)
            Button("Clear"){
                helloWorldMsg = ""
            }.buttonStyle(.bordered)
        }
        .padding()
    }
}

//FirebaseFunctionsManager.swift
class FirebaseFunctionsManager: ObservableObject {
    lazy var functions = Functions.functions()
    
    func callHelloWorld() async throws -> String {
        do {
            //functions.useEmulator(withHost: "localhost", port: 5001)
            let result = try await functions.httpsCallable("helloWorld").call()
            
            // Parse the response to extract the string message
            if let data = result.data as? [String: Any],
               let message = data["message"] as? String {
                return message
            } else {
                throw NSError(domain: "InvalidResponse", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unexpected response format."])
            }
        } catch {
            throw error
        }
    }
}

I even wondered if it was a Firebase authentication issue so I added anonymous authentication in my App init

//FirebaseFunctionsExampleApp.swift

@main
struct FirebaseFunctionsExampleApp: App {
    init() {
        FirebaseConfiguration.shared.setLoggerLevel(.debug)
        
        FirebaseApp.configure()
        
        // Authenticate Anonymously
        Auth.auth().signInAnonymously { authResult, error in
            if let error = error {
                print("🔴 Error signing in anonymously: \(error.localizedDescription)")
            } else if let user = authResult?.user {
                print("🟢 Signed in anonymously with user ID: \(user.uid)")
            }
        }
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

I've also tried messing with the IAM policies but nothing worked. And the more I look into the Authentication I keep reading that Firebase Functions are meant to handle authentication themselves. Or am I meant to be implementing from the guides here?

Reproducing the issue

  1. Make a firebase functions directory with firebase init
  2. Connect the directory to a firebase project with an iOS app
  3. Make a simple onCall function like helloWorld that sends some kind of data to be displayed to the iOS app
  4. Call the function from the iOS app

Firebase SDK Version

11.5

Xcode Version

16.1

Installation Method

Swift Package Manager

Firebase Product(s)

Functions

Targeted Platforms

iOS

Relevant Log Output

11.5.0 - [FirebaseCore][I-COR000001] Configuring the default app.
11.5.0 - [GoogleUtilities/AppDelegateSwizzler][I-SWZ001014] App Delegate does not conform to UIApplicationDelegate protocol.
11.5.0 - [GoogleUtilities/SceneDelegateSwizzler][I-SWZ001114] Successfully created Scene Delegate Proxy automatically. To disable the proxy, set the flag GoogleUtilitiesAppDelegateProxyEnabled to NO (Boolean) in the Info.plist
🟢 Signed in anonymously with user ID: OUm3lIy1K9bUCusn85XMAEAtcVH3
11.5.0 - [FirebaseCore][I-COR000033] Data Collection flag is not set.
⚪️ Attempting to call helloWorld...
11.5.0 - [FirebaseAuth][I-AUT000002] Token auto-refresh enabled.
11.5.0 - [FirebaseAuth][I-AUT000004] Token auto-refresh scheduled in 14:48 for the new token.
11.5.0 - [FirebaseAuth][I-AUT000017] Has valid access token. Estimated expiration date:2024-11-27 06:13:08 +0000, current date: 2024-11-27 05:53:20 +0000
GTMSessionFetcher 0x10660f390 (https://us-central1-fir-functionsexample-1f77e.cloudfunctions.net/helloWorld) was already running
🔴 Error: UNAUTHENTICATED

If using Swift Package Manager, the project's Package.resolved

Expand Package.resolved snippet

Replace this line with the contents of your Package.resolved.

If using CocoaPods, the project's Podfile.lock

Expand Podfile.lock snippet

Replace this line with the contents of your Podfile.lock!

reedm121 avatar Nov 27 '24 06:11 reedm121

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

google-oss-bot avatar Nov 27 '24 06:11 google-oss-bot

So I eventually figured out this has to do with the Google Cloud migration from Cloud Functions to Cloud Run. I was assigning the permissions as "Cloud Function Invoker" but I really needed to do "Cloud Run Invoker". Kind of confusing, also had something to do with v2 functions utilizing Cloud Run rather than Cloud Functions.

So basically I needed to change permissions in Cloud Run not on Cloud Functions on the Google Cloud Console

Image

I didn't mind making my function public so that's why I just selected allow unauthenticated.

Firebase Auth wasn't needed it was all just an issue with the permissions mixup between Cloud Run and Cloud Functions

reedm121 avatar Feb 12 '25 17:02 reedm121