i am newbie in AngularJS, i have read a tutorial about login and authentication with angular js but i still confused in many points of my code, for now i have arrived to login and to store a token in browser's session, but i can't redirect to home page after loggin in,
here is myservice :
function authenticationSvc ($http, $q, $window, auth_uri) {
var userInfo;
function login(username, password) {
var deferred = $q.defer();
$http.post(auth_uri, {
username: username,
password: password
}).then(function(result) {
userInfo = {
accessToken: result.data.token
};
$window.sessionStorage["pallas_token"] = result.data.token;
deferred.resolve(userInfo);
},
function(error) {
deferred.reject(error);
});
return deferred.promise;
}
function getUserInfo() {
return userInfo;
}
return {
login: login,
getUserInfo: getUserInfo
};
};
and this is my state config
.state('dashboard', {
url:'/dashboard',
controller: 'HomeController',
templateUrl: 'partials/dashboard/main.html',
resolve:{
auth: function($q, authenticationSvc) {
var userInfo = authenticationSvc.getUserInfo();
if (userInfo) {
return $q.when(userInfo);
} else {
return $q.reject({ authenticated: false });
}
}
}
}
finally this my .run block:
angular
.module ( 'mainApp' )
.run ( function ( $rootScope, $state, $location) {
$rootScope.$on('$stateChangeSuccess', function( userInfo) {
console.log( userInfo );
});
$rootScope.$on('$stateChangeError', function(evt, toState, toParams, fromState, fromParams, error) {
if (error.authenticated == false) {
$state.transitionTo("login");
}
});
});
please help me to resolve this issue, i need help my friends :(
i am sorry for missing to post my login controller, there is:
function LoginController($scope, $state, authenticationSvc){
$scope.submit = function(credentials){
authenticationSvc.login(credentials.username, credentials.password);
};
};
Your login method return a success promise when the user pass the authentication. So.. you can edit your controller in this way:
function LoginController($scope, $state, authenticationSvc){
$scope.submit = function(credentials){
authenticationSvc.login(credentials.username, credentials.password).then(function(){
$state.go('dashboard');
console.log('User logged in!');
}).catch(function(){
console.log('User NOT logged in!');
});
};
};
UPDATE
To maintain the state after a page refresh you need to restore the userInfo object from sessionStorage. I also added the logout logic! Take a look:
function authenticationSvc ($http, $q, $window, auth_uri) {
var userInfo;
function login(username, password) {
...
}
function logout() {
$window.sessionStorage.removeItem("pallas_token");
userInfo = null;
}
function getUserInfo() {
return userInfo;
}
function init(){
if ($window.sessionStorage["pallas_token"]){
userInfo = {
accessToken: $window.sessionStorage["pallas_token"]
};
}
}
init();
return {
login: login,
logout: logout,
getUserInfo: getUserInfo
};
};
Logout:
function LoginController($scope, $state, authenticationSvc){
$scope.submit = function(credentials){
...
};
$scope.logout = function(){
authenticationSvc.logout();
$state.go('login');
console.log('User logged out!');
};
};
Enjoy!
Related
I am using Facebook SDK to build an application however I am faced with a challenge, I try to get the login status of the user before I redirect him to his profile page, however during the call to get the login status I get the error that
ReferenceError: FB is not defined
now the SDK is being loaded asynchronously so the error makes sense, how can i resolve the problem. Here is my code:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "Views/ListPages.html",
controller: 'MainCtrl',
resolve: {
authentication:["$location", "LoginFactory", function($location, LoginFactory){
console.log("in resolve");
LoginFactory.getLoginStatus()
.then(function(response){
if(response){
$location.path('/login');
}
else{
$location.path('/');
}
});
}]
}
})
.when('/login', {
templateUrl: "Views/Login.html",
controller: 'LoginCtrl'
})
.otherwise
({redirectTo: '/'});
});
loginApp.factory("LoginFactory", function ($rootScope, $q, $location, UserInfo) {
return {
getLoginStatus: function () {
var deferred = $q.defer();
FB.getLoginStatus(function (response) {
if(!response || response.error){
deferred.reject(new error("User Not logged in."));
}
else{
deferred.resolve(response);
}
});
return deferred.promise;
},
login: function () {
FB.login(function (response) {
if (response.authResponse === "connected") {
$rootScope.$broadcast('fb_connected', {facebook_id:response.authResponse.userID});
} else {
$rootScope.$broadcast('fb_login_failed');
}
}, {scope: "read_insights, publish_pages, manage_pages"});
},
logout: function () {
FB.logout(function (response) {
if (response) {
$rootScope.$broadcast('fb_logout_succeded');
$location.path('/login');
} else {
$rootScope.$broadcast('fb_logout_failed');
}
});
}
};
angular.module("LoginCtrlModule", ["FacebookLogin"])
.controller("LoginCtrl", ["$scope", "$location", "LoginFactory", function ($scope, $location, LoginFactory) {
$scope.login = function () {
LoginFactory.login();
$scope.on("fb_connected", function () {
$location.path("/");
});
$scope.on("fb_login_failed", function(){
$location.path("/login");
});
}
}]);
app.run(function ($rootScope, $location, LoginFactory) {
window.fbAsyncInit = function () {
FB.init({
appId: '',
status: true,
cookie: true,
xfbml: true
});
};
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement('script');
js.id = id;
js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
});
I was following these posts:
AngularJS- Login and Authentication in each route and controller
http://www.sitepoint.com/implementing-authentication-angular-applications/
But the problem i am facing is different.
Thanks for the help.
I think the solution is to not call the FB.getLoginStatus() but rather have an object that contains the User information and the access token, each time I try to route I should check if the User info object is null, in case if its not null then call the check login status method and accordingly take the route or not to take the route.
I am creating a simple login application using HTTP Auth Interceptor Module.
in my LoginController I have:
angular.module('Authentication')
.controller('LoginController',
['$scope', '$rootScope', '$location', 'AuthenticationService',
function ($scope, $rootScope, $location, AuthenticationService) {
// reset login status
AuthenticationService.ClearCredentials();
$scope.login = function () {
$scope.dataLoading = true;
AuthenticationService.Login($scope.username, $scope.password, function (response) {
if (response.success) {
AuthenticationService.SetCredentials($scope.username, $scope.password);
$location.path('/');
} else {
$scope.error = response.message;
$scope.dataLoading = false;
}
});
};
}]);
and following is the simple service for it:
angular.module('Authentication')
.factory('AuthenticationService',
['Base64', '$http', '$cookieStore', '$rootScope', '$timeout',
function (Base64, $http, $cookieStore, $rootScope, $timeout, $scope) {
var service = {};
service.Login = function ($scope, username, password, callback) {
$http
.get('http://Foo.com/api/Login',
{ username: username, password: password } , {withCredentials: true}).
then(function (response) {
console.log('logged in successfully');
callback(response);
}, function (error) {
console.log('Username or password is incorrect');
});
};
service.SetCredentials = function (username, password) {
var authdata = Base64.encode(username + ':' + password);
$rootScope.globals = {
currentUser: {
username: username,
authdata: authdata
}
};
$http.defaults.headers.common['Authorization'] = 'Basic ' + authdata;
$http.defaults.headers.common['Content-Type'] = 'application/json'
$cookieStore.put('globals', $rootScope.globals);
};
service.ClearCredentials = function () {
$rootScope.globals = {};
$cookieStore.remove('globals');
$http.defaults.headers.common.Authorization = 'Basic ';
};
return service;
}])
This is my login page:
as I try to test this in browser, instead of successful login or even receiving an error, I get this popup:
what I don't get is that why the credential passed from the login form is not taken into account. and how can I get rid of this popup.
as I cancel this popup, again instead of getting the error for http request, in console I get 401 (Unauthorized) Error.
What am I missing?
I also ran in in Emulator and instead of getting any error, the application stays on loading part.
Change your URL to something like
.get('https://username:password#Foo.com/api/Login',...
use the following sample to handle 401 error:
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push(['$q', function ($q) {
return {
'responseError': function (rejection) {
if (rejection.status === 401) {
console.log('Got a 401');
}
return $q.reject(rejection)
}
}
}])
}])
Here is the working code snippet. Just copy paste from here.
routerApp.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push(['$q', function ($q) {
return {
'responseError': function (rejection) {
if (rejection.status === 401) {
window.console.log('Got a 401');
}
return $q.reject(rejection);
}
};
}]);
}]);
Here is how I'm doing it so far:
angular
.module('mean-starter')
.factory('Auth', function($http, $state, $window, $cookies) {
var currentUser = {};
return {
signup: function(user) {
return $http
.post('/users', user)
.then(function(data, status, headers, config) {
angular.copy(data, currentUser);
$cookies.put('userId', data._id);
$window.location.href = '/';
})
;
},
login: function(user) {
return $http
.post('/login', user)
.then(function(data) {
angular.copy(data, currentUser);
$cookies.put('userId', data._id);
$window.location.href = '/';
})
;
},
logout: function() {
$http
.get('/logout')
.then(function() {
angular.copy({}, currentUser);
$cookies.remove('userId');
$window.location.href = '/';
})
.catch(function() {
console.log('Problem logging out.');
})
;
},
getCurrentUser: function() {
// user is logged in
if (currentUser._id) {
return currentUser;
}
// user is logged in, but page has been refreshed and currentUser is lost
if ($cookies.get('userId')) {
return $http.get('/current-user')
.then(function(data) {
angular.copy(data, currentUser);
})
;
}
// user isn't logged in
else {
return currentUser;
}
},
isLoggedIn: function() {
return !!currentUser._id;
}
};
})
;
After a page reload, the Auth factory gets re-run and currentUser is reassigned to {}. So if the user was logged in, currentUser won't reflect it. So I have to check for the case where !currentUser._id && $cookies.get('userId') and if so, query the database for the currently logged in user.
Now I want to access currentUser:
angular
.module('mean-starter')
.run(run)
;
function run($rootScope, Auth, $state) {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams) {
if (typeof toState.authenticate !== 'undefined') {
var currentUser = Auth.getCurrentUser();
var admin = currentUser.role === 'admin';
var authorized = currentUser._id.toString() === toParams.id;
if (!Auth.isLoggedIn()) {
event.preventDefault();
alert('Must be logged in to access this route.');
$state.go('login');
}
else if (toState.authenticate.authorized) {
if (!admin && !authorized) {
event.preventDefault();
alert('You are not authorized to access that route.');
}
}
else if (toState.authenticate.isAdmin) {
if (!admin) {
event.preventDefault();
alert('You must be an admin to access this route.');
}
}
}
});
}
The problem is that I don't know whether or not Auth.getCurrentUser() will return the user or a promise. How can I check for this? How should this be architected?
Why not just always return a promise in your getCurrentUser with the help of $q?
So something like this
getCurrentUser: function() {
if (currentUser._id || !$cookies.get('userId')) {
// $q.when will wrap your currentUser into a promise
return $q.when(currentUser);
}
return $http.get('/current-user')
.then(function(data) {
angular.copy(data, currentUser);
return currentUser;
});
}
}
and in your controller:
Auth.getCurrentUser().then(function(currentUser) {
// Your code
})
You can adapt your function to return a promise in both cases using $q. In this case, all three logical paths should result in the same outcome, albeit by different sets of operations in between, therefore a promise would work perfectly here. Especially because you can have very specific control over the error handling flow (if needed)
http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
I am new to angular and probably doing this completely wrong. On the /sites controller page, I want to access verifyAuth.getUserSiteAccess() to return a list of sites and build html links for the view.
I am using a google auth module, if the user logs in the userSites var is empty so I ping google, then call /api/index.php/login to return a list of user sites, then in this case finish with $q.defer().resolve(true);. Problem is the site controller function is trying to access userSites before it is defined. Is there a way to call $scope.test() after $q.defer().resolve is finished? or is there a better way to do this?
If I run setTimeout($scope.test, 500) it works fine.
Route -> Verify user access token, load userSites if undefined -> verify section access -> complete defer.
Site controller
'use strict';
angular.module('mps.sites', ['ngRoute'])
.controller('sites', ['verifyAuth', '$rootScope', '$scope', '$q', function(verifyAuth, $rootScope, $scope, $q) {
$scope.test = function() {
var test = verifyAuth.getUserSiteAccess();
console.log('test', test, '/test');
};
$scope.test();
}]);
** App.js routing and auth ** - not entire file...
'use strict';
angular.module('mps', [
'ngRoute',
'angularOauth',
'googleOauth',
'mps.global',
'mps.home',
'mps.sites',
'mps.site'
]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'views/home/index.html',
controller: 'home',
resolve: {
auth: function(verifyAuth) {
verifyAuth.verifyUserAccess(true);
}
}
});
$routeProvider.when('/sites', {
templateUrl: 'views/sites/index.html',
controller: 'sites',
resolve: {
auth: function(verifyAuth) {
console.log('sites route selected');
verifyAuth.verifyUserAccess(false);
}
}
});
...
.factory('verifyAuth', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token',
function($rootScope, $window, $q, $location, $timeout, $http, Token) {
var userSites = null;
return {
deferLocation: function(isToken, index) {
var deferred = $q.defer();
var _location = $location;
if(isToken) {
switch(index) {
case true:
// Homepage/site toggling.
deferred.reject();
_location.path('/sites');
_location.replace();
break;
default:
// All pages.
deferred.resolve(true);
break;
}
} else {
// No token, redirect to login screen.
this.userError();
}
},
verifySectionAccess: function(userSites, siteName, index) {
if(siteName) {
// Subpage, verify section.
for(var i in userSites.sites) {
if(userSites.sites[i].sitename === siteName) {
this.deferLocation(true, index);
return false;
}
}
} else {
// Sites page.
this.deferLocation(true, index);
return false;
}
// No access to section.
this.userError();
return false;
},
// Check user permission are set.
verifyUserAccess: function (index, siteName) {
var token = Token.get();
var _this = this;
if(token) {
if(userSites) {
// Verify user section access.
_this.verifySectionAccess(userSites, siteName, index);
} else {
// Use google token to get user email and load site permissions.
Token.verifyAsync(token).
then(function(data) {
$http({method: 'GET', async: false, url: '/api/index.php/login/' + data.email}).success(function(d) {
userSites = d;
// Verify user access to specific section.
_this.verifySectionAccess(userSites, siteName, index);
});
}, function(error) {
_this.userError();
return false;
}
);
}
} else {
this.deferLocation(false, index);
}
},
getUserSiteAccess: function() {
console.log(userSites);
return userSites;
}
You have a number of issues here, all seem to be stemming from a misunderstanding of how promises work:
1) Functions that make async operations and are .then-able need to return a promise.
In your case, your deferLocation creates a promise (although doesn't return it), but it does not even do anything async.
On the other hand, the only function that does do something async (verifyUserAccess) doesn't have promises at all.
2) If you want a resolve parameter to be resolved with async value (as it seems to be with auth), then the function needs to return a promise. Then $route will wait until the promise is resolved. In your case, you don't return anything.
I suggest you read more about promises. Build something small with mock $timeout calls and ask specific questions if you run into problems.
Here's a high-level idea of how to build this:
app.factory("verifyAuth", function($q, Token, AuthSvc, SitesSvc) {
return {
verifyUserAccess: function(site){
var deferred = $q.defer();
var token = Token.token;
if (!token){
deferred.reject("no-token");
} else {
AuthSvc.verifyToken(token)
.then(function(result){
if (result.isValid){
// returns a promise to get a list of userSites
return SitesSvc.getSites(result.user);
} else {
return deferred.reject("token-not-valid");
}
})
.then(function(userSites){
if (checkAccessPermissions(site, userSites)){
deferred.resolve(true);
} else {
deferred.resolve(false);
}
})
.catch(function(error){
// some other error
deferred.reject(error);
});
}
return deferred.promise;
}
};
});
Router calls return verifyUser.buildUserData in the resolve, this checks if there is a token, if not logs the user out. Then checks if there is a site list global variable, if not pings the token to google to get the user email and the user email to the database to get the site list, from there loop through the list and check auth if a site name is passed into buildUserData.
The below example will handle all the auth before the view is rendered. Thanks New Dev for pointing me in the right direction.
Router:
...
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'views/home/index.html',
controller: 'home',
resolve: {
auth: function(verifyUser) {
return verifyUser.homepageRedirect();
}
}
});
$routeProvider.when('/sites', {
templateUrl: 'views/sites/index.html',
controller: 'sites',
resolve: {
auth: function(verifyUser) {
return verifyUser.buildUserData();
}
}
});
$routeProvider.when('/sites/:site/scheduling', {
templateUrl: 'views/sites/scheduling/index.html',
controller: 'site',
resolve: {
auth: function(verifyUser, $route) {
return verifyUser.buildUserData($route.current.params.site);
}
}
});
...
Factories
.factory('getUserEmail', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token', function($rootScope, $window, $q, $location, $timeout, $http, Token) {
return {
// Get user email address based on token.
getEmail: function() {
var deferred = $q.defer();
var token = Token.get();
$http({method: 'GET', async: false, url: 'https://www.googleapis.com/oauth2/v1/tokeninfo', params: {access_token: token }}).
success(function(data) {
$rootScope.username = data.email;
deferred.resolve(data);
return data;
}).error(function(data) {
deferred.reject(data);
});
return deferred.promise;
}
}
}])
.factory('getUserSites', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token', function($rootScope, $window, $q, $location, $timeout, $http, Token) {
return {
// Get user site access.
getSites: function() {
var deferred = $q.defer();
console.log('site list is undefined.');
$http({method: 'GET', async: false, url: '/api/index.php/login/' + $rootScope.username}).
success(function(data) {
$rootScope.sites = data.sites;
deferred.resolve(data);
}).error(function(data) {
deferred.reject(data);
});
return deferred.promise;
}
}
}])
.factory('verifyUser', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token', 'getUserEmail', 'getUserSites', function($rootScope, $window, $q, $location, $timeout, $http, Token, getUserEmail, getUserSites) {
return {
siteError: function() {
localStorage.removeItem('accessToken');
$location.path('/');
},
// Redirect user to /sites if logged in.
homepageRedirect: function() {
var deferred = $q.defer();
var token = Token.get();
if(!token) {
deferred.resolve(true);
} else {
deferred.reject(true);
$location.path('/sites');
}
return deferred.promise;
},
// Verify user token exists and they have access to the section.
buildUserData: function(site) {
console.log('site',site,'/site');
var deferred = $q.defer();
var token = Token.get();
var _this = this;
if(!token) {
deferred.reject('no token');
localStorage.removeItem('accessToken');
$location.path('/');
} else {
if($rootScope.sites) {
console.log('site list present, check access.');
if(site) {
var data = $rootScope.sites;
console.log(data, site);
for(var i in data) {
console.log(data[i].sitename);
if(data[i].sitename === site) {
console.log('user has access, render view.');
deferred.resolve(true);
return false;
}
}
console.log('No site access, logout.');
deferred.reject('No access to site.');
_this.siteError();
} else {
console.log('No access needed, landing page.');
deferred.resolve(true);
}
} else {
console.log('no site list, get user email from google and query db with user.');
getUserEmail.getEmail().then(function(data) {
return getUserSites.getSites();
}).then(function(data) {
if(site) {
console.log('sub page');
for(var i in data.sites) {
console.log(data.site[i]);
if(data.sites[i].sitename === site) {
console.log('user has access, render view.');
deferred.resolve(true);
return false;
}
}
console.log('No site access, logout.');
deferred.reject('No access to site.');
_this.siteError();
} else {
deferred.resolve(true);
}
}).catch(function(data) {
deferred.reject(true);
_this.siteError();
});
}
}
return deferred.promise;
}
}
}]);
Is it possible within the AuthService to handle the response and set the session with the SessionService?
I doing it in the controller right now with the success callback but I'm still new to Angularjs and trying to understand how to customize a resource.
I'm using Angularjs 1.1.5
app.factory('AuthService', ['$resource', 'SessionService',
function($resource, SessionService) {
return $resource(
'/api/v1/auth/:action',
{action:'#action'},
{
login: {
method:'POST',
params: {
action: 'login'
}
},
logout: {
method:'GET',
params: {
action: 'logout'
}
}
}
);
}
]);
app.controller('LoginCtrl', ['$scope', '$location', 'AuthService', 'SessionService' function LoginCtrl($scope, $location, AuthService, SessionService) {
$scope.credentials = { email: "", password: ""};
$scope.login = function() {
AuthService.login($scope.credentials).success(function() {
SessionService.set('authenticated', true);
$location.path('/home');
});
}
}]);
app.factory("SessionService", function() {
return {
get: function(key) {
return sessionStorage.getItem(key);
},
set: function(key, val) {
return sessionStorage.setItem(key, val);
},
unset: function(key) {
return sessionStorage.removeItem(key);
}
}
});
I'm not sure that $resource is the appropriate abstraction to use here. I think it would be much simpler to implement your AuthService using plain $http. Just implement login and logout as normal function, then you can feel free to do whatever you want there. You should also make sure you return the promise, that way whoever calls login() or logout() can still do .then() on it if they need to do additional things after login. Here's an example:
app.factory('AuthService', ['$http', '$location' 'SessionService',
function($http, $location, SessionService) {
var baseUrl = '/api/v1/auth/';
function onLoginSuccess(data){
SessionService.set('authenticated', true);
$location.path('/home');
}
function onLoginFailure(error){
SessionService.unset('authenticated');
$location.path('/login');
}
return {
login: function(credentials){
return $http.post(baseUrl+'login', credential).then(onLoginSuccess, onLoginFailure);
}
logout: function(){
return $http.get(baseUrl+'logout');
}
};
app.controller('LoginCtrl', ['$scope', 'AuthService', function LoginCtrl($scope, AuthService) {
$scope.credentials = { email: "", password: ""};
$scope.login = function() {
AuthService.login($scope.credentials);
}
}]);
app.factory("SessionService", function() {
return {
get: function(key) {
return sessionStorage.getItem(key);
},
set: function(key, val) {
return sessionStorage.setItem(key, val);
},
unset: function(key) {
return sessionStorage.removeItem(key);
}
}
});
Your server side script on path /api/v1/auth/login should return a result to indicate that the login is successfully granted or not.
For example, If the log in is granted, then /api/v1/auth/login returns the success response 200.
If the login is denied, then /api/v1/auth/login returns the bad request response (failure) 400.
If with this condition, your code should be written as followed,
app.controller('LoginCtrl', ['$scope', '$location', 'AuthService', 'SessionService'
function LoginCtrl($scope, $location, AuthService, SessionService) {
$scope.credentials = { email: "", password: ""};
$scope.login = function() {
AuthService.login($scope.credentials, function(data) { //Success callback
SessionService.set('authenticated', true);
$location.path('/home');
}, function(error){ //Failure callback
SessionService.unset('authenticated');
$location.path('/login');
});
}
}]);