I have to allow user to access application when user is logged in
and if user is not logged in then user should not be able to access the application by simply redirecting through url
In above case user should redirect to login page if he is not logged in
I tried in below way
$rootScope.tempObj = false;
$rootScope.$on('$stateChangeStart', function (event, toState) {
if($rootScope.tempObj) {
return;
} else {
$state.go('login');
event.preventDefault();
return;
}
});
Here i am doing $rootScope.tempObj = true in login success callback
But I am getting an error - RangeError: Maximum call stack size exceeded
My question is how to block url redirection if user is not logged in..
and allow few state like forgot password, reset password, etc without user logged in
use it with the event.preventDefault();
$rootScope.$on('$stateChangeStart', function(event, toState) {
if ($rootScope.tempObj) {
event.preventDefault();
} else if (!$rootScope.tempObj) {
$state.go('login');
event.preventDefault();
}
});
check the similar question
i think this way isn't perfect way to check user, if your application deals with backend by sending http requests you should test user privileges from $httpProvider, so in $httpProvider if you get a http code in 400 range, you should redirect user to login page,
i'll put a snipping code to do this
.config(["$httpProvider", "$cookiesProvider", function($httpProvider, $cookiesProvider) {
$cookiesProvider.defaults.expires = new Date(new Date().getTime() + 120 * 60 * 1000).toString();
// $httpProvider.defaults.timeout = 50;
var interceptor = function($q, $rootScope, $injector, $location, $filter) {
return {
request: function(config) {
return config;
},
requestError: function(config) {
return config;
},
response: function(res) {
return res;
},
responseError: function(res) {
var AUTH_EVENTS = $injector.get('AUTH_EVENTS');
var $toastContent = $('<span>' + $filter('translate')(res.data.message) + '</span>');
if (res.status == 403) {
$location.path('/logOut');
}
if (res.status == 402) {
$location.path('/home');
Materialize.toast($toastContent, 4000, 'danger');
}
if (res.status == 401) {
Materialize.toast($toastContent, 4000, 'danger');
}
// }
return $q.reject(res);
}
}
}
interceptor.$inject = ["$q", "$rootScope", "$injector", "$location", "$filter"];
$httpProvider.interceptors.push(interceptor);
}])
Related
On my login page, when I enter wrong credentials (login/pass), an error message is displayed in red.
In login.html :
<div class="alert alert-danger" ng-show="authenticationError"
translate="login.messages.error.authentication">
<strong></strong>
</div>
If I set ng-show="true" explicitely, The message is persistent on the screen ; (fortunately).
The problem comes when ng-show is dynamic (with authenticationError variable)
When this variable is set to true, then the error message appears but left 1 second on the screen and then disappear.
Behind the scene (controller + service) :
login.controller.js :
$scope.login = function () {
// event.preventDefault();
Auth.login({
username: $scope.username,
password: $scope.password,
rememberMe: $scope.rememberMe
}).then(function () {
$scope.authenticationError = false;
// if()
if ($rootScope.previousStateName === 'register') {
$state.go('home');
} else {
$rootScope.back();
}
}).catch(function () {
$scope.authenticationError = true;
});
};
auth.service.js :
angular.module('tessicommunicationApp')
.factory('Auth', function Auth($rootScope, $state, $q, $translate, Principal, AuthServerProvider, Account, Register, Activate, Password, PasswordResetInit, PasswordResetFinish) {
return {
login: function (credentials, callback) {
var cb = callback || angular.noop;
var deferred = $q.defer();
console.log("login ...");
AuthServerProvider.login(credentials).then(function (data) {
// retrieve the logged account information
Principal.identity(true).then(function(account) {
// After the login the language will be changed to
// the language selected by the user during his registration
$translate.use(account.langKey).then(function(){
$translate.refresh();
});
deferred.resolve(data);
});
return cb();
}).catch(function (err) {
this.logout();
deferred.reject(err);
console.log("erreur login !");
return cb(err);
}.bind(this));
return deferred.promise;
},
auth.session.service.js :
angular
.module('tessicommunicationApp')
.factory(
'AuthServerProvider',
function loginService($http, localStorageService, $window, $state, $cookies) {
return {
login : function(credentials) {
var data = 'j_username='
+ encodeURIComponent(credentials.username)
+ '&j_password='
+ encodeURIComponent(credentials.password)
+ '&remember-me=' + credentials.rememberMe
+ '&submit=Login';
return $http
.post(
'api/authentication',
data,
{
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
}
}).success(function(response) {
return response;
});
},
Tell me if you need more code.
The context is :
1) I need to debug this ticket.
2) I didn't code anything of this web application (especially front-end part)
3) I almost have NO knowledges in AngularJS (just theory a little).
I wish you will help me to solve my first ticket :).*
Aren't you missing a parenthesis and extra parenthesis at the end of the statement here:
}).catch(function (err) {
this.logout();
deferred.reject(err);
console.log("erreur login !");
return cb(err);
}.bind(this));// <-- missing parenthesis
It should be like:
}).catch(function (err) {
this.logout();
deferred.reject(err);
console.log("erreur login !");
return cb(err);
}).bind(this);
Something else you can try when suspect about changes occurring without you know is monitoring the variable change with a $watch like:
$scope.countChange = 0;
$scope.$watch('authenticationError', function(newValue, oldValue) {
$scope.countChange = $scope.countChange + 1;
console.log('Change count :',$scope.countChange , ' New Value =', newValue );
});
I'm using Angular with Firebase and UI Router. I'm using anonymous authentication. When a session expires, I would like the unauthenticated user to be redirected to the homepage. I've used the Yeoman Angularfire Generator as a model. But when I use the code below, an authenticated page does not redirect when a user is already on that page and the session expires.
.config(['$urlRouterProvider', 'SECURED_ROUTES', function($urlRouterProvider, SECURED_ROUTES) {
$urlRouterProvider.whenAuthenticated = function(path, route) {
route.resolve = route.resolve || {};
route.resolve.user = ['Auth', function(Auth) {
return Auth.$requireAuth();
}];
$urlRouterProvider.when(path, route);
SECURED_ROUTES[path] = true;
return $urlRouterProvider;
};
}])
.run(['$rootScope', '$location', 'Auth', 'SECURED_ROUTES', '$state',
function($rootScope, $location, Auth, SECURED_ROUTES, $state) {
Auth.$onAuth(check);
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
if (error === 'AUTH_REQUIRED') {
$state.go('home');
}
else {
console.log('authenticated');
}
});
function check(user) {
if (!user && authRequired($location.path())) {
$state.go('home');
}
}
function authRequired(path) {
return SECURED_ROUTES.hasOwnProperty(path);
}
}
])
.constant('SECURED_ROUTES', {});
Router
.state('selection', {
url: '/selection',
authRequired: true,
views: {
'list': {
templateUrl: 'app/views/project-list.html',
controller: 'ProjectListCtrl as projectList',
resolve: {
'user': ['Auth', function(Auth) {
return Auth.$waitForAuth();
}]
}
},
'selectionlist': {
templateUrl: 'app/views/selection-list.html',
controller: 'SelectionListCtrl as selectionList'
resolve: {
'user': ['Auth', function(Auth) {
return Auth.$waitForAuth();
}]
}
}
}
})
This is a common problem in Single Page Applications. With Angular you can solve it with $http interceptor.
The idea is if the session is expired the first time when the user needs some action from the backend to be redirected to the login page.
Expired session is detected from the response itself.
Example:
.factory('httpAuthInterceptor', function ($q) {
return {
'responseError': function (response) {
// NOTE: detect error because of unauthenticated user
if ([401, 403].indexOf(response.status) >= 0) {
// redirecting to login page
$state.go('home');
return response;
} else {
return $q.reject(rejection);
}
}
};
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpAuthInterceptor');
});
Have you read through this?
https://www.firebase.com/docs/web/libraries/angular/guide/user-auth.html
I'd read the "Authenticating with Routers" section.
Edit
In that case you can grab the 'expires' value from Auth.$getAuth() and use it in a $timeout on $routeChangeSuccess. Essentially creating a countdown to session timeout after every route change...like so:
$rootScope.$on("$routeChangeSuccess", function() {
var sessionTimeout = Auth.$getAuth().expires * 1000;
var d = new Date();
var n = d.getTime();
var timer = sessionTimeout - n;
console.log(timer);
$timeout(function() {
$location.path("/");
},timer);
});
As you said "when a user is already on that page and the session expires.", that case you can use
$location.path('/home')
And for auto detect idle session expiration Ng-Idle, see the demo
I was able to get the redirect working with UI Router by modifying the following code from my question above:
From this (not working):
function check(user) {
if (!user && authRequired($location.path())) {
$state.go('home');
}
}
To this (working):
function check(user) {
if(!user && $state.current.authRequired===true) {
$state.go('home');
}
else {console.log('logged in')}
}
You can make use of ajaxSetup in order to catch 301 errors, something like this:
$.ajaxSetup({
error : function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status == 0) {
alert("Element not found.");
} else {
alert("Error: " + textStatus + ": " + errorThrown);
}
}
});
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 have a SPA app that uses Angular and Breeze, I need to implement the login functionality and I am new to Angular/Breeze. My architecture/code structure is as mentioned below:
login.html --> login.js -->datacontext/Service.js--->entityManagerFactory-->breezecontroller.cs -->repository->dbcontext-->database.
I am facing following challenges:
I am unable to show the login page as default, I am always getting Dashboard as a default page. I am looking for where I can route to login page.
2.breezecontroller -- This is inside controller, do I need to write my login method here?
All in all, I am looking for a complete login functionality implementation which following my architecture/code structure.
Here is a description of an approach that can be used in an Angular-based SPA. This particular example uses token-based OAuth authentication, but could be adapted to other authentication schemes. It is loosely based on the approach described at Authentication in AngularJS (or similar) based application
Some highlights are:
Authentication is managed through an auth service.
HTTP requests are intercepted, and:
When a 401 (access denied) error is detected and no user is logged in, an auth:login event is emitted (note - not broadcasted) on $rootScope
If a 401 error is detected while a user is logged in and an OAuth refresh token is available, an attempt is made to get a new access token based on the refresh token. An auth:login event is only emitted if the token cannot be refreshed.
Once a user has logged in, an Authorization header containing the user's access token is inserted onto each HTTP request so that the server can authenticate the user.
The application should watch for auth:login events and prompt the user for credentials. (I use an Angular-UI Bootstrap modal dialog for doing this.) Once credentials have been provided, the auth service's login function must be called to complete the login. After login is called, all pending HTTP requests that initially failed with a 401 error are retried. Alternatively, the auth service's loginCancelled function can be called to cancel the login, which will reject all pending HTTP requests.
For example:
angular.module('app', ['auth'])
.run(['$rootScope', 'auth', function ($rootScope, auth) {
$rootScope.$on(auth.options.loginRequiredEvent, function (event, details) {
// Display login dialog here, which will ultimately
// call `auth.login` or `auth.loginCancelled`
});
auth.restoreAuthDataFromStorage();
}]);
Here is an example of calling auth.login once the user has provided credentials:
auth.login(userName, password, isPersistent)
.success(function () {
// Dismiss login dialog here
})
.error(function (data, status) {
if (status === 401 || (data && data.error === 'invalid_grant')) {
failureMessage = 'Log in failed: Bad username or password';
} else {
failureMessage = 'Log in failed: Unexpected error';
}
});
Details of the logged in user are stored in window.sessionStorage or window.localStorage (based on whether a persistent login has been requested) to be able to be accessed across page loads.
Finally, here is the auth service itself.
var module = angular.module('auth');
module.provider('auth', function () {
var authOptions = {
tokenUrl: '/OAuthToken',
loginRequiredEvent: 'auth:loginRequired',
logoffEvent: 'auth:logoff',
loginEvent: 'auth:login',
authTokenKey: 'auth:accessToken'
};
this.config = function (options) {
angular.extend(authOptions, options);
};
// Get the auth service
this.$get = ['$rootScope', '$http', '$q', function ($rootScope, $http, $q) {
var authData = {
// Filled as follows when authenticated:
// currentUserName: '...',
// accessToken: '...',
// refreshToken: '...',
};
var httpRequestsPendingAuth = new HttpRequestsPendingAuthQueue();
// Public service API
return {
login: login,
refreshAccessToken: refreshAccessToken,
loginCancelled: loginCancelled,
logoff: logoff,
currentUserName: function () { return authData.currentUserName; },
isAuthenticated: function () { return !!authData.accessToken; },
getAccessToken: function () { return authData.accessToken; },
restoreAuthDataFromStorage: restoreAuthDataFromStorage,
_httpRequestsPendingAuth: httpRequestsPendingAuth,
options: authOptions,
};
function isAuthenticated() {
return !!authData.accessToken;
};
function restoreAuthDataFromStorage() {
// Would be better to use an Angular service to access local storage
var dataJson = window.sessionStorage.getItem(authOptions.authTokenKey) || window.localStorage.getItem(authOptions.authTokenKey);
authData = (dataJson ? JSON.parse(dataJson) : {});
}
function accessTokenObtained(data) {
if (!data || !data.access_token) {
throw new Error('No token data returned');
}
angular.extend(authData, {
accessToken: data.access_token,
refreshToken: data.refresh_token
});
// Would be better to use an Angular service to access local storage
var storage = (authData.isPersistent ? window.localStorage : window.sessionStorage);
storage.setItem(authOptions.authTokenKey, JSON.stringify(authData));
httpRequestsPendingAuth.retryAll($http);
}
function login(userName, password, isPersistent) {
// Data for obtaining token must be provided in a content type of application/x-www-form-urlencoded
var data = 'grant_type=password&username=' + encodeURIComponent(userName) + '&password=' + encodeURIComponent(password);
return $http
.post(authOptions.tokenUrl, data, { ignoreAuthFailure: true })
.success(function (data) {
authData = {
currentUserName: userName,
isPersistent: isPersistent
};
accessTokenObtained(data);
$rootScope.$emit(authOptions.loginEvent);
})
.error(function () {
logoff();
});
}
function refreshAccessToken() {
if (!authData.refreshToken) {
logoff();
return $q.reject('No refresh token available');
}
// Data for obtaining token must be provided in a content type of application/x-www-form-urlencoded
var data = 'grant_type=refresh_token&refresh_token=' + encodeURIComponent(authData.refreshToken);
return $http
.post(authOptions.tokenUrl, data, { ignoreAuthFailure: true })
.success(function (data) { accessTokenObtained(data); })
.error(function () { logoff(); });
}
function loginCancelled() {
httpRequestsPendingAuth.rejectAll();
}
function logoff() {
// Would be better to use an Angular service to access local storage
window.sessionStorage.removeItem(authOptions.authTokenKey);
window.localStorage.removeItem(authOptions.authTokenKey);
if (isAuthenticated()) {
authData = {};
$rootScope.$emit(authOptions.logoffEvent);
}
}
// Class implementing a queue of HTTP requests pending authorization
function HttpRequestsPendingAuthQueue() {
var q = [];
this.append = function (rejection, deferred) {
q.push({ rejection: rejection, deferred: deferred });
};
this.rejectAll = function () {
while (q.length > 0) {
var r = q.shift();
r.deferred.reject(r.rejection);
}
};
this.retryAll = function ($http) {
while (q.length > 0) {
var r = q.shift();
retryRequest($http, r.rejection.config, r.deferred);
}
};
function retryRequest($http, config, deferred) {
var configToUse = angular.extend(config, { ignoreAuthFailure: true });
$http(configToUse)
.then(function (response) {
deferred.resolve(response);
}, function (response) {
deferred.reject(response);
});
}
}
}];
});
module.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(['$injector', '$rootScope', '$q', function ($injector, $rootScope, $q) {
var auth;
return {
// Insert an "Authorization: Bearer <token>" header on each HTTP request
request: function (config) {
auth = auth || $injector.get('auth');
var token = auth.getAccessToken();
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = 'Bearer ' + token;
}
return config;
},
// Raise a "login required" event upon "401 access denied" responses on HTTP requests
responseError: function(rejection) {
if (rejection.status === 401 && !rejection.config.ignoreAuthFailure) {
var deferred = $q.defer();
auth = auth || $injector.get('auth');
auth._httpRequestsPendingAuth.append(rejection, deferred);
if (auth.isAuthenticated()) {
auth.refreshAccessToken().then(null, function () {
$rootScope.$emit(auth.options.loginRequiredEvent, { message: 'Login session has timed out. Please log in again.' });
});
} else {
// Not currently logged in and a request for a protected resource has been made: ask for a login
$rootScope.$emit(auth.options.loginRequiredEvent, { rejection: rejection });
}
return deferred.promise;
}
// otherwise, default behaviour
return $q.reject(rejection);
}
};
}]);
}]);
I have a basic authentication check on route change which simply checks the existence of a SessionStorage key/val. However, I have noticed that if an unauthenticated user navigates to a forbidden page who's controller contains an AJAX call, the AJAX call still happens in the background even though the forbidden page doesn't load and the user is instead redirected to login. To get around this, I have added the same authentication check in every controller. This is becoming a bit tedious and I was wondering if there is a better way, or a way I can globally check authentication before a controller's methods are run. Here is the auth function on route change:
app.run(function ($rootScope, $location, authenticationService) {
$rootScope.clearAlerts = function(){
$rootScope.alerts = []
}
$rootScope.credentials = {
username: "",
password: ""
};
$rootScope.goto = function(url){
$location.path(url);
}
$rootScope.authenticated = authenticationService.isLoggedIn()
$rootScope.logOut = function () {
authenticationService.logOut();
}
var publicRoutes = ['/login'];
$rootScope.$on('$routeChangeStart', function (event, next, current) {
$rootScope.credentials.from = $rootScope.desiredPath || '/login'
$rootScope.authenticated = authenticationService.isLoggedIn();
if (!_(publicRoutes).contains($location.path()) && !$rootScope.authenticated) {
$rootScope.desiredPath = $location.path();
$location.path('/login');
}
})
})
I suggest you should check authentication on server side and return appropriate message on client side
Below is the url help
http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application
and Write global handler in client side to check it
Below is a sample code:
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives'], function ($routeProvider, $locationProvider, $httpProvider) {
var interceptor = ['$rootScope', '$q', function (scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
window.location = "./index.html";
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);