So to import into firebase, you need the hashing stuff to decode the password properly. We're using the microsoft owen security package from asp.net. I have a machineKey setup in the webconfig (I presume this is the hmac key I need to provide?)
<machineKey validationKey="<key>" decryptionKey="<decodeKey>" validation="HMACSHA256" />
and in the startup it's a simple
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
#if DEBUG
AllowInsecureHttp = true,
#endif
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
To import into firebase, I'm using
(this is a dev DB user so it's ok that I'm showing the hash)
let userImportRecords = [
{
uid: 'afd698c4-4172-49f1-a6c4-77d175efbc1c',
email: '<usersEmail>',
passwordHash: Buffer.from('APwDZFRV6tnEREBsmECj2LMkgUZAqfOAb9/u0nMra5WrHgVG0F7ggFQABt0Qdtsw3w=='),
passwordSalt: Buffer.from('APwDZFRV6tnEREBs')
}
];
var admin = require("firebase-admin");
var serviceAccount = require("./socket-server/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "<DB URL>"
});
admin.auth().importUsers(userImportRecords, {
hash: {
algorithm: 'PBKDF2_SHA256',
rounds: 10000,
saltSeparator: 16,
// Must be provided in a byte buffer.
key: Buffer.from('<webconfig machine decryptionKey key?>')
}
});
This will import but the password doesn't work. I'm assuming there's something wrong with how I have the salt and/or the hash key. I didn't see in the asp.net documentation where it's setting the key, but I know I have that machineKey setup so I presume this is what it's using. (we have another server that validates the tokens itself)
I'm fairly certain the algorithm and rounds are correct (pulled from documentation). I know the salt is at the front of the password hash, I think it's 16 bytes. But in this setup, do I leave the 16 bytes included in the hash or do they need to be removed? Did I even remove the correct amount of characters or do I need to throw this string into memory and extract the 16 bytes manually. I feel like this is super close. I just need to re-arrange my values.
Related
I am trying to migrate users (including passwords) from an old symfony 2 application to firebase authentication (or google identity platform).
In the symfony2 application the passwords of the users are hashed using sha512 with a salt. I already found that users can be imported using their password and hash in the documentation of firebase (https://firebase.google.com/docs/auth/admin/import-users). However it seems like the sha512 hashing that is used by firebase is not the same as was used by symfony.
For the old symfony project the following configuration is used:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
By looking into the source I found that symfony given a salt and a password symfony will produce the hash like this: (in python code)
def get_hash(salt, password):
hash = password.encode('utf-8')
salted = hash + salt
hash = hashlib.sha512(salted).digest()
for i in range(1, 5000):
# symfony keeps adding salted for every iteration, this is something firebase does not it seems
hash = hashlib.sha512(hash + salted).digest()
return base64.b64encode(hash).decode('utf-8')
However this code does not allow me to login when i import it like in the code below. It however does produce the same hash as I have in my database of the symfony2 application:
app = firebase_admin.initialize_app()
salt = '{test}'.encode('utf-8')
hash = get_hash(salt=salt, password='xyz')
print('calculated hash', base64.b64encode(hash))
users = [
auth.ImportUserRecord(
uid='foobar',
email='foo#bar.com',
password_hash=hash,
password_salt=salt
)
]
hash_alg = auth.UserImportHash.sha512(rounds=5000)
try:
result = auth.import_users(users, hash_alg=hash_alg)
for err in result.errors:
print('Failed to import user:', err.reason)
except exceptions.FirebaseError as error:
print('Error importing users:', error)
I can however login with the password when i use the following fuction.
def get_hash(salt, password):
hash = password.encode('utf-8')
salted = salt + hash
hash = hashlib.sha512(salted).digest()
for i in range(1, 5000):
hash = hashlib.sha512(hash).digest()
return hash
I have already found a way to change the order of adding the salt but i can find no way to hash like this in firebase hash = hashlib.sha512(hash + salted).digest().
Now it seems like there is no way to migrate my password to firebase as the implementation of symfony is a bit different from the one used by firebase. Does anyone know a way to make sure I can still import my current hashes? This would be great.
If not, what would be alternative work arounds?
Is it possible to let firebase do a request to my own endpoint to verify password.
Another way would be to try to catch the signin process and send it to my own endpoint first, set the password in the background and then send the request to firebase?
You haven't specified what your client application is using, so I'm just going to assume it's a web application that will use the Firebase Web SDK.
To use this solution, you'll need to migrate the Symfony user data to Firestore under a private _migratedSymfonyUsers collection, where each document is the email of that user.
On the client, the process will be:
Collect email and password from the user
Attempt to sign in to Firebase with that email and password combination
If that failed, invoke a Callable Cloud Function with that email and password combination.
If function returned a success message (see below), reattempt signing in the user with the given email and password
Handle success/errors as appropriate
On the client, this would look like:
const legacySignIn = firebase.functions().httpsCallable('legacySignIn');
async function doSignIn(email, password) {
try {
return await firebase.auth()
.signInWithEmailAndPassword(email, password);
} catch (fbError) {
if (fbError.code !== "auth/user-not-found")
return Promise.reject(fbError);
}
// if here, attempt legacy sign in
const response = await legacySignIn({ email, password });
// if here, migrated successfully
return firebase.auth()
.signInWithEmailAndPassword(email, password);
}
// usage:
doSignIn(email, password)
.then(() => console.log('successfully logged in/migrated'))
.catch((err) => console.error('failed to log in', err));
In the Callable Cloud Function:
(optional) Assert that the request is coming from your application with App Check
Assert email and password were provided and throw error if not.
Assert that the email given exists in your migrated users and throw an error if not.
If in migrated users, hash the password and compare against the stored hash.
Throw an error if hashes don't match.
If hashes match, create a new Firebase user with that email and password combination
Once created, delete the migrated hash and return success message to the caller
On the server, this would look like:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
function symfonyHash(pwd, salt) {
// TODO: Hash function
return /* calculatedHash */;
}
exports.legacySignIn = functions.https.onCall(async (data, context) => {
if (context.app == undefined) { // OPTIONAL
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.');
}
if (!data.email || !data.password) {
throw new functions.https.HttpsError(
'invalid-argument',
'An email-password combination is required');
}
if (data.email.indexOf("/") > -1) {
throw new functions.https.HttpsError(
'invalid-argument',
'Email contains forbidden character "/"');
}
const migratedUserSnapshot = await admin.firestore()
.doc(`_migratedSymfonyUsers/${data.email}`);
if (!migratedUserSnapshot.exists) {
throw new functions.https.HttpsError(
'not-found',
'No user matching that email address was found');
}
const storedHash = migratedUserSnapshot.get("hash");
const calculatedHash = symfonyHash(password, salt);
if (storedHash !== calculatedHash) {
throw new functions.https.HttpsError(
'permission-denied',
'Given credential combination doesn\'t match');
}
// if here, stored and calculated hashes match, migrate user
// get migrated user data
const { displayName, roles } = migratedUserSnapshot.data();
// create the user based on migrated data
const newUser = await admin.auth().createUser({
email,
password,
...(displayName ? { displayName } : {})
});
if (roles) { // <- OPTIONAL
const roleMap = {
"symfonyRole": "tokenRole",
"USERS_ADMIN": "isAdmin",
// ...
}
const newUserRoles = [];
roles.forEach(symfonyRole => {
if (roleMap[symfonyRole]) {
newUserRoles.push(roleMap[symfonyRole]);
}
});
if (newUserRoles.length > 0) {
// migrate roles to user's token
await setCustomUserClaims(
newUser.uid,
newUserRoles.reduce((acc, r) => { ...acc, [r]: true }, {})
);
}
}
// remove the old user data now that we're done with it.
await hashSnapshot.ref.delete();
// return success to client
return { success: true };
});
I'm working on a firebase+angularjs app and I'm using the simple email and password authentication and it's working properly.
I'm just wondering if I can add extra user data on the user table which is being used by firebase email+password auth, like I want to add billing info and other details concerning the user without creating extra node/table on firebase to store these extra data.
Firebase stores the email/password users in a separate location, that you don't have direct access to. You cannot expand the data in this location.
Since many application developers want to access the user data in their application code, it is a common practice to store all users under a /users node inside the application database itself. The disadvantage is that you have to do this yourself. But the positive side of this is that you can store any extra information if you want.
See the Firebase guide on storing user data for sample code. From there:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(function(authData) {
if (authData && isNewUser) {
// save the user's profile into Firebase so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData)
});
}
});
NOTE: This method only works if you are using Firebase Admin SDK and you need to have end point on your server to manage custom tokens
Firebase Admin SDK has an option to create custom tokens with additional claims object, which can contain arbitrary data. This might be useful to store some user related info, like whether the user is premium user or not.
Additional claims data is accessible using auth object.
example
var uid = "some-uid"; //this can be existing user UID
var additionalClaims = {
premiumAccount: true,
some-user-property: 'some-value'
};
admin.auth().createCustomToken(uid, additionalClaims)
.then(function(customToken) {
// Send token back to client
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
additionalClaims are also accessible in Firebase security rules.
for more info read Firebase Custom Tokens
A Firebase User has a fixed set of basic properties—a unique ID, a primary email address, a name and a photo URL—stored in the project's user database, that can be updated by the user (iOS, Android, web). You cannot add other properties to the Firebase User object directly; instead, you can store the additional properties in your Firebase Realtime Database.
Firebase has a fixed set of user properties which can be updated but not added on to.
However you can add small amounts of data with the help of serialization and deserialization using JSON.stringify() and JSON.parse()
And then use any one of the unused properties to store the string
either in DisplayName, or photoURL property.
Keep in mind the data that can be added has to be small in size and stored as a string.
And this can be only possible with using the method in the FIREBASE SDK and not the angularfire as illustrated below
var user = firebase.auth().currentUser;
user.updateProfile({
displayName: "Jane Q. User",
photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(function() {
// Update successful.
}, function(error) {
// An error happened.
});
You could store more json like data in the photoURL or displaYName variable in the form of string here.
My answer is not angular related but I searched quiet a bit to find out how to do it using Polymer and Polymerfire so I add this answer to help people get it done faster than i did.
I had to add a separate node to db as Frank van Puffelen mentioned.
Imports:
<link rel="import" href="../bower_components/polymerfire/firebase-app.html">
<link rel="import" href="../bower_components/polymerfire/firebase-auth.html">
<link rel="import" href="../bower_components/polymerfire/firebase-document.html">
Then place anywhere in your app a <firebase-app> component:
<firebase-app
name="yourAppName"
api-key= "{{yourApi}}"
auth-domain= "{{yourAuthDomain}}"
database-url= "{{yourDbUrl}}"
>
</firebase-app>
After that you will need to use <firebase-auth> and <firebase-document>:
Template :
<firebase-auth
id="auth"
app-name="yourAppName"
signed-in="{{signedIn}}"
user="{{user}}">
</firebase-auth>
<firebase-document
id="document"
app-name="yourAppName"
path="{{usersPath}}" // e.g "/users"
data="{{userDocument}}">
</firebase-document>
Script:
this._register = function(){
var formValid = this.querySelector('#register-form').validate();
var auth = this.querySelector('#auth');
if(formValid && this.passWordsIdentic){
//The actual registration
auth.createUserWithEmailAndPassword(this.email, this.password).then(function(user){
console.log('auth user registration succes');
//Example values
this.userDocument.uid = user.uid;
this.userDocument.email = user.email;
this.userDocument.firstName = this.firstName;
this.userDocument.lastName = this.lastName;
this.userDocument.userName = this.userName;
this.$.document.save(this.usersPath).then(() => {
console.log("custom user registration succes");
this.$.document.reset();
});
}.bind(this)).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
console.log('error: ', errorCode);
);
}
}
And that's it, you may want to take a look at this excellent google codelab which is a good introduction into using firebase with polymer.
Here is the code of registration where add the extra fields in the Users table
import { AngularFireAuth } from "#angular/fire/auth";
constructor(private firebaseAuth: AngularFireAuth){}
registration(data: any, password: any) {
return this.firebaseAuth.auth.createUserWithEmailAndPassword(data.Email, password)
.then(res => {
res.user.updateProfile({
displayName: `${data.DisplayName}`
})
data.UserId = res.user.uid;
data.PhoneNumbers = [{
NumberType: '',
NumberValue: ''
}];
data.PhotoUrl = '';
data.Addresses = [{
AddressLine1: '',
AddressLine2: '',
City: '',
State: '',
Country: '',
PostalCode: '',
AddressType: ''
}];
data.IsDeleted = false;
this.fireStore.doc(`users/${res.user.uid}`).set(data);
this.toastr.success('User has been register successfully!', 'Successfull!');
return true;
}).catch(err => {
switch (err.code) {
case 'auth/email-already-in-use':
this.toastr.error(`Email address ${data.Email} already in use.`, 'Error!');
break;
case 'auth/invalid-email':
this.toastr.error(`Email address ${data.Email} is invalid.`, 'Error!');
break;
case 'auth/operation-not-allowed':
this.toastr.error('Error during sign up.', 'Error!');
break;
case 'auth/weak-password':
this.toastr.error('Password is not strong enough. Add additional characters including special characters and numbers.', 'Error!');
break;
default:
this.toastr.error(err.message, 'Error!');
break;
}
});
}
Here's a swift version. Your user structure ("table") is like
--users:
-------abc,d#email,com:
---------------email:abc.d#email.com
---------------name: userName
etc.
After you pass the auth FIRAuth.auth()?.createUser you can set the users in database as below:
let ref = FIRDatabase.database().reference()
let rootChild = ref.child("users")
let changedEmailChild = u.email?.lowercased().replacingOccurrences(of: ".", with: ",", options: .literal, range: nil) // Email doesn't support "," firebase doesn't support "."
let userChild = rootChild.child(changedEmailChild!)
userChild.child("email").setValue(u.email)
userChild.child("name").setValue(signup.name)
Please note that method is changed in v4.0.0. Therefore, you need to use the below code to retrieve the user profile:
afAuth.authState.subscribe((user: firebase.User) => {
this.displayName = user.displayName;
this.email = user.email;
this.photoURL = user.photoURL;
});
The answer from Frank is good, but things are a little different in Angular6/Firebase5/Angularfire5:
Here is my click handler for signing in a user:
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then((e) => {
console.log("Log-In Success" + e.additionalUserInfo.profile.name);
if (e.additionalUserInfo.isNewUser)
this.addUserToDatabase(/*...*/);
}).catch((error) => {
console.log("Log-In Error: Google Sign-In failed");
});
I'm migrating to the new database and 3.0 client libs. I'm updating the part which generates a custom auth token (on our server) to do a PATCH to update a resource in the Firebase DB.
These PATCH requests used to be made by our server to Firebase using admin claims based on this: https://www.firebase.com/docs/rest/guide/user-auth.htm
For the new DB, I'm generating the JWT token (using ruby-jwt) like this:
payload = {
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
claims: custom_claims.merge({ admin: true }),
exp: now_seconds + (60 * 60), # Maximum expiration time is one hour
iat: now_seconds,
iss: service_account_email,
sub: service_account_email,
uid: uid
}
JWT.encode(payload, private_key, "RS256")
A PATCH request with this token to the Firebase DB fails with: Missing claim 'kid' in auth header.
In the new Firebase you need to directly use a Service Account to create administrative access credentials. Here is a Node.js snippet that shows how to make a REST call to the Database:
// key.json is a service account key downloaded from the Firebase Console
var key = require('./key.json');
var google = require('googleapis');
var request = require('request');
var DATABASE_URL = 'https://<databaseName>.firebaseio.com';
var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/firebase.database'
]);
jwtClient.authorize(function(err, tokens) {
request({
url: DATABASE_URL + '/.json',
method: 'GET',
headers: {
'Authorization': 'Bearer ' + tokens.access_token
}
}, function(err, resp) {
console.log(resp.body);
});
});
To do the same in Ruby, you might take a look at the googleauth gem for fetching the access token using Service Account credentials.
Here is the equivalent of Michael Bleigh's answer using the ruby googleauth module:
require 'googleauth'
scopes = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/firebase.database']
auth = ::Google::Auth.get_application_default(scopes)
auth_client = auth.dup
auth_client.sub = "service-account-email-here#yourapp.iam.gserviceaccount.com"
token = auth_client.fetch_access_token!
You will also need to set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of your service account JSON file. the value for auth_client.sub comes from client_email in this JSON file.
Of course, as above, this is only valid in a server application you control.
Also, making the request to the firebase REST API is still an exercise for the reader.
references
https://developers.google.com/api-client-library/ruby/auth/service-accounts#authorizingrequests
https://developers.google.com/identity/protocols/application-default-credentials#whentouse
We have a large SPA using Firebase v2. We would like to upgrade to the new API, but we experience the following problem:
As the app is quite large, we have developed many integration tests, and for these tests we always need to reset the database and initialize it to a state, where some users exist. However, we found out there really is no such thing as creating a user on server anymore ( Firebase createUserWithEmailAndPassword method is undefined in node.js ), and we are quite unsure, how to upgrade the API and yet be able to reset and initialize the database from server.
Moreover, we are quite forced to do this upgrade, because we noticed that the Firebase v2, is still using the deprecated Graph API v2.0 for Facebook OAuth, and is not recommended for use after 8.8.2016. We understand that the Firebase v2 will probably not upgrade the calls to the Graph API, as the v2 is legacy. This, however, leaves us quite cornered for now.
Any help on this topic, please?
As of Firebase v3.3.0 you are able to create user accounts using Node, but the documentation isn't great on how to expose these methods.
In order to use the user management methods, you need to initialize an application in node using your Web API key, and not the Service Account config that is walked through in the setup guide.
// The Usual Service Account Init
// This will not contain any user management methods on firebase.auth()
this.app = firebase.initializeApp(
{
serviceAccount: 'path/to/serviceaccount/file.json',
databaseURL: 'https://mydbfb.firebaseio.com'
},
'MyAppName');
// Web Client Init in Node.js
// firebase.auth() will now contain user management methods
this.app = firebase.initializeApp(
{
"apiKey": "my-api-key",
"authDomain": "somedomain.firebaseapp.com",
"databaseURL": "https://mydbfb.firebaseio.com",
"storageBucket": "myfbdb.appspot.com",
"messagingSenderId": "SomeId"
},
'MyAppName');
You can grab your client api key from your Firebase console from the Web Setup guide
https://firebase.google.com/docs/web/setup
This is the only reference I could find that explicitly referenced the need to init with api key to get this to work.
https://groups.google.com/forum/#!msg/firebase-talk/_6Rhro3zBbk/u8hB1oVRCgAJ
Given below is a working example of creating Firebase user through Node.js
exports.addUser = function(req, res) {
var wine = req.body;
var email = req.body.email;
console.log(req.body);
var password = req.body.password;
var name = req.body.name;
console.log(“Creating user for -“+email+”-“+password);
var defaultAuth = admin.auth();
admin.auth().createUser({
email: email,
emailVerified: false,
password: password,
displayName: name,
disabled: false
})
.then(function(userRecord) {
console.log(“Created Firebase User successfully with id :”, userRecord.uid);
var wine = req.body;
wine.userId = userRecord.uid;
wine.timestamp = Date.now();
delete wine.password;
status = “201”;
var reply = JSON.stringify(wine);
db.collection(‘collname’, function(err, collection) {
collection.insert(wine, {safe:true}, function(err, result) {
if (err) {
wine.status = “200”;
wine.message = “An error occured”;
reply.set(‘status’,”201″);
res.status(201).send(wine);
} else {
console.log(‘Success: ‘ + JSON.stringify(result[0]));
status= “200”;
wine.status = “200”;
wine.message = “Account created Successfully”;
res.status(200).send(wine);
}
});
});
})
.catch(function(error) {
wine.message = “An error occured—“;
wine.status = “201”;
console.log(“User Creation onf Firebase failed:”, error);
res.status(201).send(wine);
});
}
For details you can see the following blog post
http://navraj.net/?p=53
Thanks
I'm migrating to the new database and 3.0 client libs. I'm updating the part which generates a custom auth token (on our server) to do a PATCH to update a resource in the Firebase DB.
These PATCH requests used to be made by our server to Firebase using admin claims based on this: https://www.firebase.com/docs/rest/guide/user-auth.htm
For the new DB, I'm generating the JWT token (using ruby-jwt) like this:
payload = {
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
claims: custom_claims.merge({ admin: true }),
exp: now_seconds + (60 * 60), # Maximum expiration time is one hour
iat: now_seconds,
iss: service_account_email,
sub: service_account_email,
uid: uid
}
JWT.encode(payload, private_key, "RS256")
A PATCH request with this token to the Firebase DB fails with: Missing claim 'kid' in auth header.
In the new Firebase you need to directly use a Service Account to create administrative access credentials. Here is a Node.js snippet that shows how to make a REST call to the Database:
// key.json is a service account key downloaded from the Firebase Console
var key = require('./key.json');
var google = require('googleapis');
var request = require('request');
var DATABASE_URL = 'https://<databaseName>.firebaseio.com';
var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/firebase.database'
]);
jwtClient.authorize(function(err, tokens) {
request({
url: DATABASE_URL + '/.json',
method: 'GET',
headers: {
'Authorization': 'Bearer ' + tokens.access_token
}
}, function(err, resp) {
console.log(resp.body);
});
});
To do the same in Ruby, you might take a look at the googleauth gem for fetching the access token using Service Account credentials.
Here is the equivalent of Michael Bleigh's answer using the ruby googleauth module:
require 'googleauth'
scopes = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/firebase.database']
auth = ::Google::Auth.get_application_default(scopes)
auth_client = auth.dup
auth_client.sub = "service-account-email-here#yourapp.iam.gserviceaccount.com"
token = auth_client.fetch_access_token!
You will also need to set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of your service account JSON file. the value for auth_client.sub comes from client_email in this JSON file.
Of course, as above, this is only valid in a server application you control.
Also, making the request to the firebase REST API is still an exercise for the reader.
references
https://developers.google.com/api-client-library/ruby/auth/service-accounts#authorizingrequests
https://developers.google.com/identity/protocols/application-default-credentials#whentouse