How to set Authorization header while calling "callable function" on Firebase - firebase

Basically my problem is, I have my callable functions on Firebase where I want to use "context" to identify if the user is authenticated or not. In the front-end I am logging in user using Firebase authentication (which is an http function on firebase), and as I result I get my user token (which should be used as a Bearer token in the authorization header). The problem is I am not sure how to set the header when I sign in the user so that my "context.auth" would contain the logged in user info rather than being empty. I use firebase.functions().httpsCallable('myFunction'); as the document suggests to make the call from front-end where the problem is even though I logged in before making this call, my context is null.
To give more context think about the following scenario,
//Backend (deployed to cloud functions)
exports.signout = functions.https.onCall((data, context) => {
if(context.auth){
//do signout stuff and return true
}
else{
//not logged in so you can't sign out return false
}
});
//Client
let signout = firebase.functions().httpsCallable('signout');
signout()
.then(res => console.log("signed out"))
.catch(err => console.log(err))
So simply put, while making the httpsCallable('signout') in client, I should have the user token in the 'Authorization' header according to docs, so that I can access the context.auth from my callable function. The thing that I don't understand is how that header should be set there? The most logical thing is setting it on login, but it is not something like setting default header for axios since the call is not exactly an http request rather we use that special httpsCallable function. So how/when is that auth header should be set?

When you use a callable type function from a web or mobile client using the provided SDK, all of the details of the HTTP protocol are handled automatically. There's nothing you have to do to set any headers.
If the user is currently signed in at the time of the request, the SDK will add the authorization header automatically. If the user is signed out, then no header will be added. So, if you want to invoke signout with the authorization of the end user, you will obviously have to call it while they are signed in.
It sounds like you might have signed out the user before invoking the callable. In that case, your function will receive no user data.

Related

Why there is no Rules tab inside Firebase Authentication?

Before adding a new user to Firebase Authentication should the name be qualified first:
The name must not be null
The name must not be empty
The name must contain one D character at least
Examples:
"Frank van Puffelen" => It is unacceptable because there is no D character
"Doug Stevenson" => It is acceptable
"Alex Mamo" => It is unacceptable because there is no D character
"Renaud Tarnec" => It is acceptable
"" => It is unacceptable because it is empty value
NULL => It is unacceptable because it is a null value
On the client side before adding a new user I check if the name follows the above qualifiers or not but the problem is if someone modifies the code.
The client side is not safe and I should check again on the server side if the name follows the rules or not.
So the question is why there is no Rules tab inside Firebase Authentication?
Since you want to check that the user name (the displayName I guess) follows the set of constraints listed at the top of your question you can take advantage of the new blocking Cloud Functions that "let you execute custom code that modifies the result of a user signing in to your app".
For example:
exports.checkDisplayName = functions.auth.user().beforeCreate((user, context) => {
if (!user.displayName || !user.displayName.toUpperCase().includes('D')) {
throw new functions.auth.HttpsError(
'invalid-argument', `displayName is invalid`); // adapt as follows
}
});
More details in the specific section of the doc, and in particular on how to catch and handle the error in your front-end.
The security rules concept is used to prevent unauthorized access to your Firebase resources such as database and storage. The displayName property is optional irrespective of which authentication method you chose.
If you require users to have a displayName then you can:
Check if user has displayName set every time they login. If not, then redirect them to a screen where they can set a name.
Disable sign-ups directly from Firebase client SDKs and use Firebase Cloud Functions with the Admin SDK to create user. No one else can reverse engineer the functions code so the validation on server side will ensure a user has displayName.
exports.createUser = functions.https.onCall((data, context) => {
const { displayName, email, password } = data;
// check if displayName is valid
// if not return error
// create user using Admin SDK if all data provided is valid
return { message: "User created" };
});
Then you can login your user with the Client SDK using signInWithEmailAndPassword()
In case you are using any Auth providers e.g. Google, Facebook and the display name is unavailable for some reason, then you'll need some custom logic as explain in method 1 above.
Either of the solution does not prevent users from using updateProfile() APIs so make sure have some validation on client end as well and report such events somewhere in the database where you can monitor it.

Sign in with Google - What should I do with `nonce`?

What I'm doing now:
Using the JavaScript API to render the button on my web page.
When the Sign in with Google flow is complete, my client-side JavaScript callback is called.
That callback sends the given .credentials string to my server.
The backend server (Node.js) calls the google-auth-library library's OAuth2Client.verifyIdtoken method on the .credentials string, which returns the user's email address (among other things), which my server uses to verify the user and create a session.
Everything works, but I'm wondering if there are any security concerns I'm missing. In particular there's a nonce field. The docs (link) don't explain how to use it.
Note: I'm using "Sign in with Google" and not the deprecated "Google Sign-In".
Edit: I'm familiar with the concept of nonces and have used them when doing the OAuth 2 server-side flow myself. What I can't figure out is how the Sign in with Google SDK expects me to use its nonce parameter with the flow above, where I'm using both their client-side and server-side SDKs.
Nonces are used as a CSRF-prevention method. When you make a request to Google, you include a nonce, and when authentication is complete, Google will send the same nonce back. The magic in this method is that if the nonce does not match what you sent then you can ignore the response, because it was probably spoofed.
Read more about CSRF here: https://owasp.org/www-community/attacks/csrf
Nonces are usually crytographically secure random strings/bytes.
I use crypto-random-string as a base to generate nonces, but any package with this functionality should suffice.
Sometimes I store nonces with a TTL in Redis, but other times I store nonces with an ID attached to the request so I can later verify it.
I'm telling you this since it took a bit long for me to figure out this nonce stuff :P
Using the example from Google's website (https://developers.google.com/identity/one-tap/android/idtoken-auth), I added the code for the nonce:
const nonce = '...'; // Supplied by client in addition to token
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const serverNonce = payload['nonce'];
if (nonce != serverNonce) {
// Return an error
}
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);

How to retrieve email address from id_token Google Javascript API client (GAPI)

I have an SPA with Firebase backend and have integrated Google Calendar access.
To be able to authorise a user to use his/her Google Calendar I am using the gapi.auth2.authorize(params, callback) method. (this as opposed to the regular gapi.auth2.init and signIn flow because my users can link multiple Calendar accounts)
Docs: gapi.auth2.authorize
The problem I am experiencing:
Sometimes the id_token that is returned from authorize includes an email address, and sometimes it doesn't.
The id_token which is returned is a long string that can be read on the front end with a JavaScript function like so:
function parseJwt (token) {
let base64Url = token.split('.')[1]
let base64 = base64Url.replace('-', '+').replace('_', '/')
return JSON.parse(window.atob(base64))
}
When I parse the id_token, I am expecting an object including an email address. However sometimes it doesn't include the email property at all....
How can I retrieve the user's google calendar email address from this id_token in with JavaScript, so I can save it to the user's firestore DB?
Example of an expected result when parsing the id_token:
Example of an un-expected result (no email):
Possible cause:
I think that it might be related to the accounts not returning an email being a Google G-Suite account? And the ones that do return the email is a regular gmail account? But I don't know the solution.
PS:
My flow for re-authorisation for return users is to just use the same gapi.auth2.authorize but with {prompt: 'none', login_hint: 'emailaddress'} and fill in the user's saved email address. This works fine.
In case you want to authorise the JavaScript client with gapi.auth2.authorize but also require the email address the user authorised for, be sure to include email in the scope of the gapi.auth2.authorize(params, callback) parameters!!
A correct example of using JavaScript gapi for authorisation of Google calendar:
Step 1. Include in main HTML head:
<script type=text/javascript src="https://apis.google.com/js/api.js" async defer=defer></script>
Step 2. (once) Load the client: window.gapi.load('client', callbackFunction)Important: Only load the client!
Step 3. (once) Initialise the client for usage of Calendar API.
Important: Only include the discovery docs!
let calDocs = {
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest']
}
window.gapi.client.init(calDocs)
.then(_ => {
console.log('Calendar client initialised')
})
})
},
Step 4. (once) Authorise the gapi client for API calls with gapi.auth2.authorize(params, callbackFunction)
Important: Scope is a string with spaces! Include email in the scope. Do NOT include the discovery docs here!
params = {
client_id: clientId,
scope: 'https://www.googleapis.com/auth/calendar email',
response_type: 'permission id_token'
}
You can repeat the gapi.auth2.authorize before any API call with extra params: {prompt: 'none', login_hint: 'emailaddress'} to refresh the user's access token. This will not show any prompt to the user if he already authorised once for your domain.

Handling authentification to Firebase Database with Fetch in a Service Worker

I'm trying to query a Firebase database from a Service Worker using the Fetch API. However it doesn't work as expected as I can't get authenticated correctly.
Basically what I'm trying to do is from origin https://myproject.firebaseapp.com inside a Service Worker I do a call like this :
var fetchOptions = {};
fetchOptions.credentials = 'include';
var url = options.messageUrl;
var request = new Request('https://myproject.firebaseio.com/user/foobar.json', fetchOptions);
messagePromise = fetch(request).then(function(response) {
return response.json();
});
I'm getting this error :
Fetch API cannot load https://myproject.firebaseio.com/user/foobar.json. Response to preflight request doesn't pass access control check: Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin 'https://myproject.firebaseapp.com' is therefore not allowed access.
Any idea of a way to fix it? How one should do to query/update the Firebase database from a SW?
I've read https://jakearchibald.com/2014/using-serviceworker-today/ and one of the gotcha was exactly that problem, the fact that Fetch request do not send authentification.
Ideally it would be great to be able to use the Firebase JS API inside a SW but this doesn't seem to work as well.
Firebase doesn't store authentication info as a cookie or in anything that would be sent along in the credentials, so there's no need to send them in your fetch request. Instead, you'll need to pull the token from Firebase Auth:
firebase.auth().currentUser.getToken(true).then(function(token) {
// token is the value you'll need to remember for later
});
Once you've got the token, you should be able to add it as a query parameter to the REST request e.g. ?auth={THE_TOKEN}. This will allow you to make your authenticated request in the Service Worker.

Meteor.user() on iron-router server side

How can check, on server side route, if user is logged?
I would add check on 'before', but Metor.user() don't work here.
thanks in advance.
p.s. I have found How to get Meteor.user() to return on the server side?, but not work on iron-router
I'm afraid that this is not possible. I guess that the problem comes from the fact that you're trying to connect to the server with two different protocols - both literally and in logically - so there is no obvious way to relate this two actions.
There is, however, a pretty simple solution that may suit your needs. You'll need to develop a simple system of privileges tokens, or secret keys, or whatever you call them. First, create a server method
var Secrets = new Meteor.Collection("secrets"); // only on server!!!
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
Then, you can now use it on the client to get the secretKey which attach to your AJAX request (or something), either within the HTTP header or in the URL itself. Fear not!
They will all be encrypted if you're using HTTPS.
On the server side you can now retrieve the secretKey from the incoming request and check if it is present in the Secrets collection. You'll know then if the user is granted certain privileges or not.
Also you may want to remove your secret keys from the collection after some time for safety reasons.
If what you're looking to do is to authenticate the Meteor.user making the request, I'm currently doing this within the context of IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user:
###
Verify the request is being made by an actively logged in user
#context: IronRouter.Router.route()
###
authenticate = ->
# Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
# Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
# Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
# Attach the user to the context so they can be accessed at this.user within route
this.user = user
###
Respond to an HTTP request
#context: IronRouter.Router.route()
###
respond = (body, statusCode=200, headers={'Content-Type':'text/json'}) ->
this.response.writeHead statusCode, headers
this.response.write(JSON.stringify(body))
this.response.end()
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus

Resources