SigningError with Firebase getSignedUrl() - firebase

I'm trying to use file.getSignedUrl() to get the download URL from Firebase Storage via Google Cloud Functions (Nodejs). I'm getting this error in the Cloud Functions console:
{ SigningError: A Forbidden error was returned while attempting to retrieve an access token for the Compute Engine built-in service account. This may be because the Compute Engine instance does not have the correct permission scopes specified. Permission iam.serviceAccounts.signBlob is required to perform this operation on service account projects/myapp-cd94d/serviceAccounts/myapp-cd94d#appspot.gserviceaccount.com.
at SigningError (/user_code/node_modules/#google-cloud/storage/build/src/file.js:58:9)
at authClient.sign.then.catch.err (/user_code/node_modules/#google-cloud/storage/build/src/file.js:1019:22)
at process._tickDomainCallback (internal/process/next_tick.js:135:7) name: 'SigningError' }
I copied the code from the Add the Firebase Admin SDK to Your Server documentation. I have my serviceAccountKey.json in my functions folder. firebase deploy isn't given me the error
Error parsing triggers: Cannot find module 'serviceAccountKey.json'
so I must have the right path to my serviceAccountKey.json. I even generated a new private key, that didn't fix the problem. I have firebase-admin 6.1.0 and firebase-tools 6.1.0. Here's the relevant parts of my code:
const admin = require('firebase-admin');
var serviceAccount = require("./myapp-cd94d-firebase-adminsdk-1234x-sEcReT.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://myapp-cd94d.firebaseio.com"
});
...
const config = {
action: 'read',
expires: '03-17-2025'
};
file.getSignedUrl(config).then(function(data) {
const url = data[0];
console.log(url);
})
.catch(function(error) {
console.error(error);
})
I saw that Doug Stevenson's answer has different code but it appears to be equivalent to the code in the documentation.

The answer has to do with Cloud Identity and Access Management. First, go to your Google Cloud Platform IAM & admin page. You'll see various service accounts. Look for the service account that looks like myapp-cd99d#appspot.gserviceaccount.com. It should say App Engine default service account in the Name column. (If an error message referenced a different service account, find that service account.)
In the Role column, you may or not see some roles. If you're getting a SigningError message, the Role column is missing the role Service Account Token Creator. Check the checkbox to the left of myapp-cd99d#appspot.gserviceaccount.com to select the service account, and then click the pencil to the right to edit it. In the next screen, click +ADD ANOTHER ROLE. Scroll down to Service Accounts, select Service Account Token Creator, and save. Now you should see Service Account Token Creator in the Roles column for App Engine default service account. Now you have permission to create signed tokens.
Next, repeat these steps and add a role for Storage Object Creator. This will allow you to run getSignedURL().
You could save alternatively assign Service Account Admin and Storage Admin, which include the Service Account Token Creator and Storage Object Creator roles respectively, plus other roles.
Now, if you instead got a SingingError message, it might be because you're warbling Bruce Springsteen's "Glory Days" out of tune. :-)

In my case I had enable Identity and Access Management (IAM), the url is the following one:
https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project="YOUR
PROJECT NAME"

Related

Creating Google Calendar events with a GCP Service Account

I would like to rig things so that my GCP service account can invite users to calendar events. So for example my-service-account#myproject.iam.gserviceaccount.com would invite user#myCorporation.com to an event. It seems to me that this should be possible simply by giving my-service-account#myproject.iam.gserviceaccount.com permission to use the Calendar API, without having user#myCorporation.com grant any additional permissions.
I tried to implement this example, but replaced the compute scope and the compute API calls with the calendar scope and calendar API calls. My code is returning the error
Insufficient Permission: Request had insufficient authentication scopes.
I've poked around on the internet a bunch, and I cannot tell if the problem is that I did something wrong or if the problem is that Google does not support what I'm trying to do.
Here is my code:
const {google} = require('googleapis');
const compute = google.compute('v1');
const {GoogleAuth} = require('google-auth-library');
async function main() {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/compute']
});
//keeping the compute stuff in as a sanity check
//to ensure that the problem is with calendar, not something more general
const authClient = await auth.getClient();
const project = await auth.getProjectId();
const res = await compute.zones.list({project, auth: authClient});
console.log(res.data);
createEvent(auth);
}
/**
* Lists the next 10 events on the user's primary calendar.
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function createEvent(auth) {
const calendar = google.calendar({version: 'v3', auth});
calendar.events.insert({
calendarId: 'primary',
event: {
"description": "my test event",
"start": {
"date": "2020-05-20",
},
attendees: [{email: "myGuest#mailinator.com"}]
}
}
);
}
main().catch(console.error);
Answer:
You need to enable the APIs and provide scopes in three places: in your auth code, in the GCP console, and the Google Admin console.
More Information:
As I explained in the comments, the code you have provided should run without issue. The Insufficient Permission: Request had insufficient authentication scopes. error is a result of the service account not being given access to the required scopes somewhere on Google's side.
Make sure you have completed the following steps:
Provided the scopes in the application as an auth object (which you have already done):
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/compute']
});
Enabled the Calendar API in the GCP console for your Service Account GCP Project.
Provided the required scopes for your service account in the OAuth consent screen settings in GCP.
Added the required scopes to the service account in the Google Admin console. This is done by following the Security > Advanced Settings > Manage API client access UI elements, and assigning all scopes the service account needs to the service account client ID.
Note: This final step must be done by a domain admin and can not be done by anyone who is not.
In this case, you will need to contact your domain admin to give your project API access.
References:
Google API Console
Google Admin Console
Related Answers:
Google Calendar API. Adding an event to someone calendar throws error “Error 401: invalid_client” just when authenticating

firebase react-native: convert anonymous user to a permanent user via phone auth

My react-native application allows users to sign in anonymously using firebase. After that I am hoping to allow user to create an account via firebase phone auth and convert this anonymous account into a permanent account.
I think this is possible based on the api & documentation provided by firebase
anonymous account creation:
https://rnfirebase.io/docs/v5.x.x/auth/reference/auth#signInAnonymously
phone auth creation & retrieve credential:
https://rnfirebase.io/docs/v5.x.x/auth/reference/PhoneAuthProvider#credential
Linking an existing user with a new credential:
https://rnfirebase.io/docs/v5.x.x/auth/reference/User#linkWithCredential
https://firebase.google.com/docs/auth/web/account-linking
Here's what I did
anonymous account creation:
const {user: {uid: userID}} = await firebase.auth().signInAnonymously();
// successfully created and save this user's id
get the users to authenticate and get the phone auth credential:
const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, code);
at this point I would have a valid credential & an existing anonymous user ready for linking
I then provide the phone auth credential to my anonymous user for linking
const authUser = await firebaseAuth.currentUser.linkWithCredential(credential);
I expected a successful linking to happen based on my inputs, but I'm getting errors instead, the error I'm getting is
Error: This credential is already associated with a different user account.
Error code is: ERROR_CREDENTIAL_ALREADY_IN_USE
in the documentation under ERROR_CREDENTIAL_ALREADY_IN_USE, they did mention:
this error could be thrown if you are upgrading an anonymous user to a Google user by linking a Google credential to it and the Google credential used is already associated with an existing Firebase Google user.The fields error.email, error.phoneNumber, and error.credential (AuthCredential) may be provided, depending on the type of credential. You can recover from this error by signing in with error.credential directly via auth#signInWithCredential.
But I have not been able to find error.credential under the error object to come back from the error.
I have seen many working example of linking anonymous user with other auth providers, but never seen the linking of a firebase anonymous user with a firebase phone auth crendential. Does anyone know if this type of linking is supported? Could anyone point out what I'm doing wrong here?
Follow up:
So I actually found out that my code did work, and that is how you'd implement firebase anonymous user -> firebase phone auth account linking.
The reason this didn't work for me was because..... for our app, we had a micro-service creating accounts in the backend every time the user inputs the confirmation code. In hindsight I should have really double checked our own codebase, lesson learned.

What service account permissions are required for firebase cloud messaging on app engine?

we have an app engine app that we want to use to send push notifications through fcm. We're using the firebase admin sdk (go) and authorize with a service account json. It seems like the roles required on the service account are different once the app is deployed. Using dev_appserver.py Firebase Admin is enough, but once deployed I can't seem to find a role other than Project Owner that would let the app post messages. Anything less and it seems like the app throws a http error status: 403; reason: sender id does not match regisration token; code: mismatched-credential; details: The caller does not have permission.
What roles are required for a service account to send messages to fcm on an app engine app? Project Owner works, but, would like to avoid that if possible.
There is no existing role that I know that allows to send push notifications through FCM, so you'll have to create a custom role yourself and give it the cloudmessaging.messages.create permission as described in the Firebase IAM Permissions documentation

How to create Firebase Authentication claims?

I am developing an Android app that needs to authenticate with a server application. The server application is a Spring Boot app that uses Spring Security. The server app uses a custom authentication provider to set the Granted Authorities for the user.
This is the code that I am using to retrieve the idToken:
FirebaseToken firebaseToken = null;
try {
FirebaseApp app = FirebaseUtil.getFirebaseApp();
firebaseToken = FirebaseAuth.getInstance(app).verifyIdTokenAsync(authenticationToken.getIdToken()).get();
} catch ( InterruptedException | ExecutionException | CancellationException e ) {
throw new AuthenticationServiceException(e.getMessage());
}
return firebaseToken;
One I have the Firebase Token I can retrieve the claims like this:
Map<String, Object> claims = token.getClaims();
I can then iterate over the claims and create the authorities like so:
List<GrantedAuthority> authorities = Lists.newArrayList();
authorities.add(new SimpleGrantedAuthority("some_role"));
What I can't figure out is how to create the claims using the Firebase Console. Is this possible? I suspect that I need to use the Firebase Database but can't find exactly what I'm looking for in the firebase docs.
Custom claims for Firebase Authentication can currently only be created through the Firebase Admin SDKs. From the documentation on creating custom claims:
// Set admin privilege on the user corresponding to uid.
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
It's not a bad idea to allow creating claims from the console too, so I'd recommend you file a feature request.
If you've been working with the Firebase Emulator Suite on localhost, you've probably noticed that you can directly edit the user in that console to add a custom claim like {"role":"admin"}. Just go to the Authentication tab, click the overflow menu for a specific user, select "Edit User", and set the custom claim in a text box.
You're probably here because you've discovered that the "Edit User" option doesn't appear in the production Firebase console, so you need to figure out how to do the same thing with code. Here are the steps I took to set a custom claim from Windows using Python, a service account, and the Firebase admin tools. Steps should be similar on Linux or OSX. This assumes you already have python and pip installed.
Download your credentials for a service user by starting here. You'll use the file you download in step 3.
https://console.firebase.google.com/project/<your_project>/settings/serviceaccounts/adminsdk
Install the firebase-admin tools for Python from a terminal (For Linux or OSX, you probably need to precede this command with sudo):
pip install firebase-admin
Open a python terminal by running python from the command-line and execute these lines of code:
import firebase_admin
from firebase_admin import credentials, auth
cred = credentials.Certificate("c:\\Users\\<path_to_my_credentials_file>.json")
default_app = firebase_admin.initialize_app(cred)
user = auth.get_user_by_email('<user_email_address>')
auth.set_custom_user_claims(user.uid, {'role': 'admin'})
# Verify the change worked:
user = auth.get_user_by_email('<user_email_address>')
print(user.custom_claims)
You can also easily do it with Python with:
auth.set_custom_user_claims(user_id, {"admin":True}, app=None)

Cloud function authentication with limited privileges

I am creating a RESTful API for my firebase database using Cloud functions. All the tutorials and guides I found suggest using firebase-admin to get admin access to the database.
Here is my question:
Is there a way to access the database the same way as the user would have directly? Like getting the auth token from the user and passing it on to the database to make changes the same way the user would. The security rules should prevent me if I try to make changes to a different user.
Possible solution:
Access the database using the built-in REST API of the database and use ?auth=CREDENTIAL to authenticate the user. Is there an alternative using the firebase SDK?
Is there a way to access the database the same way as the user would have directly?
Yes there is, I recommend you check out the sample here. But it requires that your code decodes and verifies the JWT of that user. The easiest way to do this is with the Admin SDK.
The sample uses the JavaScript SDK to get the token from the client:
firebase.auth().currentUser.getToken()
It passes this token to the server, which then verifies it using the Admin SDK:
admin.auth().verifyIdToken(idToken)
You can then create a second instance of the admin SDK and set this UID for your database requests:
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://databaseName.firebaseio.com",
databaseAuthVariableOverride: {
uid: "my-service-worker"
}
});

Resources