Fetching custom fields from auth object in Firebase - firebase

I am trying to invalidate/revoke client's auth token when they sign on a different device. Initial auth-token is supplied through our server and not firebase (but uses the same secret key, hence works with firebase too).
For each user we save an associated password which gets passed as part of auth-token, when user switches the device - we issue a new password from the server and compare password to invalidate token on server. Firebase connection however still persists.
I am trying to store passwords on firebase for each user. This can then be updated every time we change the password at the backend and use it to invalidate the firebase token as well. However, I am not able to extract password from the auth object. Any ideas?
This is my firebase security rule.
{
"rules": {
".read": root.child('passwords').child(auth.uid).val() == auth.password,
".write": root.child('passwords').child(auth.uid).val() == auth.password
}
}
Surprisingly, none of the custom field in auth object are present.

Whatever you put in the custom auth object would be available in security rules and would be part of the authData returned after authenticating on the client. When you're creating the JWT, you have to follow Firebase's specific rules, which are outlined here. The important part being that a d key contains the authentication data you want to make available. Here's basic example of what the payload might look like.
{
"v": "0",
"iat": 1448391639,
"d": {
"uid": "user1234",
"password": "mySecretPassword"
}
}
http://jwt.io/ is a great tool for quickly creating JWTs for testing. If you use the payload above in conjunction with your Firebase secret, you should see the same payload within your auth data and security rules.
f.authWithCustomToken('eyJhbGciOiJIUz...', function(err, data) {
if (err) {
// Handle error
} else {
console.log(data.auth); // {uid: "userId1234", password: "mySecretPassword"}
}
});

Related

How to check for authenticated users on Google Cloud Function

I am building a web site and decided to go pure HTML+JS with full Firebase so I don't have to implement a backend system to test new ideas. The use case for this question is that all users should be authenticated in order to get access to the pages (pretty standard security feature, right?).
To accomplish that, I am taking advantage of Google Cloud Functions to check whether a user is signed in or not before allowing access to the pages.
Here is the code implemented on firebase.json:
"hosting": {
"rewrites": [ {
"source": "/home.html",
"function": "home"
} ]
}
Inside the home function, I run the following code to check whether the Id Token is a valid one:
admin.auth().verifyIdToken(idToken).then((decodedToken) => {
const userId = decodedToken.uid;
})
The problem I am facing is that the value for idToken is invalid:
Firebase ID token has incorrect algorithm. Expected "none" but got
"RS256"
I tried to copy & past the value from result.credential.accessToken, but I still get the same error message.
firebase.auth().getRedirectResult().then(function(result) {
if (result.credential) {
var token = result.credential.accessToken;
}
});
Any help will be very appreciated.
Thanks!
I understand that you direct the HTTPS requests to your home HTTPS Cloud Function.
You should pass the Firebase ID token as a Bearer token in the Authorization header of the HTTP request, as explained and demonstrated in the following official Cloud Function sample.

Access denied on Firebase Realtime Database security rules simulator

It is hard for me to understand why the simulator puts me off for this input
I supplied this auth token body:
{
"account": "7xms2zm6noz03f2mvn",
"playerId": "d3221a31-263c-4629-92fb-6cac89b67088"
}
I am using custom authentication to supply the above blob.
Please see the attached screenshot for how the simulator treats this:
This is my database tree:
What am I missing?
It appears that when you use the simulator, the "auth token payload" is actually at the auth level, not at the auth.token level.
So, you need to simulate the auth.token.account claim like this:
{
"token": {
"account": "7xms2zm6noz03f2mvn",
"playerId": "d3221a31-263c-4629-92fb-6cac89b67088"
}
}
You can tell it is (apparently) misnamed because if, for example, you select the "Google" provider, then provider and uid are both at the top level of this blob (which can't be modified), and that is where you would expect to find them.
Likewise this image from this blog post shows the token block as a sub-block in the simulator.

Firebase validate rule. How to make the rules work?

Data in Firebase has the following structure:
emailsLending
|___-LqQFYK-iI8a8qe2msVk
|___email
|___serverTimestamp
I wrote the following rules checking the entry in the email field:
{
"rules": {
"emailsLending": {
"$emailsLending_id": {
".indexOn": "email",
"email": {
".validate": "newData.isString() && newData.val().matches(/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,4}$/i)"
}
}
}
}
}
But when I write data to the email field using the Cloud Function, the rule does not apply and I can write anything, for example, "123".
What am I doing wrong?
Cloud Functions run with administrative privileges, and by default bypass the security rules of your database.
The logic here is that rules are to protect against malicious behavior from untrusted users, while Cloud Functions are authored by the most trusted users: collaborators on your app. To catch mistakes made by these types of users, you'd typically use unit tests instead of security rules.
If you have a case where this logic doesn't apply and you're using the Realtime Database, you can set databaseAuthVariableOverride to the UID of the user to run the code as. For an example of this, see the Firebase documentation on accessing user authentication information.

Access URL Parameters In Firebase RTDB Security Rules

I want to read data from Firebase:
firebase.database().ref('videos/' + $videoId + '/data' ).once('value')
But in security rules I don't want to make this readable by anyone. Aka I do not want to use:
".read" : true
I want non-authenticated users to be able to read data if they have a special token, that they pass in the url.
firebase.database().ref('videos/' + $videoId + '/data?token=secretToken').once('value')
My video data looks like this:
{
vidoes: {
$videoId: {
data: {
...
}
tokens: {
secretToken: true
}
}
}
I imagine security rules would look something like this:
{
vidoes: {
$videoId: {
data: {
".read": "data.parent().child('tokens/' + auth.urlQuery('token').val() ).exist()"
}
}
}
Is there anyone I can access query string/ url parameters in Firbase security rules? I could use Firebase functions to create an api, but that is an extra step and network request.
Essentially it would behave like a "token" for Firebase storage:
https://firebasestorage.googleapis.com/v0/b/columbus-c4de8.appspot.com/o/richContent%2F1-min.jpeg_-LPgLxBMt1tBR0dDdNzH?alt=media&token=dfe24c92-e0c2-484a-81df-c09a710b3d34
If the token is correct, then user can read the data.
Note: It's technically possible to use tokens for ".write" security rules. See: Using newData on Updates in Firebase Security Rules
This is not possible, and is also not inherently secure. Firebase security rules would not be secure if anyone had a simple password that let them access data. It's pretty easy to reverse engineer a mobile client to extra the password that allows the query.
If you want to grant access to a query to a user, the only secure way to do that is in tandem with Firebase Authentication, which validates the identity of the person performing the request.

New user email verification [duplicate]

Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.

Resources