How to handle Firebase auth in single page app - angularjs

I am using firebase simple login auth with angularjs and I want to create a single page app.
Before this, I have tried using service, main controller to handle it but I don't think it is good enough because I have to call FirebaseAuthClient() every time there is a route.
I also try to put FirebaseAuthClient() in angularjs .run() which initialize when app is start.
But it won't work when there is a route, I think it is because not a full page load.
Ok,
And here is what I want,
except login page, every route (pages) are required login.
A global FirebaseAuthClient() checking on every route so I don't need to call it again.
A global user which return from FirebaseAuthClient()

I'm not sure I understand. You only need to initialize FirebaseAuthClient and all login() once in your entire app. It's a singleton, and your auth credentials apply to any Firebase operations you perform.
What makes you think that this is not the case? What sorts of errors are you seeing?

Here is what I was using before moving auth over to Singly. Maybe it helps?
p4pApp.factory('firebaseAuth', function($rootScope) {
var auth = {},
FBref = new Firebase(p4pApp.FIREBASEPATH);
auth.broadcastAuthEvent = function() {
$rootScope.$broadcast('authEvent');
};
auth.client = new FirebaseAuthClient(FBref, function(error, user) {
if (error) {
} else if (user) {
auth.user = user;
auth.broadcastAuthEvent();
} else {
auth.user = null;
auth.broadcastAuthEvent();
}
});
auth.login = function() {
this.client.login('facebook');
};
auth.logout = function() {
this.client.logout();
};
return auth;
});
The AuthCtrl is common to all/most of my pages.
var AuthCtrl = function($scope, firebaseAuth) {
$scope.login = function() {
firebaseAuth.login();
};
$scope.logout = function() {
firebaseAuth.logout();
};
$scope.isLoggedIn = function() {
return !!$scope.user;
};
// src: Alex Vanston (https://coderwall.com/p/ngisma)
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
$scope.$on('authEvent', function() {
$scope.safeApply(function() {
$scope.user = firebaseAuth.user;
});
});
};
From within the AuthCtrl you can just call isLoggedIn() to see if the user is logged in or not.

Related

AngularFire: how to set $location.path in onAuth()?

I am building an angular+firebase app with user authentication (with angularfire 0.8).
I need to use onAuth() auth event handler, since I will provide multiple authentication paths, included social, and want to avoid code duplication. Inside onAuth callback, I need to reset location.path to '/'.
Usually everything works nicely, but, if app is loaded on an already authenticated session (<F5>, for example), on $scope.$apply() I get "Error: [$rootScope:inprog] $apply already in progress"
(if I don't use $scope.$apply(), location path is not applyed to scope, and no page change happens).
I suspect I make some stupid mistake, but can't identify it...
This is my workflow:
app.controller('AuthCtrl', function ($scope, $rootScope, User) {
var ref = new Firebase(MY_FIREBASE_URL);
$scope.init = function () {
$scope.users = [];
User.all.$bindTo($scope, 'users').then(function () {
console.info('$scope.users bound:', $scope.users);
});
};
$scope.login = function () {
ref.authWithPassword({
email: $scope.user.email,
password: $scope.user.password,
}, function(err) {
if (err) {
console.error('Error during authentication:', err);
}
});
};
ref.onAuth(function(authData) {
if (authData) {
console.info('Login success');
var $rootScope.currentUser = $scope.users[authData.uid];
$location.path('/');
$scope.$apply();
} else {
console.info('Logout success');
}
});
};
app.factory('User', function ($firebase) {
var ref = $firebase(new Firebase(MY_FIREBASE_URL + 'users'));
return {
all: ref.$asObject()
};
});
Just as a reference, I want to post the solution I found, and I'm currently adopting:
$scope.init = function () {
$scope.params = $routeParams;
$scope.debug = CFG.DEBUG;
$scope.lastBuildDate = lastBuildDate;
$scope.error = null;
$scope.info = null;
$scope.users = [];
User.all.$bindTo($scope, 'users').then(function () {
// watch authentication events
refAuth.onAuth(function(authData) {
$scope.auth(authData);
});
});
...
};
...
I.e., it was enough to move the watch on authentication events inside the callback to bindTo on users object from Firebase.

Angularjs Route Listener Doesn't Recognise Service Function

I've implemented a token based authentication system to my script. I have a simple service that checks if the user is logged on. And I use this service to check user status on every route change. Weird thing is; I can login just fine, and logout with no problems. But if I login again I get this error:
Error: AuthenticationService.isLogged is not a function
and as a result my logout function doesn't work. If I reload the page the error disappears and I can logout just fine.
Here is the log out function:
$scope.logout = function logout() {
if (AuthenticationService.isLogged) {
AuthenticationService.isLogged = false;
localStorageService.remove('token');
$location.path("/login");
}
}
And here is the route listener:
run(['AuthenticationService', '$rootScope', '$location', function(AuthenticationService, $rootScope, $location) {
$rootScope.$on("$routeChangeStart", function(event, nextRoute, currentRoute) {
if (nextRoute.access.requiredLogin && !AuthenticationService.isLogged()) {
$location.path("/login");
$scope.apply();
}
});
And the service;
module.factory('AuthenticationService', ['localStorageService', function(localStorageService) {
var auth = {
isLogged: function() {
if (localStorageService.get('token')) {
return true;
} else {
return false;
}
}
}
A factory is supposed to return an object.
module.factory('AuthenticationService', ['localStorageService', function(localStorageService) {
var auth = {
isLogged: function() {
if (localStorageService.get('token')) {
return true;
} else {
return false;
}
}
return auth;
});
This should work and a good reading for factories, services, and providers can be found at http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/.
Answer was provided in comments by Aboca. I quote;
"AuthenticationService.isLogged = false <- there you are killing off the function, so when you try AuthenticationService.isLogged() it says it is not a function because it is 'false', just get rid of that part and it will work well"
Thanks a lot!

angularjs: variables in service not updating for service or controller

JavaScript and AngularJS is still new to me.
I have trouble getting variables from my services into my scope. Here is what I'm doing:
myControllers.controller('UserController', function($scope, UserService) {
clearLoginForm();
updateStuff();
$scope.notifications = UserService.notifications;
function updateStuff() {
$scope.realName = UserService.realName;
$scope.loggedIn = UserService.loggedIn;
}
function clearLoginForm() {
$scope.loginName = '';
$scope.loginPassword = '';
}
$scope.login = function() {
UserService.login($scope.loginName,$scope.loginPassword);
updateStuff();
clearLoginForm();
}
$scope.logout = function() {
UserService.logout();
updateStuff();
clearLoginForm();
}
});
the UserService should hold the information about the logged in User and the functions for login/logout and account related stuff that should be polled from the server.
myModule.factory('UserService', function($interval) {
var loggedIn = false;
var realName = "";
var notifications = {};
resetNotifications();
function resetNotifications() {
notifications.msgCount = 0;
notifications.todoCount = 0;
notifications.ergCount = 0;
}
function login(name, password) {
if (password === 'abc') {
loggedIn = true;
realName = 'John Smith';
}
};
function logout() {
loggedIn = false;
realName = '';
resetNotifications();
}
function updateNotifications() {
if (loggedIn) {
notifications.msgCount = 1;
}
else {
resetNotifications();
}
};
$interval(updateNotifications, 10000);
return {
loggedIn : loggedIn,
realName : realName,
login : login,
logout : logout,
notifications : notifications
};
});
But it's not working. So I noticed that if I change "loggedIn" in the login/logout functions to "this.loggedIn" (and same with realName) then the values get propagated to the scope. Why do I need the "this"? Aren't "loggedIn" and "realName" in my closure? But this is not a solution since this is now a different "loggedIn" than used in the updateNotifications function (and here I can't change it to "this.loggedIn").
Secondly, I don't like that I need to call the updateStuff() Function everytime a value in the service changes. But it's not working without. Is there a better way to do it?
I fear I'm doing something fundamentally wrong.
When you return loggedIn at the end of your service, what you're really returning is a copy of the variable. So when you later update it inside of UserService, the copy won't get updated. The easiest way to solve that is with the getter approach:
function getLoggedIn() { return loggedIn; }
return {
getLoggedIn: getLoggedIn
};
your answer helped. but I found a better way to do it. I just wrapped loggedIn and realName in a real object
var userStatus = {
loggedIn : false,
realName : ""
};
and using that one in the controller. That also removes the necessity of the updateStuff function. I hope that won't cause any other trouble.

FACTORY: get current user.id for Firebase Simple Login (Email / Password)

I am looking for a solid way to have the 'current user id' in all my controllers available.
Using: Firebase Simple Login : Email / Password Authentication
My ida: I need a 'Factory' wich I can inject into my controllers,
to have the 'current user id' always available.
I came up with this code:
app.factory('User', ['angularFire',
//Get Current UserID
function(angularFire){
console.log ('FACTORY: User');
var currentUser = {};
var ReturnStr = '';
var ref = new Firebase("https://myFIREBASE.firebaseio.com/");
var authClient = new FirebaseAuthClient(ref, function (err, user) {
if (err) {
ReturnStr = 'FACTORY: User Error: ' + err;
console.log (ReturnStr);
//var User = ReturnStr;
} else if (user) {
console.log ('FACTORY: User: Login successfully:');
console.log (user);
currentUser = user;
} else {
//console.log ('-----------User: Logged Out ---------------');
ReturnStr = 'FACTORY: Logged out: Redirect to Login';
console.log (ReturnStr);
window.location.href = "/login.php";
}
});
return currentUser;
}
]);
My simplest Controller looks like:
function ToDoCtrl($scope, User) {
$scope.MyUser = User;
$scope.MyUser.test = 'Test';
}
In HTML (angular partials) i have:
<h2>{{MyUser.id}}</h2>
<h2>{{MyUser.email}}</h2>
<h2>{{MyUser.provider}}</h2>
<h2>{{MyUser.test}}</h2>
=> id, email, provider are 'undefined'. In console I see the 'FACTORY: User: Login successfully:' with correct user - Object.
=> Asynchronous loading of data problem?
I have also experimented (without luck):
$timeout(function () {
currentUser = user;
}
Such a FACTORY would be very useful!
Thanks for a pointing me in the right direction!
Edit 1.1: Now, with $rootscope hack
=> Same effect - mycontroller is too fast - factory to slow.
app.factory('User', ['$rootScope', '$timeout', 'angularFire',
//Aktueller Benutzer auslesen
function($rootScope, $timeout, angularFire){
console.log ('FACTORY: User');
var currentUser = {};
var ReturnStr = '';
var ref = new Firebase("https://openpsychotherapy.firebaseio.com/");
var authClient = new FirebaseAuthClient(ref, function (err, user) {
if (err) {
ReturnStr = 'FACTORY: User Error: ' + err;
console.log (ReturnStr);
//var User = ReturnStr;
} else if (user) {
console.log ('FACTORY: User: Login successfully:');
//currentUser = user;
$timeout(function () {
ReturnStr = 'FACTORY: Inside timout';
console.log (ReturnStr);
currentUser = user;
console.log (currentUser);
$rootScope.myUser = user;
$rootScope.myUserID = user.id;
$rootScope.loggedIn = true;
$rootScope.$apply();
return currentUser;
});
} else {
//console.log ('-----------User: Logged Out ---------------');
ReturnStr = 'FACTORY: Logged out: Redirect to Login';
console.log (ReturnStr);
//var User = ReturnStr;
window.location.href = "/login.php";
}
});
return currentUser;
}
]);
TAHNKS for any helpful suggestions! Wonderin how others solve this!
So here is my solution to this exact problem. I am using Firebase, FirebaseAuthClient, and angularFire for my Angular app. I ran into the same situation for my login system where you cannot inject the $scope into the factory and therefore I came up with making a controller that used a factory for it's methods to retrieve, add, update, and delete things. And in the controller, I have my firebaseAuth stuff going on, the setting of the User values, and references whick I assign to the scope of that. Once the user is logged in, they are redirected to another location, at which point the app.js file takes over with a child controller when at that address location.
My login also uses localStorage, so logins will persist, you can refresh and not have to keep logging in, and you can change it to be cookies or sessionStorage easy enough.
This is going to need to be adapted for your needs specifically if you choose to use this method, it's quite complex no matter what, but this is very solid for me and I'm no longer needing to worry about firebaseAuth or angularFire stuff now that my factories are all setup for passing data back and forth. I'm just doing angularJS stuff mostly with directives. So here's my code.
NOTE: This will need modifying, and some things will be pseudo or open-ended for you to figure out for your needs.
AuthCtrl.js
'use strict';
angular.module('YOUR_APP', []).
controller('AuthCtrl', [
'$scope',
'$location',
'angularFire',
'fireFactory',
function AuthCtrl($scope, $location, angularFire, fireFactory) {
// FirebaseAuth callback
$scope.authCallback = function(error, user) {
if (error) {
console.log('error: ', error.code);
/*if (error.code === 'EXPIRED_TOKEN') {
$location.path('/');
}*/
} else if (user) {
console.log('Logged In', $scope);
// Store the auth token
localStorage.setItem('token', user.firebaseAuthToken);
$scope.isLoggedIn = true;
$scope.userId = user.id;
// Set the userRef and add user child refs once
$scope.userRef = fireFactory.firebaseRef('users').child(user.id);
$scope.userRef.once('value', function(data) {
// Set the userRef children if this is first login
var val = data.val();
var info = {
userId: user.id,
name: user.name
};
// Use snapshot value if not first login
if (val) {
info = val;
}
$scope.userRef.set(info); // set user child data once
});
$location.path('/user/' + $scope.userRef.name());
} else {
localStorage.clear();
$scope.isLoggedIn = false;
$location.path('/');
}
};
var authClient = new FirebaseAuthClient(fireFactory.firebaseRef('users'), $scope.authCallback);
$scope.login = function(provider) {
$scope.token = localStorage.getItem('token');
var options = {
'rememberMe': true
};
provider = 'twitter';
if ($scope.token) {
console.log('login with token', $scope.token);
fireFactory.firebaseRef('users').auth($scope.token, $scope.authCallback);
} else {
console.log('login with authClient');
authClient.login(provider, options);
}
};
$scope.logout = function() {
localStorage.clear();
authClient.logout();
$location.path('/');
};
}
]);
And now for the nice and simple yet quite reusable factory. You will need to set your Firebase path for your app for the baseUrl variable for it to work.
fireFactory.js
'use strict';
angular.module('YOUR_APP').
factory('fireFactory', [
function fireFactory() {
return {
firebaseRef: function(path) {
var baseUrl = 'https://YOUR_FIREBASE_PATH.firebaseio.com';
path = (path !== '') ? baseUrl + '/' + path : baseUrl;
return new Firebase(path);
}
};
}
]);
Info
You give the factory just a piece of the path reference such as 'users' which will be used as part of the full path ref to where you want to store your user data.
fireFactory.firebaseRef('users')
Once you have a reference set for a user, they won't need to be set again it will just use the existing data and .auth() to it. Additionally if there's an existing 'token' in localStorage it will use that to auth() the user too.
Otherwise, it will login() the user and pop open the Oauth windows for them to do so using whatever option you provide them.
I have spent a lot of time, many many hours days and yes even months searching for something better than this when it comes to Firebase/FirebaseAuthClient and angularFire. With the way the Firebase API and FireAuth API is, it's very annoying to make them play nicely with each other when using them with angularFire anyways. It's very frustrating but I've gotten past it finally.
If you want to check out the code for my app and see how I'm doing these things more completely, you can find it in this branch of my Webernote github repo.
Feel free to fork it, install and run it locally, or contribute to it even if you like. I could use some help myself :)
Hope this helps you!!
Here's the way I do it.
First of all I have my firebase auth service (I'm not using Angularfire) that calls off to Singly to handle logins. When the user status changes it $broadcasts an event.
p4pApp.factory('firebaseAuth', function($rootScope, singlyAuth) {
var auth = {},
FBref = new Firebase(p4pApp.FIREBASEPATH);
auth.login = function(service) {
singlyAuth.login(service);
};
auth.logout = function() {
FBref.unauth();
auth.user = null;
auth.broadcastAuthEvent();
};
auth.broadcastAuthEvent = function() {
$rootScope.$broadcast('authEvent');
};
auth.authWithToken = function(token) {
if (token !== undefined) {
FBref.auth(token, function(error, authData) {
if(!error) {
auth.user = authData.auth.account;
auth.broadcastAuthEvent();
} else {
auth.user = null;
auth.broadcastAuthEvent();
}
}, function(error) {
auth.user = null;
auth.broadcastAuthEvent();
});
}
};
return auth;
});
Then I have a 'top level' controller that looks after authorisation state.
var AuthCtrl = function($scope, firebaseAuth, singlyAuth, firebase, user) {
$scope.user = null;
$scope.logout = function() {
firebaseAuth.logout();
};
$scope.isLoggedIn = function() {
return !!$scope.user;
};
// src: Alex Vanston (https://coderwall.com/p/ngisma)
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
$scope.$on('authEvent', function() {
$scope.safeApply(function() {
$scope.user = firebaseAuth.user;
});
user.setID(firebaseAuth.user);
if (firebaseAuth.user) {
firebase.fetch(['users', firebaseAuth.user], function(results) {
if (results) {
user.setData(results);
} else {
results = {};
results.createdAt = DateTimeStamp();
}
results.lastLogin = DateTimeStamp();
firebase.set('users', firebaseAuth.user, results);
});
} else {
user.clearData();
}
});
};
Finally, I use a dedicated user service to maintain user state. (It's still in development)
p4pApp.factory('user', function() {
var userService = {}, user={};
user.data = {};
userService.setID = function(id) {
user.id = id;
};
userService.getID = function() {
return user.id;
};
userService.setData = function(data) {
user.data = data || {};
};
userService.getData = function() {
return user.data;
};
userService.clearData = function() {
user.data = {};
};
userService.setDataField = function(field, data) {
user.data[field] = data;
};
userService.clearDataField = function(field) {
delete user.data[field];
};
userService.pushData = function(key, data) {
if (typeof(key) === 'string') {
user.data[key] = data;
} else {
_.reduce(key, function(obj, child, index, list) {
obj[child] = obj[child] || {};
if (index == list.length-1) {
obj[child] = data;
}
return obj[child];
}, user.data);
}
};
userService.deleteData = function(key) {
if (typeof(key) === 'string') {
delete user.data[key];
} else {
_.reduce(key, function(obj, child, index, list) {
obj[child] = obj[child] || {};
if (index == list.length-1) {
delete obj[child];
return;
}
return obj[child];
}, user.data);
}
};
return userService;
});
This is most probably due to async nature of the call. To fix it you would have to
Inject $scope into the factory function (similar to angularFire dependency)
Use $scope.$apply() after the assigment currentUser = user;
Another solution that works for me:
account.service.js
(function() {
'use strict';
angular
.module('app.account')
.factory('Account', Account);
Account.$inject = [
'$firebaseAuth',
'$firebaseObject'
];
/* #ngInject */
function Account(
$firebaseAuth,
$firebaseObject,
) {
var firebaseRef = new Firebase('https://<<-- MY_FIREBASE -->>.firebaseio.com');
var authObj = $firebaseAuth(firebaseRef);
var service = {
userInfo: null
};
activate();
return service;
////////////////
function activate() {
// Add listeners for authentication state changes
authObj.$onAuth(function(authData) {
if (authData) {
// Load the userInfo
loadUserInfo(authData);
} else {
// Destroy the userInfo Object if one exists
if (service.userInfo) {
service.userInfo.$destroy();
service.userInfo = null;
}
}
});
}
function loadUserInfo(authData) {
var userRef = firebaseRef.child('users').child(authData.uid);
var loadedInfo = $firebaseObject(userRef);
loadedInfo.$loaded()
.then(function() {
service.userInfo = loadedInfo;
})
.catch(function(error) {
switch (error.code) {
case 'PERMISSION_DENIED':
alert('You don\'t have the permission to see that data.');
break;
default:
alert('Couldn\'t load the user info.');
}
});
}
}
})();
some-component.controller.js
(function() {
'use strict';
angular
.module('app')
.controller('SomeController', SomeController);
SomeController.$inject = [
'Account'
];
/* #ngInject */
function SomeController(
Account
) {
var vm = this;
vm.userInfo = userInfo;
////////////////
function userInfo() {
return Account.userInfo;
}
...
}
})();
some-component.html
...
<div class="user-id">
{{vm.userInfo().id}}
<div>
...

angularjs with firebase auth share service

Halo all,
I want to use angularjs with firebase simple login (facebook). But I have no idea how to
create the auth share service.
What I want to do is
create a authentication service
use this auth service to check if user loggedin in every controllers
controllers will do the $location if user loggedin/not-login
I also new to angularjs but I don't know which services should I use in this situation.
service or factory?
How can I put the below code in angular service then tell each controller if user logged in or not?
var firebaseRef = new Firebase("https://test.firebaseio.com");
var auth = new FirebaseAuthClient(firebaseRef, function(error, user) {
if (user) {
console.log(user);
} else if (error) {
console.log(error);
} else {
console.log('user not login');
}
});
Here is what i'm guessing, return user value from authService so in controllers if authService.user exists then redirect to loggedin page otherwise show login dialog with a
login button to call the following code
authService.login('facebook');
Let me know if I can do like this, or there is a better way?
Here is what I'm using so far...
I haven't implemented the redirecting yet but the rest works.
p4pApp.factory('firebaseAuth', function($rootScope) {
var auth = {},
FBref = new Firebase(p4pApp.FIREBASEPATH);
auth.broadcastAuthEvent = function() {
$rootScope.$broadcast('authEvent');
};
auth.client = new FirebaseAuthClient(FBref, function(error, user) {
if (error) {
} else if (user) {
auth.user = user;
auth.broadcastAuthEvent();
} else {
auth.user = null;
auth.broadcastAuthEvent();
}
});
auth.login = function() {
this.client.login('facebook');
};
auth.logout = function() {
this.client.logout();
};
return auth;
});
The AuthCtrl is common to all/most of my pages.
var AuthCtrl = function($scope, firebaseAuth) {
$scope.login = function() {
firebaseAuth.login();
};
$scope.logout = function() {
firebaseAuth.logout();
};
$scope.isLoggedIn = function() {
return !!$scope.user;
};
// src: Alex Vanston (https://coderwall.com/p/ngisma)
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
$scope.$on('authEvent', function() {
$scope.safeApply(function() {
$scope.user = firebaseAuth.user;
});
});
};
Regarding factory vs. service, there was a good blog post explaining how to approach the choice that I recommend reading: http://iffycan.blogspot.com/2013/05/angular-service-or-factory.html
In terms of doing authentication, your general approach of assigning signed in state to a variable in a service seems alright. We're thinking about doing a deeper integration with Angular for authentication, but for now this seems like a reasonable approach.

Resources