AngularJS Authentication + RESTful API - javascript

Angular+RESTful Client-side Communication w/ API for Auth/(re)Routing
This has been covered in a few different questions, and in a few different tutorials, but all of the previous resources I've encountered don't quite hit the nail on the head.
In a nut-shell, I need to
Login via POST from http://client.foo to http://api.foo/login
Have a "logged in" GUI/component state for the user that provides a logout route
Be able to "update" the UI when the user logs out / logs out.
This has been the most frustrating
Secure my routes to check for authenticated-state (should they need it) and redirect the user to the login page accordingly
My issues are
Every time I navigate to a different page, I need to make the call to api.foo/status to determine whether or not user is logged in. (ATM I'm using Express for routes) This causes a hiccup as Angular determines things like ng-show="user.is_authenticated"
When I successfully login/logout, I need to refresh the page (I don't want to have to do this) in order to populate things like {{user.first_name}}, or in the case of logging out, empty that value out.
// Sample response from `/status` if successful
{
customer: {...},
is_authenticated: true,
authentication_timeout: 1376959033,
...
}
What I've tried
http://witoldsz.github.io/angular-http-auth/1
http://www.frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/2
https://github.com/mgonto/restangular (For the life of me I could not figure out how to POST with post data and not query params. The docs turned up nothing on the matter.
Why I feel like I'm losing my mind
It seems as though every tutorial relies on some database (lots of Mongo, Couch, PHP+MySQL, ad infinitum) solution, and none rely purely on communication with a RESTful API to persist logged-in states. Once logged in, additional POSTs/GETs are sent with withCredentials:true, so that's not the issue
I cannot find ANY examples/tutorials/repos that do Angular+REST+Auth, sans a backend language.
I'm not too proud
Admittedly, I'm new to Angular, and would not be surprised if I'm approaching this in a ridiculous way; I'd be thrilled if someone suggest an alternative—even if it's soup-to-nuts.
I'm using Express mostly because I really love Jade and Stylus— I'm not married to the Express' routing and will give it up if what I want to do is only possible with Angular's routing.
Thanks in advance for any help anyone can provide. And please don't ask me to Google it, because I have about 26 pages of purple links. ;-)
1This solution relies on Angular's $httpBackend mock, and it's unclear how to make it talk to a real server.
2This was the closest, but since I have an existing API I need to authenticate with, I could not use passport's 'localStrategy', and it seemed insane to write an OAUTH service...that only I intended to use.

This is taken from my blog post on url route authorisation and element security here but I will briefly summaries the main points :-)
Security in frontend web application is merely a starting measure to stop Joe Public, however any user with some web knowledge can circumvent it so you should always have security server-side as well.
The main concern around security stuff in angular is route security, luckily when defining a route in angular you are create an object, an object that can have other properties. The cornerstone to my approach is to add a security object to this route object which basically defines the roles the user must be in to be able to access a particular route.
// route which requires the user to be logged in and have the 'Admin' or 'UserManager' permission
$routeProvider.when('/admin/users', {
controller: 'userListCtrl',
templateUrl: 'js/modules/admin/html/users.tmpl.html',
access: {
requiresLogin: true,
requiredPermissions: ['Admin', 'UserManager'],
permissionType: 'AtLeastOne'
});
The whole approach focuses around an authorisation service which basically does the check to see if the user has the required permissions. This service abstract the concerns away from the other parts of this solution to do with the user and their actual permission that would have been retrieved from the server during login. While the code is quite verbose it is fully explained in my blog post. However, it basically handle the permission check and two modes of authorisation. The first is that the user must have at least on of the defined permissions, the second is the user must have all of the defined permissions.
angular.module(jcs.modules.auth.name).factory(jcs.modules.auth.services.authorization, [
'authentication',
function (authentication) {
var authorize = function (loginRequired, requiredPermissions, permissionCheckType) {
var result = jcs.modules.auth.enums.authorised.authorised,
user = authentication.getCurrentLoginUser(),
loweredPermissions = [],
hasPermission = true,
permission, i;
permissionCheckType = permissionCheckType || jcs.modules.auth.enums.permissionCheckType.atLeastOne;
if (loginRequired === true && user === undefined) {
result = jcs.modules.auth.enums.authorised.loginRequired;
} else if ((loginRequired === true && user !== undefined) &&
(requiredPermissions === undefined || requiredPermissions.length === 0)) {
// Login is required but no specific permissions are specified.
result = jcs.modules.auth.enums.authorised.authorised;
} else if (requiredPermissions) {
loweredPermissions = [];
angular.forEach(user.permissions, function (permission) {
loweredPermissions.push(permission.toLowerCase());
});
for (i = 0; i < requiredPermissions.length; i += 1) {
permission = requiredPermissions[i].toLowerCase();
if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.combinationRequired) {
hasPermission = hasPermission && loweredPermissions.indexOf(permission) > -1;
// if all the permissions are required and hasPermission is false there is no point carrying on
if (hasPermission === false) {
break;
}
} else if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.atLeastOne) {
hasPermission = loweredPermissions.indexOf(permission) > -1;
// if we only need one of the permissions and we have it there is no point carrying on
if (hasPermission) {
break;
}
}
}
result = hasPermission ?
jcs.modules.auth.enums.authorised.authorised :
jcs.modules.auth.enums.authorised.notAuthorised;
}
return result;
};
Now that a route has security you need a way of determining if a user can access the route when a route change has been started. To do this we be intercepting the route change request, examining the route object (with our new access object on it) and if the user cannot access the view we replace the route with another one.
angular.module(jcs.modules.auth.name).run([
'$rootScope',
'$location',
jcs.modules.auth.services.authorization,
function ($rootScope, $location, authorization) {
$rootScope.$on('$routeChangeStart', function (event, next) {
var authorised;
if (next.access !== undefined) {
authorised = authorization.authorize(next.access.loginRequired,
next.access.permissions,
next.access.permissionCheckType);
if (authorised === jcs.modules.auth.enums.authorised.loginRequired) {
$location.path(jcs.modules.auth.routes.login);
} else if (authorised === jcs.modules.auth.enums.authorised.notAuthorised) {
$location.path(jcs.modules.auth.routes.notAuthorised).replace();
}
}
});
}]);
The key here really is the '.replace()' as this replace the current route (the one they have not got rights to see) with the route we are redirecting them to. This stop any then navigating back to the unauthorised route.
Now we can intercept routes we can do quite a few cool things including redirecting after a login if a user landed on a route that they needed to be logged in for.
The second part of the solution is being able to hide/show UI element to the user depending on there rights. This is achieve via a simple directive.
angular.module(jcs.modules.auth.name).directive('access', [
jcs.modules.auth.services.authorization,
function (authorization) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var makeVisible = function () {
element.removeClass('hidden');
},
makeHidden = function () {
element.addClass('hidden');
},
determineVisibility = function (resetFirst) {
var result;
if (resetFirst) {
makeVisible();
}
result = authorization.authorize(true, roles, attrs.accessPermissionType);
if (result === jcs.modules.auth.enums.authorised.authorised) {
makeVisible();
} else {
makeHidden();
}
},
roles = attrs.access.split(',');
if (roles.length > 0) {
determineVisibility(true);
}
}
};
}]);
You would then sure an element like so:
<button type="button" access="CanEditUser, Admin" access-permission-type="AtLeastOne">Save User</button>
Read my full blog post for a much more detailed overview to the approach.

I haven't been using $resource because I'm just hand crafting my service calls for my application. That said I've handled login by having a service which depends on all the other services that get some sort of initialization data. When the login succeeds it triggers for initialization of all the services.
Within my controller scope I watch the loginServiceInformation and populate some properties of the model accordingly (to trigger the appropriate ng-show/hide). With regard to routing I'm using Angular's built in routing and I simply have an ng-hide based on the loggedIn boolean shown here, it shows text to request login or else the div with the ng-view attribute (so if not logged in immediately after login you're on the correct page, currently I load data for all views but I believe this could be more selective if necessary)
//Services
angular.module("loginModule.services", ["gardenModule.services",
"surveyModule.services",
"userModule.services",
"cropModule.services"
]).service(
'loginService',
[ "$http",
"$q",
"gardenService",
"surveyService",
"userService",
"cropService",
function ( $http,
$q,
gardenService,
surveyService,
userService,
cropService) {
var service = {
loginInformation: {loggedIn:false, username: undefined, loginAttemptFailed:false, loggedInUser: {}, loadingData:false},
getLoggedInUser:function(username, password)
{
service.loginInformation.loadingData = true;
var deferred = $q.defer();
$http.get("php/login/getLoggedInUser.php").success(function(data){
service.loginInformation.loggedIn = true;
service.loginInformation.loginAttemptFailed = false;
service.loginInformation.loggedInUser = data;
gardenService.initialize();
surveyService.initialize();
userService.initialize();
cropService.initialize();
service.loginInformation.loadingData = false;
deferred.resolve(data);
}).error(function(error) {
service.loginInformation.loggedIn = false;
deferred.reject(error);
});
return deferred.promise;
},
login:function(username, password)
{
var deferred = $q.defer();
$http.post("php/login/login.php", {username:username, password:password}).success(function(data){
service.loginInformation.loggedInUser = data;
service.loginInformation.loggedIn = true;
service.loginInformation.loginAttemptFailed = false;
gardenService.initialize();
surveyService.initialize();
userService.initialize();
cropService.initialize();
deferred.resolve(data);
}).error(function(error) {
service.loginInformation.loggedInUser = {};
service.loginInformation.loggedIn = false;
service.loginInformation.loginAttemptFailed = true;
deferred.reject(error);
});
return deferred.promise;
},
logout:function()
{
var deferred = $q.defer();
$http.post("php/login/logout.php").then(function(data){
service.loginInformation.loggedInUser = {};
service.loginInformation.loggedIn = false;
deferred.resolve(data);
}, function(error) {
service.loginInformation.loggedInUser = {};
service.loginInformation.loggedIn = false;
deferred.reject(error);
});
return deferred.promise;
}
};
service.getLoggedInUser();
return service;
}]);
//Controllers
angular.module("loginModule.controllers", ['loginModule.services']).controller("LoginCtrl", ["$scope", "$location", "loginService", function($scope, $location, loginService){
$scope.loginModel = {
loadingData:true,
inputUsername: undefined,
inputPassword: undefined,
curLoginUrl:"partials/login/default.html",
loginFailed:false,
loginServiceInformation:{}
};
$scope.login = function(username, password) {
loginService.login(username,password).then(function(data){
$scope.loginModel.curLoginUrl = "partials/login/logoutButton.html";
});
}
$scope.logout = function(username, password) {
loginService.logout().then(function(data){
$scope.loginModel.curLoginUrl = "partials/login/default.html";
$scope.loginModel.inputPassword = undefined;
$scope.loginModel.inputUsername = undefined;
$location.path("home");
});
}
$scope.switchUser = function(username, password) {
loginService.logout().then(function(data){
$scope.loginModel.curLoginUrl = "partials/login/loginForm.html";
$scope.loginModel.inputPassword = undefined;
$scope.loginModel.inputUsername = undefined;
});
}
$scope.showLoginForm = function() {
$scope.loginModel.curLoginUrl = "partials/login/loginForm.html";
}
$scope.hideLoginForm = function() {
$scope.loginModel.curLoginUrl = "partials/login/default.html";
}
$scope.$watch(function(){return loginService.loginInformation}, function(newVal) {
$scope.loginModel.loginServiceInformation = newVal;
if(newVal.loggedIn)
{
$scope.loginModel.curLoginUrl = "partials/login/logoutButton.html";
}
}, true);
}]);
angular.module("loginModule", ["loginModule.services", "loginModule.controllers"]);
The HTML
<div style="height:40px;z-index:200;position:relative">
<div class="well">
<form
ng-submit="login(loginModel.inputUsername, loginModel.inputPassword)">
<input
type="text"
ng-model="loginModel.inputUsername"
placeholder="Username"/><br/>
<input
type="password"
ng-model="loginModel.inputPassword"
placeholder="Password"/><br/>
<button
class="btn btn-primary">Submit</button>
<button
class="btn"
ng-click="hideLoginForm()">Cancel</button>
</form>
<div
ng-show="loginModel.loginServiceInformation.loginAttemptFailed">
Login attempt failed
</div>
</div>
</div>
The Base HTML that uses the parts above to complete the picture:
<body ng-controller="NavigationCtrl" ng-init="initialize()">
<div id="outerContainer" ng-controller="LoginCtrl">
<div style="height:20px"></div>
<ng-include src="'partials/header.html'"></ng-include>
<div id="contentRegion">
<div ng-hide="loginModel.loginServiceInformation.loggedIn">Please login to continue.
<br/><br/>
This new version of this site is currently under construction.
<br/><br/>
If you need the legacy site and database click here.</div>
<div ng-view ng-show="loginModel.loginServiceInformation.loggedIn"></div>
</div>
<div class="clear"></div>
<ng-include src="'partials/footer.html'"></ng-include>
</div>
</body>
I have the login controller defined with an ng-controller higher up in the DOM so that I can change the body area of my page based on the loggedIn variable.
Note I haven't implemented form validation here yet. Also admittedly still quite fresh to Angular so any pointers to things in this post are welcome. Although this doesn't answer the question directly since it isn't a RESTful based implementation I believe the same can be adapted to $resources since it's built on top of $http calls.

I've written an AngularJS module for UserApp that does pretty much everything you ask for. You could either:
Modify the module and attach the functions to your own API, or
Use the module together with the user management API, UserApp
https://github.com/userapp-io/userapp-angular
It supports protected/public routes, rerouting on login/logout, heartbeats for status checks, stores the session token in a cookie, events, etc.
If you want to give UserApp a try, take the course on Codecademy.
Here's some examples of how it works:
Login form with error handling:
<form ua-login ua-error="error-msg">
<input name="login" placeholder="Username"><br>
<input name="password" placeholder="Password" type="password"><br>
<button type="submit">Log in</button>
<p id="error-msg"></p>
</form>
Signup form with error handling:
<form ua-signup ua-error="error-msg">
<input name="first_name" placeholder="Your name"><br>
<input name="login" ua-is-email placeholder="Email"><br>
<input name="password" placeholder="Password" type="password"><br>
<button type="submit">Create account</button>
<p id="error-msg"></p>
</form>
How to specify which routes that should be public, and which route that is the login form:
$routeProvider.when('/login', {templateUrl: 'partials/login.html', public: true, login: true});
$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', public: true});
The .otherwise() route should be set to where you want your users to be redirected after login. Example:
$routeProvider.otherwise({redirectTo: '/home'});
Log out link:
<a href="#" ua-logout>Log Out</a>
(Ends the session and redirects to the login route)
Access user properties:
User info is accessed using the user service, e.g: user.current.email
Or in the template: <span>{{ user.email }}</span>
Hide elements that should only be visible when logged in:
<div ng-show="user.authorized">Welcome {{ user.first_name }}!</div>
Show an element based on permissions:
<div ua-has-permission="admin">You are an admin</div>
And to authenticate to your back-end services, just use user.token() to get the session token and send it with the AJAX request. At the back-end, use the UserApp API (if you use UserApp) to check if the token is valid or not.
If you need any help, just let me know :)

I've created a github repo summing up this article basically: https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec
ng-login Github repo
Plunker
I'll try to explain as good as possible, hope I help some of you out there:
(1) app.js: Creation of authentication constants on app definition
var loginApp = angular.module('loginApp', ['ui.router', 'ui.bootstrap'])
/*Constants regarding user login defined here*/
.constant('USER_ROLES', {
all : '*',
admin : 'admin',
editor : 'editor',
guest : 'guest'
}).constant('AUTH_EVENTS', {
loginSuccess : 'auth-login-success',
loginFailed : 'auth-login-failed',
logoutSuccess : 'auth-logout-success',
sessionTimeout : 'auth-session-timeout',
notAuthenticated : 'auth-not-authenticated',
notAuthorized : 'auth-not-authorized'
})
(2) Auth Service: All following functions are implemented in auth.js service. The $http service is used to communicate with the server for the authentication procedures. Also contains functions on authorization, that is if the user is allowed to perform a certain action.
angular.module('loginApp')
.factory('Auth', [ '$http', '$rootScope', '$window', 'Session', 'AUTH_EVENTS',
function($http, $rootScope, $window, Session, AUTH_EVENTS) {
authService.login() = [...]
authService.isAuthenticated() = [...]
authService.isAuthorized() = [...]
authService.logout() = [...]
return authService;
} ]);
(3) Session: A singleton to keep user data. The implementation here depends on you.
angular.module('loginApp').service('Session', function($rootScope, USER_ROLES) {
this.create = function(user) {
this.user = user;
this.userRole = user.userRole;
};
this.destroy = function() {
this.user = null;
this.userRole = null;
};
return this;
});
(4) Parent controller: Consider this as the "main" function of your application, all controllers inherit from this controller, and it's the backbone of the authentication of this app.
<body ng-controller="ParentController">
[...]
</body>
(5) Access control: To deny access on certain routes 2 steps have to be implemented:
a) Add data of the roles allowed to access each route, on ui router's $stateProvider service as can be seen below (same can work for ngRoute).
.config(function ($stateProvider, USER_ROLES) {
$stateProvider.state('dashboard', {
url: '/dashboard',
templateUrl: 'dashboard/index.html',
data: {
authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor]
}
});
})
b) On $rootScope.$on('$stateChangeStart') add the function to prevent state change if the user is not authorized.
$rootScope.$on('$stateChangeStart', function (event, next) {
var authorizedRoles = next.data.authorizedRoles;
if (!Auth.isAuthorized(authorizedRoles)) {
event.preventDefault();
if (Auth.isAuthenticated()) {
// user is not allowed
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
} else {d
// user is not logged in
$rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);
}
}
});
(6) Auth interceptor: This is implemented, but can't be checked on the scope of this code. After each $http request, this interceptor checks the status code, if one of the below is returned, then it broadcasts an event to force the user to log-in again.
angular.module('loginApp')
.factory('AuthInterceptor', [ '$rootScope', '$q', 'Session', 'AUTH_EVENTS',
function($rootScope, $q, Session, AUTH_EVENTS) {
return {
responseError : function(response) {
$rootScope.$broadcast({
401 : AUTH_EVENTS.notAuthenticated,
403 : AUTH_EVENTS.notAuthorized,
419 : AUTH_EVENTS.sessionTimeout,
440 : AUTH_EVENTS.sessionTimeout
}[response.status], response);
return $q.reject(response);
}
};
} ]);
P.S. A bug with the form data autofill as stated on the 1st article can be easily avoided by adding the directive that is included in directives.js.
P.S.2 This code can be easily tweaked by the user, to allow different routes to be seen, or display content that was not meant to be displayed. The logic MUST be implemented server-side, this is just a way to show things properly on your ng-app.

Related

angularfire cannot read property facebook - how do i keep using authData throughout app

I´m working on an android game using ionic framework and firebase.
My plan is to let users login using facebook login with firebase, after this i want to save the game data to the users database key.
The first part is working. the script makes an database array based on the users facebook details. but the problem is after this is made, i cant seem to let angular change any database data. It seems like the authData is stuck in the login function...
Is there a way to keep the authdata for use in different controllers and functions?
app.factory("Auth", function($firebaseAuth) {
var FIREB = new Firebase("https://name.firebaseio.com");
return $firebaseAuth(FIREB);
});
app.controller('HomeScreen', function($scope, Auth, $firebaseArray) {
Auth.$onAuth(function(authData){
$scope.authData = authData;
});
var users = new Firebase("https://name.firebaseio.com/users/");
// create a synchronized array
$scope.users = $firebaseArray(users);
$scope.facebooklogin = function() {
Auth.$authWithOAuthPopup("facebook").then(function(authData){
users.child(authData.facebook.cachedUserProfile.id).set({
Username: authData.facebook.displayName,
Id: authData.facebook.cachedUserProfile.id,
Gender: authData.facebook.cachedUserProfile.gender,
Email: authData.facebook.email,
level: "1"
});
}).catch(function(error){
});
}
$scope.facebooklogout = function() {
Auth.$unauth();
}
$scope.changeLVL = function(authData) {
users.child(authData.facebook.cachedUserProfile.id).set({
level: "2"
});
}
});
And this is the datastructure it creates in firebase
users
998995300163718
Email: "Name#email.com"
Gender: "male"
Id: "998995300163718"
Username: "name lastname"
level: "1"
and after trying to edit i get this error... (using the changelevel function)
TypeError: Cannot read property 'facebook' of undefined
at Scope.$scope.changeLVL (http://localhost:8100/js/controllers.js:35:23)
at fn (eval at <anonymous> (http://localhost:8100/lib/ionic/js/ionic.bundle.js:21977:15), <anonymous>:4:218)
at http://localhost:8100/lib/ionic/js/ionic.bundle.js:57606:9
at Scope.$eval (http://localhost:8100/lib/ionic/js/ionic.bundle.js:24678:28)
at Scope.$apply (http://localhost:8100/lib/ionic/js/ionic.bundle.js:24777:23)
at HTMLButtonElement.<anonymous> (http://localhost:8100/lib/ionic/js/ionic.bundle.js:57605:13)
at HTMLButtonElement.eventHandler (http://localhost:8100/lib/ionic/js/ionic.bundle.js:12103:21)
at triggerMouseEvent (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2870:7)
at tapClick (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2859:3)
at HTMLDocument.tapMouseUp (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2932:5)
The main issue is you're relying on the cachedUserProfile.gender property to exist. This isn't guaranteed to be there for every user. You'll need to find a fallback to avoid an error.
Let's simplify by injecting the user via the resolve() method in the router. Don't mind the structure of the code, it's from the Angular Styleguide (my preferred way of writing Angular apps).
angular.module("app", ["firebase"])
.config(ApplicationConfig)
.factory("Auth", Auth)
.controller("HomeScreen", HomeController);
function Auth() {
var FIREB = new Firebase("https://name.firebaseio.com");
return $firebaseAuth(FIREB);
}
function ApplicationConfig($stateProvider) {
$stateProvider
.state("home", {
controller: "HomeScreen",
templateUrl: "views/home.html"
})
.state("profile", {
controller: "ProfileScreen",
templateUrl: "views/profile.html",
resolve: {
currentUser: function(Auth) {
// This will inject the authenticated user into the controller
return Auth.$waitForAuth();
}
}
});
}
function HomeController($scope, Auth, $state) {
$scope.googlelogin = function() {
Auth.$authWithOAuthPopup("google").then(function(authData) {
users.child($scope.authData.google.cachedUserProfile.id).set({
Username: $scope.authData.google.cachedUserProfile.id,
Gender: $scope.authData.google.cachedUserProfile.gender || ""
});
$state.go("app.next");
});
}
}
function ProfileController(currentUser) {
console.log(currentUser.facebook); // injected from router
}
The benefit of this approach is that you don't have to check for authenticated users in the controller. If the user is injected, you know you have an authenticated user.
Check out the AngularFire docs for more information.

angularjs i18n routing with path params

In one application that I'm working with, the route system need to be integrated with i18n, like the example below:
$routeProvider.when('/:i18n/section', ...);
But I'm facing some issues due to, what I guess it is, the $digest cycle, which doesn't change the i18n param at the runtime.
Other issue that I'm facing is, if the location path is pointed to something like:
http://localhost:9000/section/...
not like:
http://localhost:9000/en/section/...
the i18n path param ends being associated with /section/, which means, on $routeParams service, $routeParams.i18n = 'section';. This is expected, but I need to be able to parse the /:i18n/ param, to avoid this conflicts, and change the URL, concatenating one locale, to contextualize the session, replacing the current route with the new one, i18n-ized, whithout refreshing the view/app automatically, yet selectivelly, because some features only need to be changed, not all.
Also, I've designed one service that evaluates, based on a list of possible language settings and its weights, the language that'll be selected to the current context:
var criteria = {
default: {
tag: 'en',
weight: 10
},
user: {
tag: 'pt',
weight: 20
},
cookie: {
tag: 'zh',
weight: 30
},
url: {
tag: 'ru',
weight: 40
},
runtime: {
tag: 'es',
weight: 50
}
};
function chooseLanguage (languages) {
var deferred = $q.defer();
var weights = [];
var competitors = {};
var choosen = null;
if (defineType(languages) === 'array') {
for (var i = languages.length - 1; i >= 0; i--) {
if (languages[i].tag !== null) {
weights.push(languages[i].weight);
competitors[languages[i].weight] = languages[i];
}
}
choosen = competitors[Math.max.apply(Math, weights)];
} else if (defineType(languages) === 'object') {
choosen = languages;
} else {
return;
}
setRuntimeLanguage(choosen.tag);
deferred.resolve(choosen);
return deferred.promise;
}
Explaining the code above, when angular bootstraps the app, the snippet is executed, selecting which language is defined and if its strong enough to be selected. Other methods are related to this operation, like de detection of the URL param, if there's one logged user, etc, and the process is executed not only on the bootstrap, but on several contexts: $routeChangeStart event, when the user autenticates its session, switching the languages on a select box, and so on.
So, in resume, i need to be able to:
Parse the URL and apply the locale param properly, if its not informed initialy;
Change the URL i18n param during the runtime, whithout reloading the whole view/app;
Deal with the language changes correctly, which means, if my approach based on weights isn't the better way to go, if you suggest me something else.
Edit 1:
A $watcher doesn't do the trick because the app needs the correct locale in every path, even before it instantiates all the elements. AngularJS is used in every step of this check, but if there's any clue to do this outside, before Angular instantiates, we can discuss about it.
For now, I'm using the accepted answer below, with a solution that I developed, but it has to be improved.
I've ended up doing a kind of preliminary parse on the URL. Using only ngRoute (ui-router wasn't an option...), I check if the path matches with the restrictions, if not, a redirect is triggered, defining correctly the path.
Below, follows a snippet of the solution, for the primary route, and a simple subsequent example, due to the quantity of the routes on the app, and their specific data, that doesn't belongs to the basis idea:
$routeProvider
.otherwise({
redirectTo: function () {
return '/404/';
}
})
.when('/:i18n/', {
redirectPath: '/:i18n/',
templateUrl: 'home.html',
controller: 'HomeCtrl',
resolve: {
i18n: [
'$location',
'$route',
'i18nService',
function ($location, $route, i18nService) {
var params = $route.current.params;
return i18nService.init(params.i18n)
.then(null, function (fallback) {
if (params.i18n === '404') {
return $location.path('/' + params.i18n + '/404/').search({}).replace();
}
$location.path('/' + fallback).search({}).replace();
});
}
],
data: [
'dataService',
function (dataService) {
return dataService.resolve();
}
]
}
})
.when('/:i18n/search/:search/', {
redirectPath: '/:i18n/search/:search/',
templateUrl: 'search.html',
controller: 'SearchCtrl',
resolve: {
i18n: [
'$location',
'$route',
'i18nService',
function ($location, $route, i18nService) {
var params = $route.current.params;
return i18nService.init(params.i18n)
.then(null, function (fallback) {
$location.path('/' + fallback + '/search/').search({
search: params.search
}).replace();
});
}
],
data: [
'$route',
'searchService',
function ($route, searchService) {
var params = $route.current.params;
return searchService.resolve({
'search': params.search
});
}
]
}
});
The redirectPath property is used in case of you need the pattern for that route, because ngRoute keeps one copy for private use, but doesn't give access to it with $route public properties.
Due to the required parse before any app request (except the i18nService, which loads the locale list and triggers angular-translate to load the translations), this methods causes several redirects, which leads to a significant delay on the instantiation.
If any improvements are possible, I'll thanks for, also, if I found a better way to do it, I'll update this answer with it.

Route resolve promise, not fully resolved - angularjs

So, what I'm trying to do here is something simple:
check the role of the loggedUser on each route (with a resolve that sets the user if a token or login credentials are valid on the backend)
redirect to the intended route
if not allowed for a route, redirect to a different route
In my route provider I have something like
$routeProvider
...
.when('/admin', {
templateUrl: 'views/admin/dashboard.html',
controller: 'AdminDashboardCtrl',
resolve: {
checkLoggedUser: check
}
})
...
where ckeck is this function
var check = function($rootScope, $q, AuthService) {
var deferred = $q.defer();
if($rootScope.loggedUser) {
return;
}
console.log('inside resolve check')
AuthService.check().success(function(data) {
$rootScope.loggedUser = data.user;
deferred.resolve(data.user);
});
console.log('finished check')
return deferred.promise;
};
And my AuthService.check() is this function
check: function()
{
var authtoken = StorageService.get('authtoken');
if(!authtoken) {
$location.path('login');
}
console.log('before returning');
return $http.post($rootScope.base + 'auth/authenticate', { 'authtoken': authtoken });
},
In my .run(function I have
$rootScope.$on('$routeChangeSuccess', function() {
setIntendedUrl();
console.log($rootScope.loggedUser);
console.log($location.path());
});
and setIntendedUrl() check for the loggedUser and redirects to the correct page (or, in what I'm trying to accomplish, redirect to a different page if not allowed, for example the loggedUser has role = 1, can visit only the routes /admin, if a user has role = 2, and the requested path is /admin, he has to be redirected to /user)
So after all this code, when the app run this is my log in the console (see in the code where are they called)
inside resolve check app.js:29
before returning authservice.js:24
finished check app.js:36
intended: /admin/agents/create app.js:149 <--- here is where I redirect the user
Object {id: "21", name: "Kyle", surname: "Butler", roleId: "2"...} app.js:167
/admin/agents/create <--- requested path
This is not what I was expecting, so the first three logs are good, the third doesn't wait the promise to be returned (so I don't have a loggedUser) then the AuthService:check() returns the user and it's everything done at this point, the user with role = 2 is in a route that is not allowed to see.
Just to complete the code, this is the setIntendedUrl function
var setIntendedUrl = function() {
intended = $location.path();
console.log('intended: ' + intended)
if(intended !== '/login') {
if($rootScope.loggedUser && $rootScope.loggedUser.roleId === '1' && !/^\/admin*/.test(intended)) {
intended = '/admin';
} else if($rootScope.loggedUser && $rootScope.loggedUser.roleId === '2' && !/^\/manager*/.test(intended)) {
intended = '/manager';
}
StorageService.set('intended', intended);
//$location.path(intended);
}
};
What I am doing wrong? Why the user in the check function is not resolved before the other code is executed?
Can you make use of session/locals storage or $rootScope where you can store the users authorization object with given routes, permission info once user logged in.
Now is route resolve or run() block you can retrieve the user auth object perform authorization action.
e.g.
.run(['sessionService', '$rootScope', '$location', function(sessionService, $rootScope, $location) {
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
var currentUser = sessionService.get('user_details');
if(next.taskAccess && next.taskAccess != ""){
var hasPerm = $rootScope.getPermission(next.taskAccess);
if(!hasPerm){
$location.path('/unauthorized');
}
}
});
}]);

firebase simple login tutorial missing user

Tutorial: http://www.thinkster.io/angularjs/wBhtRLWHIR/6-authenticating-users-with-a-service
I'm following this tutorial and it seems like I'm losing my user as soon as they register.
Here is my auth.js factory:
'use strict';
app.factory('Auth', function($firebaseSimpleLogin, FIREBASE_URL, $rootScope){
var ref = new Firebase(FIREBASE_URL);
var auth = $firebaseSimpleLogin(ref);
var Auth = {
register : function(user) {
return auth.$createUser(user.email, user.password);
},
signedIn : function() {
// PROBLEM: authUser is always null
console.log(auth.user);
return auth.user !== null;
},
logout : function () {
auth.$logout();
}
};
$rootScope.signedIn = function () {
return Auth.signedIn();
};
return Auth;
});
Here is my auth.js controller:
'use strict';
app.controller('AuthCtrl', function($scope, $location, Auth){
if (Auth.signedIn()) {
$location.path('/');
}
$scope.register = function () {
Auth.register($scope.user).then(function (authUser) {
console.log(authUser);
$location.path('/');
});
};
});
The console.log under signedIn in the factory is always null. Any idea where the disconnect is? The registration itself is working fine, and authUser is populated in the console.log in the controller when registering.
The latest documentation for Angularfire says that the $createUser method of $firebaseSimpleLogin returns a promise but it doesn't mention any parameters being passed to the then callback.
You can use the $getCurrentUser method to get the current user after the user registers.
The tutorial needs to be updated and you should always be checking the documentation for whatever libraries you're using yourself.
Your code for signedIn should look like this:
Auth.signedIn = function() {
auth.$getCurrentUser().then(function(currentUser) {
console.log(currentUser);
}, function() {
console.log('error');
});
};
I found a very similar question that is further along in the tutorial :can't show logout button after $createUser
In the answer I learned that angularfire used to automatically log the user in after it was created. Apparently now it no longer does that which is why the auth.user in signedIn was null.
I am now doing the same tutorial. This code (in auth controller) worked for me:
$scope.register = function () {
Auth.register($scope.user).then(function (authUser) {
console.log(authUser);
Auth.login($scope.user);
$location.path('/');
});
};
I'm a total n00b, but what (I think) this is doing is authenticating the user, then running a function that logs the user in right after. Logout button is now functioning as expected.

How to show error state for wrong password on Firebase Simple Login email password

I'm using Firebase's Simple Login as an admin login for a blog format site. The correct email and pw combo gives write access to the db on Firebase. Following the documentation I have created separate chunks.
The auth var:
var chatRef = new Firebase('https://fiery-fire-291.firebaseio.com/');
var auth = new FirebaseSimpleLogin(chatRef, function(error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
// user authenticated with Firebase
console.log('User ID: ' + user.id + ', Provider: ' + user.provider);
} else {
// user is logged out
}
});
The auth login, which I've wrapped in a login controller:
app.controller('LoginCtrl', function ($scope) {
$scope.login = function() {
auth.login('password', {
email: $scope.loginEmail,
password: $scope.loginPassword,
debug: true
});
};
});
Which gets the data from the login form:
<div ng-controller="LoginCtrl">
<form ng-submit="login()">
<fieldset ng-class="">
<input type="email" ng-model="loginEmail">
<input type="password" ng-model="loginPassword">
</fieldset>
<button type="submit" href="#">Login</button>
</form>
</div>
What would be the correct/best way of setting the ng-class to error for the login form to show the user when their Firebase login has errored?
I feel like I shouldn't set the CSS in Angular (which I could easily do in the error callback). I've tried setting a global var in the callbacks which would be picked up by
ng-class="{error: !userAuthenticated}"
but apart from not working, I feel this is also wrong.
The simplest method here is to write the error to $scope within the callback. You'll need to alert Angular to start a compile by calling $apply or $timeout:
var auth = new FirebaseSimpleLogin(chatRef, function(error, user) {
$timeout(function() {
if (error) {
$scope.error = error;
} else if (user) {
$scope.error = null;
} else {
$scope.error = 'user is logged out';
}
});
});
Display it in your page:
<p class="error" ng-show="error">{{error}}</p>
You can save yourself a good deal of effort by utilizing the in-place tools for Angular + Firebase development: Angular+Firebase Quick Start, angularFire library, angularfire-seed
The angularFire-seed contains a complete, functional login example, including error handling, which you can reference as well.
I am not all that familiar with Angular at all but having looked at the Firebase documentation I believe the callback is exactly were you are meant to do this, it's what it's there for. From the docs:
auth.createUser(email, password, function(error, user) {
if (!error) {
console.log('User Id: ' + user.id + ', Email: ' + user.email);
}
});
If you want to keep out the CSS itself from the controllers then I advise having a look at this snippet on Coderwall that goes into how the ng-class can be set not just via a classname but by an object + expression. You could then set the given boolean controlling the ng-class as required in the error callback and your angular template will update accordingly yet you've not "mixed in" the class setting to your controller code.

Categories