react native facebook login using expo client: The App_id in the input_token did not match the Viewing App - firebase

I am using expo-facebook to integrate a Facebook login using expo and firebase. Everything looks to be working and I log into Facebook but get an OAuthException once I authenticate using Facebook as follows:
Unsuccessful debug_token response from Facebook: {"error":{"message":"(#100) The App_id in the input_token did not match the Viewing App","type":"OAuthException","code":100
I have gone through a lot of issues on Stack Overflow, GitHub and looked at expo documentation as well but to no avail.
I have configured the app id and secrets from Facebook into firebase as required as well as set up the OAuth redirect URI to my Facebook app configuration. The code I have put together to setup the login is as follows:
const signInWithFacebook = async () => {
try {
// const { type, token } = await Facebook.logInWithReadPermissionsAsync(
// facebookAppId,
// {
// permissions: ["public_profile"],
// }
// );
const appId = Constants.manifest.extra.facebook.appId;
const permissions = ["public_profile"]; // Permissions required, consult Facebook docs
await Facebook.initializeAsync({
appId: appId,
});
const { type, token } = await Facebook.logInWithReadPermissionsAsync({
permissions: permissions,
});
console.log(type);
console.log(token);
if (type === "success") {
await firebase
.auth()
.setPersistence(firebase.auth.Auth.Persistence.LOCAL);
const credential = firebase.auth.FacebookAuthProvider.credential(token);
const facebookProfileData = await firebase
.auth()
.signInWithCredential(credential);
//this.onLoginSuccess.bind(this);
console.log(facebookProfileData);
}
} catch ({ message }) {
console.log(message);
alert(`Facebook Login Error: ${message}`);
}
};
I have also setup the relevant configurations in the app.json as follows:
"expo":{
"facebookScheme": "fb123243435566",
"facebookAppId": "123243435566",
"facebookDisplayName": "myapp"
}
The only aspect I am not sure about is where to grab the facebookScheme. Currenltly I have assumed it's fb+AppID. the documenatation mentioned here https://docs.expo.dev/versions/latest/sdk/facebook/
isn't clear. It states:
Configure app.json.
Add the field facebookScheme with your Facebook login redirect URL scheme found here under "4. Configure Your info.plist." It should look like "fb123456". If you do not do this, Facebook will not be able to redirect to your app after logging in.
But I am not sure how to grab that facebookScheme id. I suspect this is where the issue is as expo states that.
Expo Go from the Android Play Store will use the Facebook App ID that you provide, however, all Facebook API calls in the Expo Go from the iOS App Store will use Expo's own Facebook App ID. This is due to underlying configuration limitations.
so I am assuming the facebookScheme is some kind of workaround.
Although I am not sure if it's a working around for the ios standalone app or the expo managed.

Try this:
const { type, token } = await Expo.Facebook.logInWithReadPermissionsAsync(appId, {
permissions: [‘public_profile’, ‘email’],
behavior: Platform.OS === ‘android’ ? ‘web’ : ‘system’,
});
it should specify for the app to open a web browser on login
here's the ref:
https://forums.expo.dev/t/how-to-fix-standalone-android-expo-facebook-login/23922

Related

Trying to authenticate a service account with firebase-admin from a Cloud Scheduler call? Error: Firebase ID token has incorrect "iss" (issuer) claim

I'm trying to authenticate an API call made to my server (on Cloud Run) from a Cloud Scheduler cron job.
I'm trying to use a service account to this.
Note: this is all happening inside the same Project.
References:
https://cloud.google.com/scheduler/docs/http-target-auth
This is what I'm doing:
STEP 1 - Create the service account
I went to https://console.cloud.google.com/apis/credentials and created a new service account. I've assigned the role as owner.
STEP 2 - Create the cron job.
I went to https://console.cloud.google.com/cloudscheduler to create the cron job like I always do.
In the service account field I've put my service account e-mail. In the Audience field, I've put my project id because at some point I got an error saying that it was expecting it to be the name of my project id.
This was the error:
Firebase ID token has incorrect "aud" (audience) claim. Expected "PROJECT_ID"
STEP 3 - Running the job and identify decoding the token:
This is the code on my server:
import * as admin from "firebase-admin";
admin.initializeApp({
credential: admin.credential.cert(
// THIS IS THE DEFAULT FIREBASE-ADMIN SERVICE ACCOUNT
// THAT IS AUTOMATICALLY CREATED BY FIREBASE
SERVICE_ACCOUNT as admin.ServiceAccount
)});
// THIS IS THE CODE THAT IS INSIDE MY SERVER TRYING TO VERIFY THE SERVICE ACCOUNT
try {
const authHeader = req.headers.authorization;
console.log(`authHeader: ${authHeader}`);
if (authHeader) {
const idToken = authHeader.split(" ")[1]; // GETS THE USER ID TOKEN
const decodedToken = await admin.auth().verifyIdToken(idToken);
console.log(`decodedToken: ${decodedToken}`);
}
}
And this is the error I'm currently getting:
Firebase ID token has incorrect "iss" (issuer) claim. Expected "https://securetoken.google.com/"my-project-id" but got "https://accounts.google.com". Make sure the ID token comes from the same Firebase project as the service account used to authenticate this SDK. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
Is there anything wrong with the way I'm doing it? Should I not be using firebase-admin to this?
Should I be using google-auth-library to verify the token?
https://github.com/googleapis/google-auth-library-nodejs#verifying-id-tokens
https://developers.google.com/identity/sign-in/web/backend-auth
After a morning in hell trying to debug this, here is what I've found.
It seems that firebase-admin's admin.auth().verifyIdToken() only works for tokens generated from the firebase SDK.
I got it to work by using the google-auth-library directly.
I did the following.
NOTE: The rest of the code is the same (using the same service account as described in the question):
import { OAuth2Client } from "google-auth-library";
export const apiExpressRouteHandler: RequestHandler = async (req, res) => {
try {
const PROJECT_ID = process.env.PROJECT_ID;
const authHeader = req.headers.authorization;
if (authHeader) {
const client = new OAuth2Client(PROJECT_ID);
const ticket = await client.verifyIdToken({
idToken: authHeader.split(" ")[1],
audience: PROJECT_ID
});
// LOGGING ticket PROPERTIES
console.log(`userId: ${JSON.stringify(ticket.getUserId())}`);
console.log(`payload: ${JSON.stringify(ticket.getPayload())}`);
console.log(`envelope: ${JSON.stringify(ticket.getEnvelope())}`);
console.log(`attributes: ${JSON.stringify(ticket.getAttributes())}`);
}
// REST OF THE CODE
}
}
catch(err) {
// ...
}
I'm not sure if the PROJECT_ID is necessary to initialize the client with new OAuth2Client(PROJECT_ID); but it is working like this.

Programmatically add Google Analytics to Firebase project

I wish to automate all of the steps involved in setting up a new Firebase project without any user interaction. I've accomplished most of the steps via either the gCloud CLI, Firebase CLI or the GoogleApis NodeJS library.
Authentication has been done via the CLI tools or via service accounts.
The only thing I haven't been able to do so far is adding Google Analytics to the newly created Firebase project. I have found this Google Api which should accomplish this, but I'm having problems authenticating the request.
How would I authenticate a request to this API without any user interaction? The API is not available via the CLI tools, so my best guess would be to use a service account with the owner IAM-role, but the request keeps failing.
My steps so far have been:
Ensuring that the management API is enabled
Add a service account to the GCloud project with owner privileges
Download the service account
Run the following code
import { google } from 'googleapis';
import * as fetch from 'node-fetch';
async function addGoogleAnalytics {
const token = await getJWTAcessToken();
await addAnalyticsFetch(token);
};
async function addAnalyticsFetch(accessToken) {
const url = `https://firebase.googleapis.com/v1beta1/projects/<my-project-id>:addGoogleAnalytics`;
const fetchResult = await fetch(url, {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
json: true,
body: JSON.stringify({ analyticsAccountId: '<my-analytics-account-id>' }),
});
const fetchResultText = await fetchResult.text();
console.log('Fetch result: ', fetchResultText);
}
function getJWTAcessToken() {
const SCOPES = ['https://www.googleapis.com/auth/cloud-platform'];
const key = require('../../serviceAccount.json');
return new Promise((resolve, reject) => {
const jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, SCOPES, null);
jwtClient.authorize((err, tokens) => {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
The result of the API call is a simple 403 - "The caller does not have permission".
I've also attempted this using the GoogleApis NodeJS library with similar results.
If being a project owner doesn't give enough privileges, how do I permit this service account to perform this API call? I have a suspicion that I'm failing because the service account is in no way associated with the Google Analytics account, but the documentation doesn't list that as a requirement. It is also not listed as a step in Google's own guide.
It turns out that the above code is 100 % valid. The problem was indeed that the service account had enough privileges to edit the Firebase-project, but it had no authorization to create a new property for the Google Analytics account.
After giving the service account edit privileges for the Google Analytics account, the connection between Firebase and Google Analytics was successfully established. This process can be automated via this API.

Error on firebase admin nodejs Permission iam.serviceAccounts.signBlob is required

im using this tutorial:
https://firebase.google.com/docs/auth/admin/create-custom-tokens#using_a_service_account_id
to create a node.js function (deployed to google cloud functions) to authenticate my users. the function is super simple:
const admin = require('firebase-admin');
admin.initializeApp({
serviceAccountId: 'authenticator#igibo-b0b27.iam.gserviceaccount.com'
});
exports.authenticate = (req, res) => {
let pass;
let uid;
if (req.query) {
if (req.query.v == 3) {
pass = req.query.p;
uid = req.query.u;
}
admin.auth().createCustomToken(uid)
.then(function(customToken) {
res.status(200).send(customToken);
return customToken;
})
.catch(function(error) {
console.error("Error creating custom token:" + JSON.stringify(error));
res.status(400).send(error);
});
} else {
console.error("EMPTY to authentication");
res.end();
}
};
but im getting this annoying error:
{"code":"auth/insufficient-permission","message":"Permission iam.serviceAccounts.signBlob is required to perform this operation on service account projects/-/serviceAccounts/authenticator#igibo-b0b27.iam.gserviceaccount.com.; Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens for more details on how to use and troubleshoot this feature."}
in the very same tutorial it says i must go to IAM and adjust some roles for the service account WHICH I DID but still getting this error.
this is a absolutelly simple task and shouldn't being such a hassle...
what i am forgetting? the id is correct! the role is correct! the code is correct!
what is wrong?
Firebase mentions about this error on its docs:
https://firebase.google.com/docs/auth/admin/create-custom-tokens#failed_to_determine_service_account
You must initialize your app correctly through a JSON config file.
A simple fix would be:
Go to
https://console.cloud.google.com/iam-admin/iam?project=PROJECT_NAME
Edit your default service account.
Add the role Service Account
Token Creator
In a few minutes your project will be able to create signed tokens.

Can you use IAP to log in to Firebase?

I have an angular app that is protected with Identity Aware Proxy (IAP). I am trying to add Firebase to this app in order to use firestore for a component using AngularFire. I don't want to make the user log in twice, so I thought about using IAP to authenticate with Firebase.
I've tried:
Letting GCP do its magic and see if the user is automatically inserted the Firebase Auth module - it isn't.
I've tried using the token you get from IAP in the GCP_IAAP_AUTH_TOKEN cookie with the signInWithCustomToken method - doesn't work, invalid token.
I've tried using getRedirectResult after logging in through IAP to if it's injected there - it isn't.
I've spent days trying to get this to work, I've had a colleague look at it as well, but it just doesn't seem possible. Now, as a last resort, I'm writing here to see if someone knows if it's even possible.
If not, I will have to suggest to the team to switch auth method and get rid of IAP, but I'd rather keep it.
More info:
Environment: NodeJS 10 on App Engine Flexible
Angular version: 7.x.x
AngularFire version: 5.2.3
Notes: I do not have a backend, because I want to use this component standalone and at most with a couple of Cloud Functions if need be. I am trying to use Firestore as a "backend".
I managed to authenticate on Firebase automatically using the id token from the authentication made for Cloud IAP.
I just needed to use Google API Client Library for JavaScript
1) Add the Google JS library to your page i.e. in
<script src="https://apis.google.com/js/platform.js"></script>
2) Load the OAuth2 library, gapi.auth2
gapi.load('client:auth2', callback)
gapi.auth2.init()
3) Grab the id token from GoogleAuth:
const auth = gapi.auth2.getAuthInstance()
const token = auth.currentUser.get().getAuthResponse().id_token;
4) Pass the token to GoogleAuthProvider's credential
const credential = firebase.auth.GoogleAuthProvider.credential(token);
5) Authenticate on Firebase using the credential
firebase.auth().signInAndRetrieveDataWithCredential(credential)
Putting everything together on an Angular component, this is what I have (including a sign out method)
import { Component, isDevMode, OnInit } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/auth';
import { Router } from '#angular/router';
import * as firebase from 'firebase/app';
// TODO: move this all to some global state logic
#Component({
selector: 'app-sign-in-page',
templateUrl: './sign-in-page.component.html',
styleUrls: ['./sign-in-page.component.scss']
})
export class SignInPageComponent implements OnInit {
GoogleAuth?: gapi.auth2.GoogleAuth = null;
constructor(public auth: AngularFireAuth, private router: Router) { }
async ngOnInit(): Promise<void> {
// The build is restricted by Cloud IAP on non-local environments. Google
// API Client is used to take the id token from IAP's authentication and
// auto authenticate Firebase.
//
// GAPI auth: https://developers.google.com/identity/sign-in/web/reference#gapiauth2authorizeparams-callback
// GoogleAuthProvider: https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider
if (isDevMode()) return;
await this.loadGapiAuth();
this.GoogleAuth = gapi.auth2.getAuthInstance();
// Prevents a reauthentication and a redirect from `/signout` to `/dashboard` route
if (this.GoogleAuth && this.router.url === "/signin") {
const token = this.GoogleAuth.currentUser.get().getAuthResponse().id_token;
const credential = firebase.auth.GoogleAuthProvider.credential(token);
this.auth.onAuthStateChanged((user) => {
if (user) this.router.navigate(["/dashboard"]);
});
this.auth.signInAndRetrieveDataWithCredential(credential)
}
}
// Sign in button, which calls this method, should only be displayed for local
// environment where Cloud IAP isn't setup
login() {
this.auth.useDeviceLanguage();
const provider = new firebase.auth.GoogleAuthProvider();
provider.addScope("profile");
provider.addScope("email");
this.auth.signInWithRedirect(provider);
}
logout() {
this.auth.signOut();
if (this.GoogleAuth) {
// It isn't a real sign out, since there's no way yet to sign out user from IAP
// https://issuetracker.google.com/issues/69698275
// Clearing the cookie does not change the fact that the user is still
// logged into Google Accounts. When the user goes to your website again,
// opens a new tab, etc. The user is still authenticated with Google and
// therefore is still authenticated with Google IAP.
window.location.href = "/?gcp-iap-mode=CLEAR_LOGIN_COOKIE"
}
}
private async loadGapiAuth() {
await new Promise((resolve) => gapi.load('client:auth2', resolve));
await new Promise((resolve) => gapi.auth2.init(GAPI_CONFIG).then(resolve));
}
}
given the nature of IAP and Firebase, it seems not to be possible. The workaround could be just as mentioned in previous comments, to implement a custom provider, but you should mint your own token. Then maybe, re-thinking your solution if maybe this is the best way to achieve your goals.
I'm not experienced with Google Identity Aware Product, but my expectation is that you'll have to implement a custom provider for Firebase Authentication. The key part that you're missing now is a server-side code that take the information from the IAP token and mints a valid Firebase token from that. You then pass that token back to the client, which can use it to sign in with signInWithCustomToken.

Firebase auth - login user from app in website

We have an App that uses firebase auth. Now we would like to send the user from the app to our website in order for him to perform some actions.
Is it somehow possible to automatically login the user when he comes from the App? He has already provided his login credentials and we have the auth object from firebase. Can we pass a token or somthing to our website and have the firebase SDK on the site log him automatically?
The common way to do this right now is as follows:
After the user signs in in one website, get the ID token via currentUser.getIdToken()
Post the ID token to your backend to serve the destination website.
On your backend, using Admin SDK, get the ID token, verify it, check the auth_time to make sure it is a recent login to minimize any window of attack. If everything checks out, mint a custom token via admin sdk createCustomToken.
Return the custom token to the website.
On the client side, sign in with custom token: signInWithCustomToken and the same user is now signed in on the website.
I was able to do this a bit easier, without having my own backend server to create a custom token, as long as you are using Google auth. It might work for other providers but here's how I did it with Google auth on Android:
Android:
activity.startActivityForResult(
googleSignInClient.signInIntent,
object : ActivityWithResultListener.OnActivityResultListener {
override fun onActivityResult(resultCode: Int, data: Intent?) {
if (data != null) {
val credential = GoogleAuthProvider.getCredential(account.idToken!!, null)
idToken = account.idToken!!
// Then pass the token to your webview via a JS interface
}
)
Web:
const idTokenFromApp = AndroidBridge.getAuthToken(); // This is the idToken from the Android code above
if (idTokenFromApp) {
// from https://firebase.google.com/docs/auth/web/google-signin
// Build Firebase credential with the Google ID token.
// https://firebase.google.com/docs/reference/js/v8/firebase.auth.GoogleAuthProvider#static-credential
const credential = firebase.auth.GoogleAuthProvider.credential(idTokenFromApp);
// Sign in with credential from the Google user.
firebase.auth.signInWithCredential(credential).catch((error) => {
// Handle Errors here.
const errorCode = error.code;
const errorMessage = error.message;
logError(`Error logging in: ${errorCode} ${errorMessage} token: ${idTokenFromApp}`, error);
});
}

Resources