I am trying to access multiple users' Google Calendars in a search functionality with Flutter and Firebase Functions in Node.js.
I originally ask for user permission here in the app (Flutter):
clientViaUserConsent(_credentialsID, _scopes, prompt)
.then((AuthClient client) async {
The credentials for the user are then stored locally and in Firestore via a restricted Firebase Function for security reasons.
Calling Firebase Function (Node.js):
FirebaseFunctions.instance
.httpsCallable('addCalendar')
.call(<String, dynamic>{
'calendarAuthToken': client.credentials.accessToken.data,
'calendarRefreshToken': client.credentials.refreshToken!,
'calendarExpiration':
client.credentials.accessToken.expiry.toString(),
'idToken': client.credentials.idToken.toString()
});
Firebase Function:
return await admin.firestore().collection('[example text]').doc('[example text]').set({
CalendarAccessToken: calendarAccessToken,
CalendarRefreshToken: calendarRefreshToken,
CalendarExpiration: calendarExpiration,
// CalendarIDToken: calendarIDToken
}).then(() => {
I have a search functionality which needs access to all users' calendars. Obviously this should never be done locally due to the sensitivity of the data, so I am trying to do this in a Firebase Function as well.
exports.isAvailableNow = functions.https.onCall(async (data, context) => {
const id = data.id;
const cal= await admin.firestore().collection('[example text]').doc(id)
Should I be reconstructing user's credentials in the Firebase Function, or should I instead be using a service account? The reconstruction could get blocked because it is running on Firebase, not locally like the user permitted. If I need to use a service account, how does that change the original authorization in flutter?
EDIT: Changed the title
UPDATE: I managed to reconstruct the authentication for the user on the server-side, but my fear has become a reality. Google is saying I am not authorized for the user. How do I authorize a user on the client then access their data on the backend?
Related
I am using the MERN stack for an app im building. In this app im using twilio. I have decided to use twilio sub-accounts. The way this works is I create a MASTER twilio account that give me an accountSid and authToken.
I can store these as ENV variables in Heroku when I want to deploy, and anytime I need to access these ENV vars I can just use the process.env.AUTH_TOKEN in my Node.js server.
Every customer that signs up for my app is going have their own subaccount that is a child of my MASTER account. When this sub account is created, It will give that user their own accountSid and authToken.
This is where my issue stands, Do I need to store each users authToken on Heroku as an ENV variable?
ex..
process.env.USER_1_AUTH_TOKEN
process.env.USER_2_AUTH_TOKEN
process.env.USER_3_AUTH_TOKEN
process.env.USER_4_AUTH_TOKEN
process.env.USER_5_AUTH_TOKEN
I dont think this will work because how will I know which users auth token belongs to them?
Currently in Development I am storing the sub-account authToken directly on the user object, this user object is visible to the client side of the app and im worried that exposing the auth token directly to the client could result in some sort of hack?
Is it safe to store the auth token on the user object directly in mongodb and whenever my react app needs the user, just send the user object without the auth token?
Should I create a auth-token Model, and store a document in the auth-token model containing the auth-token and user_id and everytime I need the auth token just query mongodb for the auth-token with user_id as a parameter?
How does one go about storing say 100,000 of these auth-tokens?
I'm worried about security and twilio docs dont say much about this...
According to the Subaccounts API documentation you can use the Twilio rest API to instantiate a subaccount and assign that subaccount a friendly name that is easy to retrieve.
client.api.v2010.accounts
.create({friendlyName: 'Submarine'})
.then(account => console.log(account.sid));
This returns an object that contains a lot of information but it has a unique SID for that is associated to that new number/subaccount. That object is then also linked back to your main account via the owner_account_sid which is attached to that object.
Twilio provides functionality in the subaccount API to allow you to retrieve a subaccounts data based on the friendly name like so...
client.api.v2010.accounts
.list({friendlyName: 'MySubaccount', limit: 20})
.then(accounts => accounts.forEach(a => console.log(a.sid)));
So what you should be doing is as follows...
Create a naming convention within your system that can be used to form friendly names to assign to subaccounts.
Use the API to create a new subaccount with the Twilio API under the friendly naming convention you've developed.
Anytime you want to make a call, text, or other supported action from the Twilio API first perform an API action to look up the SID of that account by the friendly name.
Grab the sid the friendly name returns and attach it to your client object like so require('twilio')(accountSid, authToken, { accountSid: subaccountSid });
Perform your action through that client using the subaccount Sid that is now attached.
I would like to add some additional info for anyone using twilio subaccounts.
So what I did was create a master account with twilio. This gives you an accountSid and authToken.
These you can store in Heroku under config vars.
When you create your login function for a user via passport login, google-passport or some custom login you create, make your api call to create a subaccount. ( Like when you buy numbers, you buy them under your main account and drill them to your sub accounts) When this is created ONLY add the sub account accountSid to your user object. the sub accountSid is worthless without the auth-token and since you need to make an api call with your env vars your sub account auth tokens are safely stored in twilio.
Now whenever you need to make twilio api calls, say for sending a message or making a phone call etc... first make a call to this endpoint
client.api.v2010.accounts(subActServiceSid)
.fetch()
.then(account => account.authToken);
const accountSid = keys.accountSid // master accountSid
const authToken = keys.authToken // master authToken
// these will be stored in heroku
const listChatMessages = async (req, res) => {
const { subActServiceSid } = req.user // getting sub accountSid from user object
const subActAuthToken = client.api.v2010.accounts(subActServiceSid)
.fetch()
.then(account => account.authToken);
const subClient = require('twilio')(subActServiceSid, subActAuthToken)
await subClient.messages.list({ // make all the api calls your heart desires
from: chat.phone
})
.then( messages => messages.forEach((m) => { console.log("message", m})
this will contain a JSON object with the sub accounts authToken. You can then use this authToken for the API call. No need to worry about storing 100,000 users authTokens somewhere.... If this is still confusing message me.
I was building a project on node.js recently, I came across this bug of user authentication. The problem is that after I log in from one account, if at the same time I refresh the home page of the website from another device, it redirects to the dashboard of the first user account. In other words, a single user is getting logged in on all the devices over the network. If suppose I don't refresh the page, and I log in normally on the application, it works fine. This problem is happening, both on the localhost as well as after hosting it on Heroku. Technologies used- node.js for handling back-end views and URLs. HTML, CSS, JavaScript for the front-end. Firebase for the database and authentication. Here is the code for the login part-
const express = require("express");
const path = require("path");
//Create Router Object.
const router = express.Router();
//Main Login Page (GET)
router.get("/", (request, response) => {
response.sendFile(path.resolve("./views/html/login.html"));
});
//Main Login Page (POST)
router.post("/", (request, response) => {
let email = request.body.email;
let password = request.body.password;
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
firebase.auth().signInWithEmailAndPassword(email, password)
.then(r => {
let user = firebase.auth().currentUser;
if (user.emailVerified)
response.redirect('/dashboard');
else
response.send("<h1>Please Verify your email address.</h1>");
})
.catch(error => {
console.log(error);
response.send("<h1>Invalid Credentials</h1>");
});
});
Please can someone help me by resolve this bug in my project?
Calling firebase.auth().signInWithEmailAndPassword(email, password) signs the user in on the location wherever you call this code. Since you run this in an express.js app in Node.js, the user is signed in to that Node.js process, until you sign them out again. This is why the Firebase Authentication SDK you are using is only meant to be used in client-side applications, where this behavior is working as intended.
When integrating Firebase Authentication in a server-side process, you should use the Firebase Admin SDK. This Admin SDK has no concept of a currently logged in user, and no signInWithEmailAndPassword. Instead, if you need to know the identity of the user accessing the server-side code, you'll:
Sign the user in in their client-side app with the Firebase Authentication SDK.
Get the ID token for that user on the client, and pass that with your call to the server.
On the server, verify that ID token, to ensure the user is who they claim they are.
Then use the information on the user's identity to determine if they're authorized to access the information.
For more on this, see the Firebase documentation on verifying ID tokens.
I'm trying to use AWS Amplify to support email / password and Google authentication. Now, I want to store the details from Google into my user pool in AWS. I don't understand the flow here - there are many blog posts I read but most of them are just confusing.
Here's what I tried to do:
// gapi and Amplify included
googleSigninCallback(googleUser => {
const googleResponse = googleUser.getAuthResponse();
const profile = googleUser.getBasicProfile();
const name = profile.getName();
const email = profile.getEmail();
Amplify.Auth.federatedSignin('google', googleResponse, {email, name})
.then(response => { console.log(response); }) // is always null
.catch(err => console.log(err));
});
In DevTools I have the following error in the request in Network Tab:
{"__type":"NotAuthorizedException","message":"Unauthenticated access
is not supported for this identity pool."}
Why should I enable unauthenticated access to this pool? I don't want to.
Am I doing this right? Is it even possible or is it a good practice to store Google User details into the AWS User Pool? If it's not a good practice, then what is?
Also, if I want to ask user for further details not provided by Google in the app and store them, how to do it if we can't store the user in User Pool?
First make sure your identity pool and user pool are setup for google authentication.
Then federatedSignIn has a capital last I.
And finally just change your second param in the call to federatedSignIn as follows:
Amplify.Auth.federatedSignIn('google', {
token: googleResponse.id_token,
expires_at: googleResponse.expires_at
}, {email, name})...
I have created an App using the Parse server, and have now decided to implement some aspects of firebase into my app. Im trying to accomplish this by doing the signInWithCustomToken method in swift on ios. Im calling a rest service to generate a firebase token, and then signing in with that token returned upon valid sign in on my current auth system. However, the token being generated appears to have an invalid signature when pasting it into jwt.io. The environment that im using is node based (inside the parse server). It seems very simple and i have followed the firebase instrucutions/poured over questions on this over the last few days and im really not sure what im doing wrong! Heres my rest service on how i generate the token, i initialize the server with this method:
Parse.Cloud.define("initServer", function(request, response){
var admin = require('firebase-admin');
var serviceAccount = require('/home/bitnami/apps/parse/htdocs/firebase/serviceAccountKey.json');
console.log(serviceAccount);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://urabo-acb7a.firebaseio.com'
});
response.success("Server Init OK");
});
this is the post method i call to generate the token from firebase-admin sdk:
Parse.Cloud.define("generateFirebaseToken", function(request, response) {
var admin = require('firebase-admin');
admin.auth().createCustomToken(request.params.uid)
.then(function(customToken) {
// Send token back to client
response.success(customToken);
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
});
And of course I went into the firebase console and generated the private key, and then scp'd it over to my server. Im not really sure why this is not working, it generates a token it just doesnt appear to be valid or linked to my account. Am i missing some weird encoding issue or something with the token? Does anyone have insight on this?? Thanks so much!
—The main question is are your users signing in to the REST service through your app and then you are also trying to re-authenticate them again with the token generated in your system?
—If they will be accessing further REST functions once authenticated, then why not authenticate them successfully when 'a' token is returned?
—Usually token usage or handling is restricted by the API providers. Another option is instead of involving user auth directly with the API service, have a separate auth system — the usual SignIn process and then make API calls based on the requested API feature. That way your app is interacting with the APIs and users remain at the front end.
When I create a cloud function to process a charge on a user’s card (by writing a stripe token to firebase and using a cloud function to charge), how do I pass errors (like a declined card due to insufficient funds) to the client. If it’s important, I’m using the firebase web sdk to send the tokens.
write the errors to a firebase database so that you can read the errors from the database and show them where you need to.
I decided to use a Firebase HTTP cloud function and just send the token to the link firebase sets for the function. Like so,
exports.addSourceToCustomer = functions.https.onRequest((req, res) => {
const token = req.body.token // use the stripe token however you like here
// when an error occurs use res.status(errorCode).send(errorMessage);
// which sends the error back to the client that made the request
});