Get user agent in firebase functions callable - firebase

How do I get the user agent in firebase callable function?
I have tried using this:
functions.https.onCall((data, context) => {
console.log("agent", context.rawRequest.agent);
// ....
}
);
This logs the agent to be undefined.
How do I get the user agent in this callable?

When you invoke a callable function, the client SDK doesn't provide a user agent string in the request. It's not part of the protocol.
If you want to receive a user agent string, you're going to have to provide one in the data object that you pass from the client. It won't happen automatically.

It's not written in the doc as mentioned above but then I saw that firebase is sending the user agent in the request header so I decided to dig deeper and found this:
functions.https.onCall((data, context) => {
const { ['user-agent']: userAgent } = context.rawRequest.headers;
console.log('userAgent:', userAgent);
});

Related

Use Firebase callablle function context auth with other Google APIs

I have a callable function in which I want to access the Sheets API, but not with the service account, I want to impersonate the user. The problem is that I don't want to send the client an authorization URL. The user has already signed into the firebase app with google on the client-side and has consented permission to all the scopes I need (including the Sheets API scope), so I was wondering if it's possible to use the auth object in the context parameter of a callable function to authenticate with other APIs on behalf of the user.
function exportSpreadsheets(data, context)
{
const {google} = require('googleapis');
//How do I create an OAuth2 object I can use to access the API
//without having to send the user an authentication link?
//Maybe using the data in context.auth.token?
const auth = new google.auth.OAuth2();
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.create()
.then(x =>
{
console.log(x);
});
}
I tried the above code and it doesn't work. I'm struggling a bit to understand all the OAuth2 process.
Thanks!
I figured it out!
The correct way to do this is to send the client's access token as a parameter to the function and use it to access the API on behalf of the user. Like this:
function exportSpreadsheet(data, context)
{
const oauth = new google.auth.OAuth2({
clientId: '<your-apps-client-id>',
clientSecret: '<your-apps-client-secret>',
});
oauth.setCredentials(data);
const sheets = google.sheets({version: 'v4', auth: oauth});
//Use the API
Also, you have to send the provider's access token (which in this case is Google), not Firebase's access token.

firebase auth with MIcrosoft Graph (accessToken)

I am super hopeful someone can help me - I'm kind of stuck.
I'm happily using firebase auth with Microsoft AD. My AuthProvider is firebase.auth.OAuthProvider('microsoft.com').
When I call firebase.auth().signInWithPopup() with that provider, everything works GREAT. I can pick out the accessToken from the resulting UserCredential and access Microsoft Graph api's no problem (yay!).
Firebase persists and renews the authentication and my app gets the expected callback via onAuthStateChanged with the new firebase.User when the user returns to my SPA later (also yay!).
The bad news (where I'm stuck) is: how do I get the Microsoft Graph accessToken in this flow (e.g. when the user returns to my app later)? I don't want them to have to re-authenticate with another popup (yech).
Basically, how do I go from a valid firebase.User to a MS Graph accessToken when the user returns?
Thanks so so much for any help!
Firebase Auth only focuses on authentication only. They will return the OAuth access token on sign in success via UserCredential but will discard the Microsoft OAuth refresh token and not store any OAuth credential associated with the provider. So you have no way to get a new access token afterwards. If you have a good reason for Firebase Auth to manage OAuth access tokens, please file an official feature request.
UPDATE/answer: so it turns out to be simpler than I thought:
The basic idea is to authenticate (re-authenticate) using firebase and use the same clientID for silent microsoft authentication. However, you must supply a loginHint
parameter to the microsoft auth, even if you were previously authorized. loginHint can
be the email address for the firebase user...
In that scenario, the authentication is shared and you won't need to popup a second sign-in for the "microsoft half" of the process - the firebase auth works fine.
I ended up using microsoft's MSAL library (https://github.com/AzureAD/microsoft-authentication-library-for-js)... something like this:
const graphDebug = false;
const msalLogger = new Logger(msalLogCallback, { level: LogLevel.Error });
export async function graphClient(loginHint: string) {
const msal = new UserAgentApplication({
// gotcha: MUST set the redirectUri, otherwise get weird errors when msal
// tries to refresh an expired token.
auth: { clientId: CLIENT_ID, redirectUri: window.location.origin },
system: { logger: msalLogger },
// TODO: should we set cache location to session/cookie?
});
/**
* Create an authprovider for use in subsequent graph calls. Note that we use
* the `aquireTokenSilent` mechanism which works because firebase has already
* authenticated this user once, so we can share the single sign-on.
*
* In order for that to work, we must pass a `loginHint` with the user's
* email. Failure to do that is fatal.
*/
const authProvider: AuthProvider = callback => {
msal
.acquireTokenSilent({ scopes: SCOPES, loginHint })
.then(result => {
callback(null, result.accessToken);
})
.catch(err => callback(err, null));
};
const client = Client.init({
authProvider,
debugLogging: graphDebug,
});
return client;
}
When you are using signInWithPopup, the result object contains the credentials you are looking for.
firebase.auth().signInWithPopup(provider)
.then(function(result) {
// User is signed in.
// IdP data available in result.additionalUserInfo.profile.
// OAuth access token can also be retrieved:
// result.credential.accessToken
// OAuth ID token can also be retrieved:
// result.credential.idToken
})
.catch(function(error) {
// Handle error.
});
Hope this helps.
If you look deep enough you should find msal access token in firebase response under (firebaseAuth.currentUser as zzx).zzj()

Change the HTTP status code with a Google Cloud Function Promise

My code checks if a user is authorized, if the user is, the proper code is run. If the user is not, it run reject() to reject the promise.
If I run the code authenticated, I get
{"error":{"status":"INVALID_ARGUMENT","message":"Bad Request"}}
I am trying to change the status code to Forbidden (code 403) which you would normally do with res.status(403) however since this is a promise it is different. How can I change the error code returned?
My code is as follows:
const cloudFunction = functions.https.onCall((data, context) => {
return new Promise(function(resolve, reject) {
auth.verifyIdToken(data.userId).then(function(decodedToken) {
if(claims.admin === true) {
// Run code if user has admin role
}
else {
reject()
// Return error code 403 because user does not have admin role
}
}).catch(err => reject(err)) // Return error code 401 because user is not logged in
})
});
You can't change the HTTP status for a callable function. Callable functions essentially take over and hide the HTTP protocol for the request and response. All you do is specify the input and output objects, and the SDKs handle everything else.
If you need to control the HTTP status of a response, you will have to use a regular HTTP type function instead. Then you will have full control over the request and response.

Only allow Firebase function to be called by users with specific custom claims

I've got this function which sets custom claims for a user:
export const setUserClaims = functions.https.onRequest((req, res) => {
return admin.auth().setCustomUserClaims(req.body.uid, req.body.claims);
});
I only want this function to be available for users that is a super admin, i.e. custom claims consisting of {admin: true}.
So an admin can set user claims for support staff for example by calling this function.
I've been searching around but I didn't find an obvious answer to this that is 100% robust and secure.
How can I do this?
You can create an HttpsCallable cloud function, that way it will automatically get the authenticated user from the client and put it along with other info into the context.auth object.
The function can be something like this:
async function myFunctionImpl(params, context) {
if (!context.auth.token.admin) {
throw new HttpsError('permission-denied');
}
// function code
}
export const myFunction = functions.https.onCall(myFunctionImpl);
To call it you can use the functions client. This is an example for web:
await firebase
.functions()
.httpsCallable("myFunction")({example: "hello"});
That way if you authenticate the user on the client it will pass the authentication to the function.

Meteor async function for validateLoginAttempt

I wan't to use googles captcha as an security messure in my app.
The process:
Request public response from google on client (Success)
Save public response to an collection (Success)
On server validateLoginAttempt should send my secret and client aclaimed public keys for validation (Success)
If and only if 3 successfull return true otherwise false. (Fails)
The problem I am facing is in 4
I need to execute validateLoginAttempt as async, As soon as I write:
Accounts.validateLoginAttempt(async() => {
await myCallToRemoteValidation
})
However
Accounts.validateLoginAttempt(() => {...})
does process the callback, but too late since node will have already progressed. I already use async await in other parts of the code, and it works but not with accounts. How can I solve this?
Reference: http://docs.meteor.com/api/accounts-multi.html#AccountsServer-validateLoginAttempt

Resources