Reputation: 999
I've been struggling with this for hours now, if not days and can't seem to fix it.
My Requests to Cloud Functions are being denied with error code: 401: UNAUTHENTICATED.
My Code is as follow:
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . FIREBASE_SERIVCE_PATH);
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(Google_Service_CloudFunctions::CLOUD_PLATFORM);
$httpClient = $client->authorize();
$promise = $httpClient->requestAsync("POST", "<MyCloudFunctionExecutionUri>", ['json' => ['data' => []]]);
$promise->then(
function (ResponseInterface $res) {
echo "<pre>";
print_r($res->getStatusCode());
echo "</pre>";
},
function (RequestException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
);
$promise->wait();
I'm currently executing this from localhost as I'm still in development phase.
My FIREBASE_SERIVCE_PATH constant links to my service_account js
My Cloud Function index.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// CORS Express middleware to enable CORS Requests.
const cors = require('cors')({
origin: true,
});
exports.testFunction = functions.https.onCall((data, context) => {
return new Promise((resolve, reject) => {
resolve("Ok:)");
});
});
// [END all]
My Cloud Function Logs:
Function execution took 459 ms, finished with status code: 401
What am I doing wrong so I get Unauthenticated?
PS: My testFunction works perfectly when invoked from my Flutter mobile app who uses: https://pub.dartlang.org/packages/cloud_functions
Update:
I have followed this guide: https://developers.google.com/api-client-library/php/auth/service-accounts but in the "Delegating domain-wide authority to the service account" section, it only states If my application runs in a Google Apps domain, however I wont using Google Apps domain, and plus I'm on localhost.
Upvotes: 1
Views: 2147
Reputation: 41
First of all thanks to Doug Stevenson for the answer above! It helped me to get a working solution for callable functions (functions.https.onCall). The main idea is that such functions expect the auth context of the Firebase User that already logged in. It's not a Service Account, it's a user record in the Authentication section of your Firebase project. So, first, we have to authorize a user, get the ID token from response and then use this token for the request to call a callable function. So, below is my working snippet (from the Drupal 8 project actually).
use Exception;
use Google_Client;
use Google_Service_CloudFunctions;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Promise;
use GuzzleHttp\RequestOptions;
$client = new Google_Client();
$config_path = <PATH TO SERVICE ACCOUNT JSON FILE>;
$json = file_get_contents($config_path);
$config = json_decode($json, TRUE);
$project_id = $config['project_id'];
$options = [RequestOptions::SYNCHRONOUS => TRUE];
$client->setAuthConfig($config_path);
$client->addScope(Google_Service_CloudFunctions::CLOUD_PLATFORM);
$httpClient = $client->authorize();
$handler = $httpClient->getConfig('handler');
/** @var \Psr\Http\Message\ResponseInterface $res */
$res = $httpClient->request('POST', "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=<YOUR FIREBASE PROJECT API KEY>", [
'json' => [
'email' => <FIREBASE USER EMAIL>,
'password' => <FIREBASE USER PASSWORD>,
'returnSecureToken' => TRUE,
],
]);
$json = $res->getBody()->getContents();
$data = json_decode($json);
$id_token = $data->idToken;
$request = new Request('POST', "https://us-central1-$project_id.cloudfunctions.net/<YOUR CLOUD FUNCTION NAME>", [
'Content-Type' => 'application/json',
'Authorization' => "Bearer $id_token",
], Psr7\stream_for(json_encode([
'data' => [],
])));
try {
$promise = Promise\promise_for($handler($request, $options));
}
catch (Exception $e) {
$promise = Promise\rejection_for($e);
}
try {
/** @var \Psr\Http\Message\ResponseInterface $res */
$res = $promise->wait();
$json = $res->getBody()->getContents();
$data = json_decode($json);
...
}
catch (Exception $e) {
}
Upvotes: 2
Reputation: 317808
Callable functions impose a protocol on top of regular HTTP functions. Normally you invoke them using the Firebase client SDK. Since you don't have an SDK to work with that implements the protocol, you'll have to follow it yourself. You can't just invoke them like a normal HTTP function.
If you don't want to implement the protocol, you should instead use a regular HTTP function, and stop using the client SDK in your mobile app.
Upvotes: 1