I'm using lb-services from Angular JS SDK to create my default service, and currently, I have some trouble in logout & rememberMe function.
I'm following this tutorial : Angular Javascript SDK & Loopback getting started intermediate , and the way I create my authentication is as same as the example, with some adjustment to apply them in my application.
I'm not sure how to describe it, but here's some of the code for my logout & remember function
in my stateProvider, I have this kind of state as my root application
.state('app', {
url:'/',
abstract: true,
views:{
'bodyLogin': {templateUrl : 'views/login/body.html',
controller : 'BodyController'},
'bodyMain': {templateUrl : 'views/main/body.html',
controller : 'BodyController'}}
})
.state('app.mainUnreg', {
url:'home',
views: {
'content#app': {
templateUrl : 'views/login/home.html'
}}})
.state('app.mainReg', {
url:'mainMenu',
views: {
'header#app': {
templateUrl : 'views/main/header.html'
},
'sidebar#app': {
templateUrl : 'views/main/sidebar.html'
},
'middle#app': {
templateUrl : 'views/main/home.html'
},
'footer#app': {
templateUrl : 'views/main/footer.html'
}},
controller: 'HomeController'})
index.html
<div ng-if="!loggedIn" ui-view="bodyLogin" class="site-wrapper" ui-sref-active="app.mainUnreg"></div>
<div ng-if="loggedIn" ui-view="bodyMain" ui-sref-active="app.mainReg"></div>
so, if I haven't not logged in, I will enter to bodyLogin, which means I will show my login template, and otherwise, if I have logged in, I will enter to bodyMain
I have succeed to logged in and enter to my main page, but on here, and it should be have been authenticated, because in my bodyController,
.controller('BodyController', [..., function(...){
//this line is to check whether the user has been authenticated or not
if(Account.isAuthenticated() === false) {
$scope.loggedIn = false;}
// ------------
if($scope.loggedIn === false){
$scope.layout = 'login';
$state.go('app.mainUnreg');
}
else{$scope.loggedIn = true;
$scope.layout = 'styles';
$state.go('app.mainReg');
}}])
.controller('LoginController', [..., function (...) { $currentUserId = Account.getCurrentId();
$scope.$stateParams = $stateParams; $scope.loginData = {}; $scope.doLogin = function() { AuthService.login($scope.loginData)
.then(function() {
$scope.loggedIn = true;
location.reload(); }); };}])
.controller('LogoutController', [..., function(...) {
AuthService.logout()
.then(function() {
console.log("Logging out . . .");
$scope.loggedIn = false;
$state.go('app'); }); }]);
And some additional service beside lb-services.js to handle my authentication,
function login(loginData) {
var params = { rememberMe: loginData.rememberMe };
var credentials = {
email: loginData.email,
password : loginData.password
};
return User
.login(params, credentials)
.$promise.then(function(response){ $rootScope.currentUser = {...};
console.log($rootScope.currentUser);
},
function(response) {
console.log(response);
});
}
function logout(){
return User
.logout()
.$promise.then(function(){
$rootScope.currentUser = null;
});
};
I think, with this kind of code, especially I have checked with
if(Account.isAuthenticated() === false){...}
and succeed to enter my main page, I have successfully logged in and authenticated, haven't I?
But, if I tried to put ng-show="Account.isAuthenticated()" in the top div of my main page app.mainReg , my page can't be show, yet it means that my account haven't authenticated, even the loopback have successfully save my token, user id, and rememberMe boolean in local storage, like this
$LoopBack$accessTokenId = ....
$LoopBack$currentUserId = ...
$LoopBack$rememberMe = true // automatically become true?
So, do anyone know how to solve this problem? I think the problem is on the authentication.
I'm quite confused with this one, I have tried to search for solution, yet I could find any.
I just realized why I couldn't use rememberMe function, that's just because I made a silly mistake, by putting " ; " after my logout function in my additional services
And after several testing, It seems that my problem was inside my state provider. I tweaked my controller and my state provider and finally it has been work successfully.
Related
My angular JS application is for an e-commerce usecase. There would be several pages, where some data would be fetched from some REST APIs which would be authenticated (and some not requiring authentication). If authentication fails (user not logged in), the APIs would all respond with a special error_code (say 'AUTH_FAIL').
My requirement is if any API fails due to authentication, then a login modal form dialog should appear in that page. This modal form contains the Username and password field. If the login succeeds, the modal window should close, and the current route should be re-freshed.
I understand how to do this for a particular route/controller. However, since there would be a lot of such pages where this would be needed, I'm unable to think of a way in which same piece of code could be easily utilized, since in my opinion, this does seem like a common requirement. How can it be done, or if not, what's the best way around it?
You can use interceptors for this purpose. Inteceptors can be used for global error handling, authentication, or any kind of synchronous or asynchronous pre-processing of request or postprocessing of responses.
For example I use the following code to redirect user to login when authentication fails.
.factory('myInterceptor', ['$q', '$location', '$injector', function ($q, $location, $injector) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === 401) {
var stateService = $injector.get('$state');
stateService.go('login');
}
return $q.reject(rejection);
}
}
}])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
}]);
Using interceptors sounds like the most obvious and elegant solution, however I was never satisfied with it, mostly because of running into the circular dependency problems.
Here are some bits and pieces of logic from one of my apps using angular 1.6 and ui-router.
Some explanation about the business logic before you deep dive into the code.
I use JWT authentication and my server expects JWT to be passed as a header, hence the specifics of the authService implementation. The authService checks if the header is expired, and tries to send a JWT refresh request before actually showing a login dialog. Feel free to adjust it to your security implementation (e.g. session cookie/based or some other storage).
authService.js
This service is responsible for storing security token in the client. It returns a promise, which is resolved with the JWT token (if present or if it was refreshed). The promise is rejected when the token is expired and the service failed to obtain new token from the server.
app.factory('authService', function($http, $q, $window, jwtHelper, API_HOST) {
var storage = $window.localStorage;
var cacheToken = {};
var targetUrl = null;
function saveToken(data) {
var tokenPayload = jwtHelper.decodeToken(data.auth_token);
storage.setItem('auth_token', data.auth_token);
storage.setItem('refresh_token', data.refresh_token);
storage.setItem('exp', tokenPayload.exp);
storage.setItem('user_identifier', tokenPayload.user_identifier);
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
cacheToken.user_identifier = storage.getItem('user_identifier');
}
function setCacheToken() {
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
cacheToken.user_identifier = storage.getItem('user_identifier');
}
function isAuthenticated() {
return cacheToken.auth_token && cacheToken.exp > moment(new Date().getTime()).unix()
}
setCacheToken();
return {
saveToken: function(data) {
saveToken(data);
return cacheToken;
},
getToken: function() {
return cacheToken;
},
isAuthenticated: isAuthenticated,
targetUrl: targetUrl,
getAuthorizationHeader: function() {
if (isAuthenticated()) {
return $q.when({
'Authorization': 'Bearer ' + cacheToken.auth_token
});
} else {
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
if (isAuthenticated()) {
return $q.when({
'Authorization': 'Bearer ' + cacheToken.auth_token
});
} else {
if (!cacheToken.refresh_token) return $q.reject(null);
return $http.post(API_HOST + '/tokens/refresh', {
'refresh_token': cacheToken.refresh_token
}).then(function(response) {
saveToken(response.data);
return {
'Authorization': 'Bearer ' + cacheToken.auth_token
};
}).catch(function() {
cacheToken = {};
$window.localStorage.clear();
return $q.reject(null);
})
}
}
}
}
});
app.run block
This piece of logic is responsible for memorising the target url in case user tried to access protected resource, or when user token/session is expired. Please 2 things here: authService.targetUrl stores the URL and authenticate property on the ui-router state is used to check if the state is protected (e.g. if the authentication logic should be applied).
$transitions.onBefore({
to: function(state) {
return state.self.authenticate;
}
}, function(trans) {
return authService.getAuthorizationHeader().then(function() {
return null;
}).catch(function() {
authService.targetUrl = $window.location.href;
$('#login-modal').modal();
return trans.router.stateService.target('homepage');
});
});
login modal directive
This piece of code stores the user token after login and also checks if the targetUrl is present in the authService, e.g. if a user tried to access protected resource some time before.
scope.loginCallback = function(response) {
authService.saveToken(response.data);
jasprApi.User.me().then(function(response) {
$rootScope.user = response.data;
$(element).modal('hide');
if (authService.targetUrl) {
$window.location.href = authService.targetUrl;
authService.targetUrl = null;
}
});
};
routes.js
Here is the ui-router states config which specified if the state should be protected
.state('admin', {
url: '/admin',
//other configuration
//...
//...
authenticate: true
})
api.js
A bonus — this is the sample from the file with the methods for accessing the API. Please note how authService is used here.
updatePageAction: function() {
return authService.getAuthorizationHeader().then(function(authHeader) {
return $http({
method: 'PUT',
url: '/admin/page/update',
headers: authHeader
});
});
},
I hope it helps!
Cheers
In the client-side of a METEORJS application, i have a controller that display some users.
I have a problem with Meteor.user() function, the error is : Meteor.user(...) is undefined.
Here is my code :
this.AdminUsersController = RouteController.extend({
template: "Admin",
yieldTemplates: {
'AdminUsers': { to: 'AdminSubcontent'}
},
onBeforeAction: function() {
var permissions = Meteor.user().profile.permissions;
if (permissions && permissions.indexOf('Users') != -1)
this.next();
else this.redirect('/admin/dashboard');
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("Admin"); this.render("loading", { to: "AdminSubcontent" });}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("users")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
var data = {
params: this.params || {},
users: Users.find({labo_id: Meteor.user().profile.labo_id}, {sort: {createdAt:-1}})
};
return data;
},
onAfterAction: function() {
}});
It's in the data function.
I try to retrieve all users that are connected to the logged in user and got the same labo_id field...
I don't know why it give me that, because in the onBeforeAction function, i can access to Meteor.user(), and specially his profile...
Someone know what can i do to make it run ?
Thanks for your future answers :)
This is a timing issue. Meteor.user() does not necessarily return data, if it hasn't been loaded yet. Meteor.userId() however will return the _id of the user record (if they are logged in). If you can change your query to rely on that _id, then it can work.
Depending on which router you are using, you can add a resolve: entry to ensure that the route waits for the user record to be loaded before activating it, and then your query will work.
Here is my module config and run:
angular.module('FXBApp', [
'ui.bootstrap'
,'ui.router'
,'oc.lazyLoad'
,'parse-angular'
]).
config(['$urlRouterProvider','$stateProvider', '$ocLazyLoadProvider',
function($urlRouterProvider,$stateProvider,$ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
debug:false,
events:true
});
$urlRouterProvider.otherwise('/login');
$stateProvider
.state('login', {
url: "/login",
templateUrl: "views/login/login.html",
controller:'LoginCtrl',
resolve:{
loadMyFiles:function($ocLazyLoad){
return $ocLazyLoad.load({
name: 'FXBApp',
files: ['scripts/controllers/login.js']
})
}
}
})
.state('dashboard', {
url: "/dashboard",
templateUrl: "views/dashboard/main.html",
controller:'DashboardMainCtrl',
resolve:{
loadMyFiles:function($ocLazyLoad){
return $ocLazyLoad.load({
name: 'FXBApp',
files: [
'views/dashboard/dashboard.main.js'
,'views/dashboard/ribbon.js'
,'scripts/directives/sidebar/sidebar.js'
]
})
}
}
})
.state('dashboard.home', {
url: "/dashboard/home",
templateUrl: "views/dashboard/home.html",
controller:'DashboardHomeCtrl',
resolve:{
loadMyFiles:function($ocLazyLoad){
return $ocLazyLoad.load({
name: 'FXBApp',
files: [
'scripts/controllers/dashboard.home.js'
,'scripts/services/authentication.js'
,'scripts/directives/stats/stats.js'
,'scripts/services/dbstats.js'
]
})
}
}
})
run(['$rootScope','$state', function ($rootScope,$state) {
Parse.initialize("$$$$$$$", "######");
$rootScope.sessionUser ={};
$rootScope.isLoggedIn = function () {
if (!Parse.User.current()) $state.go('login');
};
$rootScope.logOut = function () {
Parse.User.logOut()
};
$rootScope.authUser = function (usr, pwd) {
//TODO: assure user is active and e-mail is verified
return Parse.User.logIn(usr,pwd).then(function (user) {
var qr = new Parse.Query(Parse.Role);
qr.equalTo('users', Parse.User.current().id);
return qr.first().then(function (e) {
try {
if (e.getName() != 'admin' && e.getName() != 'team_leader') {
return {err: true, userId: user.id, msg: 'User does not have permission to access'}
} else {
$rootScope.sessionUser = user;
return {err: false, userId: user.id}
}
} catch (e) {
return {err: true, userId: user.id, msg: 'User not assigned to any role--'+e}
}
}, function (e){
return {err: true, userId: user.id, msg:e.msg}
});
}, function (error) {
return {err:true,msg:error.message}
});
};
$rootScope.isLoggedIn()
}]);
As you can see I am using ui router as well as lazy loading to manage the app performance properly.
As soon as the login sequence initiated form the login controller using $rootScope.authUser($scope.usr,$scope.pwd), if I use then() close attached to this call I can console Parse.User.current() which shows the current user.
Routing works fine after login, the tricky part is when I tried to fetch data from parse.com class allowed for the user after the successful login; it always gives 403 error. Investigating the cause, I found that Parse.User.current() is returning null while $rootScope.sessionUser holds the right reference to the logged in user.
I tried using 'Parse.Session.getSessionToken' and the error was 'no current user'. I tried using Parse.User.become($rootScope.sessionUsr.getSessionToken()) but things get worse as now I am getting the invalid session token error and have to go to the chrome console to delete parse sdk objects from storage.
Any idea what I have done wrong?
Was a long journey till I discovered the root cause of my problem. It was not related to Parse SDK nor angular. It was related to a directive that shows the logout button (not shown in the problem post) this directive was calling doLogOut $scope function which was in turn triggering the sign-out sequence using Parse.User.logOut().
Every time this directive loads after the login the function doLogOut was being called resulting in clearing the session in Parse and causing system to be unable to do any restricted action on the db.
The reason that the doLogout was being triggered instead of waiting 'ng-click' event was simply that angular is evaluating $scope every time it loads. This means it will evaluate 'doLogOut' on load and not waiting for an event. Changing 'doLogOut' to 'doLogOut()` fixed the problem.
A stupid mistake created a lot of complexity by the end.
I'm trying to persist the Firebase user authentication state across multiple pages.
$scope.loginGoogle = function() {
console.log("Got into google login");
ref.authWithOAuthPopup("google", function(error, authData) {
$scope.loggedIn = true;
$scope.uniqueid = authData.google.displayName;
}, {
remember: "sessionOnly",
scope: "email"
});
};
function checkLogin() {
ref.onAuth(function(authData) {
if (authData) {
// user authenticated with Firebase
console.log("User ID: " + authData.uid + ", Provider: " + authData.provider);
} else {
console.log("Nope, user is not logged in.");
}
});
};
However, when the checkLogin function is called in another page, authData is not defined, even though the user has logged in on the login page. What seems to be the matter?
There are two things to know here.
First, you're using the JS Client auth methods in conjunction with AngularFire. While this is not a bad thing, you need to be aware of a few gotchas.
Second, you can use the $firebaseAuth module in AngularFire 0.9 to not deal with all of the crazy stuff below.
When using Firebase JS client level functions, Angular will not always pick up on them due to its digest loop. This is true for any external JS library. The way to get around this is to use the $timeout service.
CodePen
// inject the $timeout service
app.controller("fluttrCtrl", function($scope, $firebase, $timeout) {
var url = "https://crowdfluttr.firebaseio.com/";
var ref = new Firebase(url);
$scope.loginGoogle = function() {
console.log("Got into google login");
ref.authWithOAuthPopup("google", function(error, authData) {
// wrap this in a timeout to allow angular to display it on the next digest loop
$timeout(function() {
$scope.loggedIn = true;
$scope.uniqueid = authData.google.displayName;
});
}, {
remember: "sessionOnly",
scope: "email"
});
});
});
By wrapping the $scope properties in the $timeout another cycle will be run and it will display on the page.
Ideally, you don't want to deal with this yourself. Use $firebaseAuth module built into AngularFire. You need to upgrade to the 0.9 version to use the module.
I have a Login API which I am using it in my service
function logInToService(callback, errback, login, password, rememberLogin) {
var url = "User/Login";
var authorizationHeader = {'Authorization': "Basic " + login + ":" + password};
httpWrapperService.post(url, {login: login, password: password}, authorizationHeader).then(
function success(loginToken) {
// transform data here
self.currentUser.emailAddress = login;
self.currentUser.password = password;
// store token in a cookie
self.currentUser.token = loginToken;
$rootScope.currentUser = self.currentUser;
if (rememberLogin) {
localStorage.userName=login;
localStorage.password=password;
}
httpWrapperService.setAuthenticationToken(loginToken);
callback(loginToken);
},
function error(errorObject) {
errback(errorObject);
}
);
}
And in my header html I am displaying the user name on top when he gets logged in
Header.html
<div class="header-user-menu">
<div ng-show="isUserLoggedIn() == false ">
{{'HEADER.LOGIN' | translate}}
<!--<a ui-sref="app.sr.login" class="">{{'HEADER.LOGIN' | translate}}</a>-->
</div>
<div ng-show="isUserLoggedIn()" class="userName">{{'HEADER.WELCOME' | translate}} {{currentUser.emailAddress}}</div>
</div>
and js file is here
(function() {
'use strict';
angular
.module('safe-repository')
.controller('AppLayoutCtrl', appLayoutCtrl);
// Implementation of controller
appLayoutCtrl.$inject = ['$rootScope', '$scope'];
function appLayoutCtrl($rootScope, $scope) {
$scope.isUserLoggedIn = isUserLoggedIn;
function isUserLoggedIn() {
if ($rootScope.currentUser
&& $rootScope.currentUser.emailAddress != null
&& $rootScope.currentUser.emailAddress != '') {
return true;
}
return false;
}
}
})();
and here I have one registration service where I have defined logInToService method
function registrationService($rootScope, httpWrapperService, $modal, $state, $cookieStore) {
var self = this;
// expose functions
self.hasUserAccessToLevel = hasUserAccessToLevel;
self.logInToService = logInToService;
self.getCurrentUserToken = getCurrentUserToken;
self.showRegistrationViewForLevel = showRegistrationViewForLevel;
self.currentUser = {
//emailAddress: '',
//password: '',
//token: ''
}
$rootScope.currentUser = null;
self.currentUserToken = null;
function logInToservice (------){----}})();
The problem is that every time when user presses page refresh F5 , user gets logged out. Even though I am trying to store the data in localstorage , but actually I am not getting the flow of control.
You are trying to save the data into localStorage, but you are not reading it.
The currentUser variable you have the user data in is a regular javascript variable which gets reset when you reload the page.
You need to do something like this:
// ...
// user logs in, remember it into the local storage
// Note: is is better to use it via $window.localStorage
$window.localStorage.setItem('user', JSON.stringify(user));
this.currentUser = user;
//...
// function to get the user, if this.currentUser is not set,
// try to load from the local storage
getUser: function() {
if (this.currentUser) {
return this.currentUser;
}
var storageUser = $window.localStorage.getItem('user');
if (storageUser) {
try {
this.user = JSON.parse(storageUser);
} catch (e) {
$window.localStorage.removeItem('user');
}
}
return this.currentUser;
}
// you may also want to remove the user data from storage when he logs out
logout: function() {
$window.localStorage.removeItem('user');
this.currentUser = null;
},
you need to create a session for login so that it does not log out after page refresh. have a look at this link:
http://maffrigby.com/maintaining-session-info-in-angularjs-when-you-refresh-the-page/