Using firebase auth token to make graph api calls using JS SDK - javascript

I currently have a Facebook app written with app that uses firebase to authenticate and login to my app. I am getting a access token with the firebase auth. I wanted to use this token to make graph api calls like
FB.api(
'/me/albums',
'GET',
{},
function(response) {
// Insert your code here
console.log(response);
console.log(token);
}
);
I am following the documentation on firebase to make the authentication. The user is successfully created on firebase the user has given permission for photo access. I just am not able to figure out how to use the token to makes calls to Facebook's graph api.
var provider = new firebase.auth.FacebookAuthProvider();
provider.addScope('user_photos');
provider.addScope('user_friends');
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
EDIT
When I make the call after the redirect I get the following error
"An active access token must be used to query information about the current user."

You could do what #luschn said or you could also make a simple http request with facebook api.
$.get(
"https://graph.facebook.com/me",
{
'fields' : fields,
'access_token' : token
},
function(response) {
//enter code here
}
)
You can get the fields from facebook's graph api and access token is the one you got from firebase.

While working with the JS SDK, you don´t have to deal with the User Token after authorization. I am not sure how it works with Firebase, but i assume that you have to add the Token on your own if you want to use the JS SDK after login with Firebase:
FB.api(
'/me/albums', {fields: '...', access_token: token}, function(response) {
console.log(response);
}
);
Also, make sure the Access Token is valid and includes the user_photos permission. You can debug it here: https://developers.facebook.com/tools/debug/accesstoken/
You can also try using the Fetch API instead of the JS SDK to make API calls with the Token from Firebase: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

Related

How to get the user ID token after sign-in on the web

I use firebase-admin for node.js and firebase for react.
I want to get access token to send it to my server in header for defend my data.
I use this code on react app
firebase.auth().signInWithEmailAndPassword(form.elements.email.value, form.elements.password.value)
.then((result) => {
console.log(result)
})
.catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorMessage);
});
but I dont know how to get token from result.
Or i have some ways to work with API on my server?
It should work like this:
if user want to get to admin panel, he should sign in. Client get token, set in cookie ans use with any request?
The ID token is not available in the data returned from signInWithEmailAndPassword. What you can do instead is follow the documentation and use getIdToken() to get the token after sign-in finishes.
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
The linked documentation goes on to explain how to validate the token on the backend using the Firebase Admin SDK.

How to authenticate API calls with Cognito using Facebook?

I want to authenticate users using Cognito, with option to use Facebook. User can sign_in/sign_up using either of those options.
I have created Cognito User Pool and Cognito Federated Identity, and also I have created Facebook App for authentication. Both User Pool and Facebook app are connected to Federated identity.
When I sign_up and later authenticate Cognito User via Cognito User Pool, then Cognito returns accessToken, which I store in localStorage on front and use whenever needed for athentication.
I have /authenticate endpoint (express), that takes in username & password, and returns accessToken if all went well. Whenever I make API call that requires auth, I send accessToken that I have in local storage. It goes, more or less as this:
// POST user/authenticate
const authenticationData = {
Username: username,
Password: password
}
authenticationDetails = new AuthenticationDetails(authenticationData)
const userData = {
Username: username,
Pool: userPool()
}
cognitoUser = new CognitoUser(userData)
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (res) => resolve(res), // here I get accessToken
onFailure: (err) => {
console.log('[authenticateUser error]', err)
reject(err)
},
//...
However
When I use Facebook, I do not get accessToken I could use in same fashion. I get accessToken from Facebook via FB.login, I pass it to Cognito to authenticate, and then I don't know what to do, because I cannot get any token that could be used to authenticate API calls, that require Cognito Authentication.
Here's what I do:
await window.FB.login((response) => {
props.userFacebookSignIn(response)
})
// ...
call(foo, 'users/facebook_sign_in', { accessToken: payload.facebookAccessToken })
// ...
// users/facebook_sign_in
AWS.config.region = config.AWSRegion
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'foo',
Logins: {
'graph.facebook.com': facebookAccessToken
}
})
AWS.config.credentials.get((err) => {
// Here I get no errors, I presume that I have logged Facebook user in
const accessKeyId = AWS.config.credentials.accessKeyId
const secretAccessKey = AWS.config.credentials.secretAccessKey
const sessionToken = AWS.config.credentials.sessionToken
// here I can do stuff probably,
// but I would like to receive token that would allow me to do stuff,
// rather than context I can do stuff in
})
While I am doing all of this, I have this feeling, that devs at AWS implemented Cognito as frontend solution, rather than something to be used in backend. Correct me if I am wrong.
Nevertheless, I would like to be able authenticate api calls using Cognito and Facebook interchangeably in express middleware.
Is that possible? Thanks.
I have used federated identity for salesforce single sign on but i imagine the steps will the same. After authenticating with facebook you will recieve and id_token from them in response. You have to pass this as a parameter in the getId method:
var params = {
IdentityPoolId: 'STRING_VALUE', /* required */
AccountId: 'STRING_VALUE',
Logins: {
'<IdentityProviderName>': 'STRING_VALUE',
/* 'graph.facebook.com': ... */
}
};
cognitoidentity.getId(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
In the result you will get an identity id which you can save somewhere so that you don't have to make this call everytime while authenticating. Now take this identity id and make the getCredentialsForIdentity call:
response = client.get_credentials_for_identity(
IdentityId='string',
Logins={
'string': 'string'
},
CustomRoleArn='string'
)
This will finally give you the temporary access key, secret key and session key you need.
I decided to use oAuth.
Here's quick & dirty look on how it's done
In AWS Cognito
1) Set up Cognito User Pool. Add App Client save App client id & App client secret as COGNITO_CLIENT_ID and COGNITO_CLIENT_SECRET
2) Go to Federation > Identity providers and add your Facebook app ID and App secret (both you will find in Facebook app panel)
3) Go to App integration > App client settings click "Select all", set up your Callback URL, mine is localhost:5000/facebook also select Authorization code grant and Allowed OAuth Scopes (save scopes to say: COGNITO_SCOPES)
4) Now go to App integration > Domain name and enter your custom domain; let's say example-app-debug so it's: https://example-app-debug.auth.us-east-1.amazoncognito.com
That's all there is to Cognito
no the Facebook part
5) Settings > Basic add example-app-debug.auth.us-east-1.amazoncognito.com to your App domains - Save Changes
6) In Facebook Login > Settings in Valid OAuth Redirect URIs add this URL: https://example-app-debug.auth.us-east-1.amazoncognito.com/oauth2/idpresponse and Save Changes
and the code
In browser, redirect user to this url when Login w. Facebook button is clicked:
window.location.href =
`https://example-app-debug.auth.us-east-1.amazoncognito.com/oauth2/authorize` +
`?identity_provider=Facebook` +
`&redirect_uri=http://localhost:5000/facebook` +
`&response_type=code` +
`&client_id=${COGNITO_CLIENT_ID}` +
`&scope=${COGNITO_SCOPES}`
this call should come back to you with a code, like this: http://localhost:5000/facebook?code=foo-bar-code Send this code to your backend.
In backend, do this:
const axios = require('axios')
const url = `` +
`https://${COGNITO_CLIENT_ID}:${COGNITO_CLIENT_SECRET}` +
`#example-app-debug.auth.us-east-1.amazoncognito.com/oauth2/token` +
`?grant_type=authorization_code` +
`&code=foo-bar-code` + // <- code that came from Facebook
`&redirect_uri=http://localhost:5000/facebook` +
`&client_id=${COGNITO_CLIENT_ID}`
const response = await axios.post(url)
// response should have access_token, refresh_token and id_token in data
You send access_token, refresh_token and id_token back to frontend and save them in local storage and use them to authenticate and Done.

Firebase, Auth0, React. The custom token format is incorrect. Please check the documentation

I’m trying to use Auth0 JWT Tokens with Firebase, with no much luck.
When using the token with Firebase:
const token = localStorage.getItem('id_token'); //from auth0
firebase.auth().signInWithCustomToken(token).catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.log(error);
console.log(token);
});
All I get is:
“The custom token format is incorrect. Please check the documentation.”
As far as I saw in Firebase’s documentation Auth0 and Firebase tokens are different:
https://firebase.google.com/docs/auth/admin/create-custom-tokens
Apparently, Firebase expects an uid which is not present in the one generated by Auth0 which uid equivalent is in sub.
I tried to create a rule to modify the Auth0’s token to include a copy of sub named uid to see if this could be a solution, but it’s not working, nothing is added to the body of the token.
function (user, context, callback) {
context.idToken.uid = user.user_id;
callback(null, user, context);
}
Any idea / suggestion?
PS:
1.I checked the token in jwt.io and its valid.
2.I tried reducing the expiring time to less than 5min, as I saw some people considering this a possible solution, but its not.
You can't use an Auth0 token directly with Firebase. You need to create a server-side API that uses the firebase-admin SDK to create a Firebase Custom Token using the Auth0 data.
There's a full tutorial over on the OAuth site. Check out the API Routes section on how to use firebaseAdmin.auth().createCustomToken given the OAuth token:
// Auth0 athentication middleware
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${config.AUTH0_DOMAIN}/.well-known/jwks.json`
}),
audience: config.AUTH0_API_AUDIENCE,
issuer: `https://${config.AUTH0_DOMAIN}/`,
algorithm: 'RS256'
});
// Initialize Firebase Admin with service account
const serviceAccount = require(config.FIREBASE_KEY);
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(serviceAccount),
databaseURL: config.FIREBASE_DB
});
// GET object containing Firebase custom token
app.get('/auth/firebase', jwtCheck, (req, res) => {
// Create UID from authenticated Auth0 user
const uid = req.user.sub;
// Mint token using Firebase Admin SDK
firebaseAdmin.auth().createCustomToken(uid)
.then(customToken =>
// Response must be an object or Firebase errors
res.json({firebaseToken: customToken})
)
.catch(err =>
res.status(500).send({
message: 'Something went wrong acquiring a Firebase token.',
error: err
})
);
});

How to send Facebook authentication details to Firebase using a Cordova plugin & Firebase template

Sorry this question is kind of long, it's because I've been trying to solve this problem for a while and want to make sure I don't leave any info out. I'm building a Cordova app and using Firebase for the authentication/database back end. I've been trying to authenticate users into Firebase using a Log in with Facebook button for almost a week now, but I haven't been able to get it to work.
Originally I tried following Firebase's example here: https://firebase.google.com/docs/auth/web/facebook-login (I need to use the "Advanced: Handle the sign in flow manually" as it is a Cordova Android & iOS app), this example didn't work for me as the link to Facebook's SDK script (//connect.facebook.net/en_US/sdk.js) kept throwing the error:
file://connect.facebook.net/en_US/sdk.js Failed to load resource: net::ERR_FILE_NOT_FOUND
I tried to fix this error in several ways, such as:
Changing it to https://connect.facebook.net/en_US/sdk.js (this resulted in the error: Can't Load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and subdomains of your app to the App Domains field in your app settings. )
Added the links in question to the list of "Valid OAuth redirect URIs" and domains in the Facebook app settings
Storing the file in my local file system (and locally inside the app on my phone)
Including the entire SDK inside the head of my index.html file
None of these attempts worked. So instead I decided to use the plugin cordova-plugin-facebook from here: https://github.com/bisrael/cordova-plugin-facebook
This is the code I'm using to get the user's information from Facebook with the plugin:
function logInWithFacebook(){
CordovaFacebook.login({
onSuccess: function(result) {
console.log(result);
console.log(result.authToken);
// Store or send the user auth/access key here?
// Get user's name
retrieveUserDetails();
if(result.declined.length > 0) {
alert("The User declined something!");
}
},
onFailure: function(result) {
if(result.cancelled) {
alert("The user doesn't like my app");
} else if(result.error) {
alert("There was an error:" + result.errorLocalized);
}
}
});
}
function retrieveUserDetails(){
// Now that the user has authroised the app, make request to CordovaFacebook plugin to get user's name
CordovaFacebook.graphRequest({
path: '/me',
params: { fields: 'name' },
onSuccess: function (userData) {
console.log(userData);
console.log(userData.name);
// Here somehow send the retrieved username and send it to the Firebase function so that it's linked with the auth key.
},
onFailure: function (result) {
if (result.error) {
Error.log('error', 'There was an error in graph request:' + result.errorLocalized);
}
}
});
}
I'm now able to click on a log in button and log in successfully through Facebook. That process is returning a user auth/access key and the user's name from Facebook.
As I understand it, the manual log in flow example in Firebase's docs (https://firebase.google.com/docs/auth/web/facebook-login) takes the key returned from Facebook, converts it into a Firebase key, and then enters the user's newly created Firebase key and their username into Firebase's servers.
This seems pretty straight forward in the following sample code:
function checkLoginState(event) {
if (event.authResponse) {
// User is signed-in Facebook.
var unsubscribe = firebase.auth().onAuthStateChanged(function(firebaseUser) {
unsubscribe();
// Check if we are already signed-in Firebase with the correct user.
if (!isUserEqual(event.authResponse, firebaseUser)) {
// Build Firebase credential with the Facebook auth token.
var credential = firebase.auth.FacebookAuthProvider.credential(
event.authResponse.accessToken);
// Sign in with the credential from the Facebook user.
firebase.auth().signInWithCredential(credential).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
} else {
// User is already signed-in Firebase with the correct user.
}
});
} else {
// User is signed-out of Facebook.
firebase.auth().signOut();
}
}
function isUserEqual(facebookAuthResponse, firebaseUser) {
if (firebaseUser) {
var providerData = firebaseUser.providerData;
for (var i = 0; i < providerData.length; i++) {
if (providerData[i].providerId === firebase.auth.FacebookAuthProvider.PROVIDER_ID &&
providerData[i].uid === facebookAuthResponse.userID) {
// We don't need to re-auth the Firebase connection.
return true;
}
}
}
return false;
}
FB.Event.subscribe('auth.authResponseChange', checkLoginState);
My question is, how can I send the auth key and username returned from the Cordova plugin code, to Firebase's example code so that it works smoothly?
Firebase's example code includes this listener which listens for any change in the Facebook authorization status: FB.Event.subscribe('auth.authResponseChange', checkLoginState); but as this uses Facebook's SDK it won't work with my current set up.
I'm using the following Firebase chat app as a template to work from: https://gist.github.com/puf/8f67d3376d80ed2d02670d20bfc4ec7d as you can see it has a Login with Facebook button, but no code for handling the process, I'm trying to apply parts of the manual log in flow example in Firebase's docs (https://firebase.google.com/docs/auth/web/facebook-login) with data returned from the cordova-plugin-facebook queries, and integrate both with Firebase's chat app template.
I'm really at a loss as to what to do next, I've tried everything I can think of. Any help in solving this problem would be really, really appreciated.
Thank you in advance!
UPDATE
Questions and answers:
How does it work at the moment?
Right now I have a "Facebook Login" button - when this is clicked it runs logInWithFacebook(). This function uses the CordovaFacebook plugin, it also runs the function retrieveUserDetails() after the user signs in with Facebook. retrieveUserDetails() gets some user info from Facebook which I hope to then insert into my Firebase database.
logInWithFacebook() works correctly (it opens up a Facebook login page, and when the user logs in, I'm able to console.log the user's Facebook ID, and the Facebook access Token.
retrieveUserDetails() also works correctly (I'm able to console.log the user's name taken from Facebook).
How do you want it to work?
I'm happy with how the first half of the process is working (the logging in with Facebook and retrieving user details is working correctly). However I want this log in to trigger Firebase's auth state change listener, so that Firebase detects and confirms that the user has logged in:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("User is signed in.");
} else {
console.log("User is not signed in.");
}
});
What is not working the way you want?
The first half of the process is working correctly, but I'm lost when it comes to what to do with the accessToken returned from Facebook. From reading the docs I think that Firebase is supposed to convert this token into a Firebase access token, and then that is used to log the user into Firebase (this would also trigger the above AuthStateChanged function). From there I want to be able to insert any data I've retrieved from Facebook (the user's name etc) into my Firebase database. But the main problem is getting the Facebook accessToken converted into a Firebase login (the second block of code in my original question is where I'm trying to perform the conversion/sign into Firebase).
Because I'm using Cordova, this method (logging into Facebook with a plugin and then handling the conversion of the accessToken) seems to be the only way to log in with Facebook. But I'm totally lost on how to complete the second half.
UPDATE 2
I've trimmed parts from the sample convert-Facebook-token-to-Firebase-token code from the docs so that the Facebook SDK isn't required. And it appears to be working. This is the code after cutting away the SDK related parts:
// First, define the Facebook accessToken:
var FBaccessToken = result.accessToken;
// Build Firebase credential with the Facebook auth token.
var credential = firebase.auth.FacebookAuthProvider.credential(
FBaccessToken);
// Sign in with the credential from the Facebook user.
firebase.auth().signInWithCredential(credential).then(function(user){
console.log("It looks like we signed into Firebase with the Facebook token correctly.");
}, function(error) {
console.log("Something went wrong, user isn't signed into Firebase with the FB token.");
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
I still need to add the user's email from Facebook and try to send that while logging into Firebase too - so that I'll have some identifier for the user in the Firebase console, but this is a good start.
2nd UPDATE
The below code successfully gets user data from Facebook after the user authorizes the app:
CordovaFacebook.graphRequest({
path: '/me',
params: { fields: 'first_name,last_name,email,locale,gender,age_range,picture.width(200).height(200)' },
onSuccess: function (userData) {
console.log(userData)
var first_name = userData.first_name;
var last_name = userData.last_name;
var email = userData.email;
var locale = userData.locale;
var gender = userData.gender;
var min_age = userData.age_range.min;
var profile_picture = userData.picture.data.url;
// Enter user details into the Firebase database:
firebase.database().ref('users/' + uid).set({
first_name: first_name,
last_name: last_name,
email: email,
locale: locale,
gender: gender,
min_age: min_age,
profile_picture : profile_picture
});
console.log("Facebook user data should now be in the database!");
},
onFailure: function (result) {
if (result.error) {
Error.log('error', 'There was an error in graph request:' + result.errorLocalized);
}
}
});
(Just an answer to the last update, as you figured out the rest :))
How to get user email from CordovaFacebook.login()
Looking at the CordovaFacebook documentation you can add a permissions property on the object passed to the login method.
According to the Facebook API documentation the permission name for email is just "email".
I haven't tested, but I think this should work:
CordovaFacebook.login({
permissions: [ 'email' ],
onSuccess: function(result) {
console.log('email:', result.email);
...
},
onFailure: function(result) {
...
}
});

Unable to query Google Search Console API using a Service Account

I need to retrieve some data from Google Search Console (Webmaster Tools) using a service account.
So far I've been able to retrieve an access_token for the service account which I need to append to the url of the request. The problem is that I can't find a way to do so, this is the code i'm using:
function retrieveSearchesByQuery(token)
{
gapi.client.webmasters.searchanalytics.query(
{
'access_token': token,
'siteUrl': 'http://www.WEBSITE.com',
'fields': 'responseAggregationType,rows',
'resource': {
'startDate': formatDate(cSDate),
'endDate': formatDate(cEDate),
'dimensions': [
'date'
]
}
})
.then(function(response) {
console.log(response);
})
.then(null, function(err) {
console.log(err);
});
}
This is the url called by the function:
https://content.googleapis.com/webmasters/v3/sites/http%3A%2F%2Fwww.WEBSITE.com/searchAnalytics/query?fields=responseAggregationType%2Crows&alt=json"
Instead it should be something like this:
https://content.googleapis.com/webmasters/v3/sites/http%3A%2F%2Fwww.WEBSITE.com/searchAnalytics/query?fields=responseAggregationType%2Crows&alt=json&access_token=XXX"
The gapi.client.webmasters.searchanalytics.query doesn't recognize 'access_token' as a valid key thus it doesn't append it to the url and that's why I get a 401 Unauthorized as response.
If I use 'key' instead of 'access_token' the parameter gets appended to the url but 'key' is used for OAuth2 authentication so the service account token I pass is not valid.
Does anyone have a solution or a workaround for this?
If your application requests private data, the request must be authorized by an authenticated user who has access to that data. As specified in the documentation of the Search Console API, your application must use OAuth 2.0 to authorize requests. No other authorization protocols are supported.
If you application is correctly configured, when using the Google API, an authenticated request looks exactly like an unauthenticated request. As stated in the documentation, if the application has received an OAuth 2.0 token, the JavaScript client library includes it in the request automatically.
You're mentioning that you have retrieved an access_token, if correctly received, the API client will automatically send this token for you, you don't have to append it yourself.
A very basic workflow to authenticate and once authenticated, send a request would looks like the following code. The Search Console API can use the following scopes: https://www.googleapis.com/auth/webmasters and https://www.googleapis.com/auth/webmasters.readonly.
var clientId = 'YOUR CLIENT ID';
var apiKey = 'YOUR API KEY';
var scopes = 'https://www.googleapis.com/auth/webmasters';
function auth() {
// Set the API key.
gapi.client.setApiKey(apiKey);
// Start the auth process using our client ID & the required scopes.
gapi.auth2.init({
client_id: clientId,
scope: scopes
})
.then(function () {
// We're authenticated, let's go...
// Load the webmasters API, then query the API
gapi.client.load('webmasters', 'v3')
.then(retrieveSearchesByQuery);
});
}
// Load the API client and auth library
gapi.load('client:auth2', auth);
At this point, your retrieveSearchesByQuery function will need to be modified since it doesn't need to get a token by argument anymore in order to pass it in the query. The JavaScript client library should include it in the request automatically.
You can also use the API Explorer to check what parameters are supported for a specific query and check the associated request.
If you need to use an externally generated access token, which should be the case with a Service Account, you need to use the gapi.auth.setToken method to sets the OAuth 2.0 token object yourself for the application:
gapi.auth.setToken(token_Object);

Categories