I'm using Devise for authenticatication in my Rails and Angular app. I'm trying to do a conditional statement on one of the states based on whether a user is authenticated.
I'm using the onEnter callback function to determine if a user is authenticated.
routes:
// Dashboard state
.state('dashboard', {
url: '/dashboard',
templateUrl: 'dashboard.html',
controller: 'MainCtrl',
})
// Login state
.state('login', {
url: '/login',
templateUrl: '_login.html',
controller: 'AuthCtrl',
onEnter: ['$state', 'Auth', function($state, Auth) {
Auth.currentUser().then(function (){
$state.go('dashboard');
})
}]
})
// Register state
.state('register', {
url: '/register',
templateUrl: '_register.html',
controller: 'AuthCtrl',
onEnter: ['$state', 'Auth', function($state, Auth) {
Auth.currentUser().then(function (){
$state.go('dashboard');
})
}]
})
$urlRouterProvider.otherwise('dashboard');
Nav controller
// NAV controller
// ------------------------------
.controller('NavCtrl', ['$scope', 'Auth',
// Main scope (used in views)
function($scope, Auth) {
$scope.signedIn = Auth.isAuthenticated;
$scope.logout = Auth.logout;
Auth.currentUser().then(function (user){
$scope.user = user;
});
$scope.$on('devise:new-registration', function (e, user){
$scope.user = user;
});
$scope.$on('devise:login', function (e, user){
$scope.user = user;
});
$scope.$on('devise:logout', function (e, user){
$scope.user = {};
});
}
])
Authentification controller
// Authentification controller
// ------------------------------
.controller('AuthCtrl', ['$scope', '$state', 'Auth',
// Main scope (used in views)
function($scope, $state, Auth) {
$scope.login = function() {
Auth.login($scope.user).then(function(){
$state.go('home');
});
};
$scope.register = function() {
Auth.register($scope.user).then(function(){
$state.go('home');
});
};
}
]);
How can I determine a non-authenticated user on the dashboard state, and redirect them to login?
You have have a few options to go down for this sort of thing:
1) In MainCtrl add an init() function which checks if(!Auth.isAuthenticated) $state.go('login'). Downside is if they lose auth after the controller is instantiated this does not redirect them.
2) I presume you're making some kind of back-end calls which require Authentication. If so, if they're not authed you can catch the 401 response via an http interceptor and redirect them to login there. This is the method I always use.
3) Attach a $watch to Auth.isAuthenticated do the redirect from there
Related
I need to restrict the user from redirect and need to login only with authentication.
I tried but I can redirect to login page using back button and again come to same page using forward button. Even I can go to the required page using URL without login.
My code :
config.$inject = ['$routeProvider', '$locationProvider'];
function config($routeProvider, $locationProvider ) {
$routeProvider
.when('/login', {
controller: 'LoginController',
templateUrl: 'view/login.view.html',
controllerAs: 'vm'
})
.when('/profileData', {
controller: 'profileDataController',
templateUrl: 'view/profiledata.view.html',
controllerAs :'vm'
})
.when('/questionBank', {
controller: 'questionbankController',
templateUrl: 'view/questionbank.view.html',
controllerAs: 'vm'
})
.when('/dashboard', {
// controller: 'PersonalInfoController',
templateUrl: 'view/dashboard.view.html',
controllerAs:'vm'
})
.otherwise({ redirectTo: '/login' });
}
run.$inject = ['$rootScope', '$location', '$cookieStore', '$http'];
function run($rootScope, $location, $cookieStore, $http) {
// keep user logged in after page refresh
$rootScope.globals = $cookieStore.get('globals') || {};
if ($rootScope.globals.currentUser) {
$http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.globals.currentUser.authdata; // jshint ignore:line
}
$rootScope.$on('$locationChangeStart', function (event, next, current) {
//redirect to login page if not logged in and trying to access a restricted page
var restrictedPage = $.inArray($location.path(), ['/dashboard','/questionBank', '/profileData']) === -1;
/* var a = $location.$$absUrl.split('#')[1];
var patt = new RegExp(a);
var res = patt.test(restrictedPage); */
var loggedIn = $rootScope.globals.currentUser;
if (restrictedPage && !loggedIn) {
$location.path('/login');
}
});
}
use this :based on response from server
.when('/login', {
controller: 'LoginController',
templateUrl: 'view/login.view.html',
resolve:{
logincheck: checklogedin
})
/ resolve function for user....
var checklogedin = function($q ,$http,$location)
{
var deferred =$q.defer();
$http.get('/loggedin').success(function(user){
if (user.staus==true)
{
//goo
deferred.resolve();
}
else
{
deferred.reject();
$location.url('/login');
}
});
return deferred.promise
};
Based on the code that you have provided, I can't tell 100% what is going on in your code. But... you could always try to use the resolve property on each route that you don't want to allow access without authentication. Here is what that would look like for questionBank:
.when('/questionBank', {
controller: 'questionbankController',
templateUrl: 'view/questionbank.view.html',
controllerAs: 'vm',
resolve: {
auth: function(AuthService, $q){
if(AuthService.isAuthenticated()) return $q.resolve();
return $q.reject();
}
}
})
Each property of the resolve object should return a promise, and if that resolves... the route change works. If it rejects... the route change is not allowed. If the promise never resolves, you are screwed, so make sure it resolves or it will never do the route.
This isn't the only way to try what you are saying. It is A way of trying it.
You can also add event listener on your $scope and prevent moving in case of unauthenticated user.
$scope.$on('$locationChangeStart', function (event, next, current) {
if (!is_logged_in) {
event.preventDefault();
}
});
In my code I have two main controllers LoginCtrl and AppCtrl, and all other controllers are nested within AppCtrl. Then in AppCtrl I have this code, which will check for logged user.
if (localStorageService.get('authToken') === null) {
$state.go('login', {locale: CONFIG.defaultLang});
} else if (!userService.isLoggedIn()) {
tokenStorage.setAuthToken(localStorageService.get('authToken'));
userService.setIdentity(JSON.parse(localStorageService.get('user')));
}
As you can see I store auth token from server in local storage. When page loades this code will be executed and if you are not logged in you will be redirected. And because all other application controllers are nested within AppCtrl this code will be executed every time.
For more info about nested controllers try for example this article - https://rclayton.silvrback.com/parent-child-controller-communication
What I am trying to do is verify user authentication and respond accordingly. Either populate the $rootScope with session data or redirect to the login page. I want the session data to be accessible to other controllers and views.
The Session response from the server is a JSON object:
{"user_id":"17","name":"Administrator","user_email":"admin#example.com"}
My app.js looks like this:
var app = angular.module('app', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider, $locationProvider){
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'app/views/login.html'
})
.state('logout', {
url:'/logout',
templateUrl: 'app/views/login.html'
})
.state('dashboard', {
url:'/dashboard',
templateUrl:'app/views/dashboard.html',
role: 0
});
})
.run(function($rootScope, $location, Data){
$rootScope.$on('$StateChangeStart', function (event, toState, fromState) {
$rootScope.authenticated = false;
Data.get('session').then(function (results) { // Sends request to server the above mention JSON object is returned
if (results.user_id) {
$rootScope.authenticated = true;
$rootScope.user_id = results.user_id;
$rootScope.name = results.name;
$rootScope.user_email = results.user_email;
} else {
$location.path("/login");
}
});
});
Then in my controller:
app.controller('AuthCtrl', ['$scope','$rootScope', '$location', '$http', 'Data', function ($scope, $rootScope, $location, $http, Data) {
$scope.login = {};
$scope.doLogin = function (user) {
Data.post('login', { // Sends login information to server
user: user
}).then(function (results) {
if (results.status == "success") {
$location.path('dashboard');
}
});
};
$scope.logout = function () {
Data.get('logout').then(function (results) { // sends logout request to server
$location.path('login');
});
}
}]);
The login function and response works as it should. The logout function works and destroys the session. The API on the server responds the way it should, but the session data is not stored or accessible in the $rootScope and I can not use it in my views.
The dashboard view looks like this:
<div ng-controller="AuthCtrl">
<h4>User Authenticated</h4>
User ID: {{user_id}}
<br/>NAME: {{name}}
<br/>E-MAIL: {{user_email}}
<br/>
<a ng-click="logout();">Logout</a>
</div>
I'm not sure if I am using $rootScope.$on('$StateChangeStart', function(...) correctly. Any suggestions?
It should be $stateChangeStart. Note the first letter is small and not caps.
I'm trying to check permissions on routes in an AngularJS application. Routes are handled by angular-ui-router.
My routes and permissions-per-route are defined as this:
angular.module('my_app')
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('root', {
abstract: true,
templateUrl: 'content.html',
resolve: {
user_info: function (UserService) {
return UserService.user_info();
}
}
})
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
data: {
roles: ['some_right', 'some_other_right]
}
});
}
I then check for permissions as this:
[...].run(function ($rootScope, $state, UserService) {
$rootScope.$on('$stateChangeStart',
function (event, toState) {
if (!!toState.data) {
var required_roles = toState.data.roles;
if (!UserService.has_permission_in(required_roles)) {
event.preventDefault();
$state.go('root.access_forbidden');
}
}
});
});
All good for now. My issue is resolving user permissions. There are provided by a backend and fetched using $http.get(...). Since the $http service returns a promise, it don't always have the result when the $stateChangeStart is event triggered.
It's "good" when the application is "warm", but if I land on a protected page, it fails (the data are not ready). I have no way to "wait" for the promise.
What would be a "good" way of doing access control on routes?
In your protected state's definition:
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: function($http, $state){ //Note: you can inject your own services/factories too
var requiredPermissions = ['some_right', 'some_other_right'];
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
})
Resolve waits for promises to settle before moving to a new state.
Better:
function checkPermissions(requiredPermissions){
return function ($http, $state){ //Note: you can inject your own services/factories too
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
//In state definition
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: checkPermissions(['first','second'])
}
})
I'm having a issue in my Angular app with regards to the resolve section of the ui-router. I'm trying to present a login modal the first time a user hits the website.
In my app.config.js, how do I inject my 'loginService':
angular
.module('rage')
.config(config);
function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/dashboard");
$stateProvider
.state('main', {
url: "/dashboard",
templateUrl: "app/views/dashboard.html",
controller: 'MainCtrl',
data: { pageTitle: 'RAGE', requireLogin: true },
resolve: {
// *** WHERE DO I INJECT 'loginService' ? ***
authUser: function () {
return loginService.loginModal().then(function (user) {
$rootScope.userID = user.userId;
userService.openUserSession(razorEnvJson).then(function (data) {
// assign some scope vars here..
});
})
}
}
})
.state('login', {
url: "/login",
templateUrl: "app/views/login-view.html",
controller: 'LoginCtrl'
})
}
loginService code:
(function () {
'use strict';
angular.module('rage').service('loginService',
['$rootScope', '$modal', 'datacontext', 'userService', login]);
function login($rootScope, $modal, datacontext, userService) {
var modalInstance = null
this.loginModal = function(){
modalInstance = $modal.open({
animation: true,
templateUrl: 'app/components/login/login.html',
controller: 'LoginCtrl as login',
});
return modalInstance.result.then(function (user) {
return user;
});
};
}
})();
LoginCtrl controller code:
(function () {
'use strict';
angular.module('rage').controller('LoginCtrl',
['$rootScope', '$scope', '$modalInstance', '$q', 'datacontext', 'userService', authenticate]);
function authenticate($rootScope, $scope, $modalInstance, $q, datacontext, userService) {
var login = this;
// OK,CANCEL CLICK EVENTS FROM MODAL !!!
$scope.ok = function () {
// var user = userService.authenticateWebUser(); // **** TBD ****
var user = {userId: login.userId, pswd: login.pswd};
$modalInstance.close(user);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
})();
I've also attempted $rootScope.$on('$stateChangeStart' event inside app.js to transition the state from main to login, but that hangs up on me.
**** MY UPDATED APP.CONFIG.JS CODE, SEPT 18 ****
Here is the proper usage of resolve: using ui-router states.
function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/dashboard");
$stateProvider
.state('main', {
url: "/dashboard",
templateUrl: "app/views/dashboard.html",
controller: 'MainCtrl',
data: { pageTitle: 'RAGE', requireLogin: true },
resolve: {
authUser: ['$rootScope', 'loginService', 'userService', function ($rootScope, loginService, userService) {
return loginService.loginModal().then(function (user) {
$rootScope.userID = user.userId;
initSession(user, $rootScope, loginService, userService);
})
}]
}
})
.state('login', {
url: "/login",
templateUrl: "app/views/login-view.html",
controller: 'LoginCtrl'
})
}
function initSession(user, $rootScope, loginService, userService) {
userService.getInitParams().then(function (envJson) {
// some code omitted here...
userService.openUserSession(envJson).then(function (data) {
var sessionID = data.data[0];
$rootScope.rageSessionVars.sessionID = sessionID;
$rootScope.rageSessionVars.userID = $rootScope.userID; // *** HOW TO SUSPEND ALL CODE UNTIL userID IS ASSIGNED ??? ***
console.log("sessionID = " + sessionID);
$rootScope.rageSessionVars.currDashboardName = "Default";
});
});
}
* MainCtrl controller code *
(function () {
'use strict';
angular.module('rage')
.controller('MainCtrl',
['$rootScope', '$scope', '$interval', '$window', '$state', '$location', 'widgetDefinitions',
'defaultWidgets', 'gadgetInitService', main]);
function main($rootScope, $scope, $interval, $window, $state, $location, widgetDefinitions, defaultWidgets, gadgetInitService, authUser) {
var main = this;
**** authUser IS NEVER DEFINED !!!
if ($scope.userID == undefined) { /// *** NOTHING WORKS HERE !!! ***
//$state.go('login');
//$location.url('index.html#/?login');
//return ;
}
if ($scope.userID == undefined) {
main.userName = "risk user";
}
else {
$scope.main.userName = $scope.userID;
}
}
})();
Edit:
I see your getting confused on the use of 'Resolve'.
It should be used when you want some data passed into the controller when it's being run. Not to run a function before initiating a controller.
This isn't what you really want in this situation.
It kinda depends on your authentication method, cookies/tokens etc.
Here is a similar method that I would follow.
Have a Login service which handles the following
-- Login/Logout of the user
-- Checks if the user is authenticated
In your Controller call your service to check if the user is logged in.
-- If the user is not logged in, then prompt login screen
-- If the user is logged in, then let the controller continue execution
How I handle it, is whenever the user makes an UnAuthorised request where the server returns a 401 response I call my LoginService to prompt the login screen again.
I use an authInterceptor in Angular to catch any 401 response from the server.
Here is a nice guide on it: http://onehungrymind.com/winning-http-interceptors-angularjs/
This allows you to write your Unauthorized handler in one place.
Hope that makes it a bit clearer.
Ok so I created a project with Yeoman (angular-fullstack -> ui-route) and was able to change the main view directly to the login view. Now when you login successfully it should redirect to a desired route of my choosing.
My folder structure looks like this -> http://gyazo.com/a5e60dc2bc873c769f5926353829881c
I am trying to redirect to the month.html view. Here is the code for this (login.controller.js)
'use strict';
angular.module('zazzleToolPlannerApp')
.controller('LoginCtrl', function ($scope, Auth, $location) {
$scope.user = {};
$scope.errors = {};
$scope.login = function(form) {
$scope.submitted = true;
if(form.$valid) {
Auth.login({
email: $scope.user.email,
password: $scope.user.password
})
.then( function() {
// Logged in, redirect to home
$location.path('/month');
})
.catch( function(err) {
$scope.errors.other = err.message;
});
}
};
});
Code of calendar.js:
'use strict';
angular.module('zazzleToolPlannerApp')
.config(function ($stateProvider) {
$stateProvider
.state('day', {
url: '/day',
templateUrl: 'app/calendar/day/day.html',
controller: 'CalendarCtrl',
authenticate: true
})
.state('week', {
url: '/week',
templateUrl: 'app/calendar/week/week.html',
controller: 'CalendarCtrl',
authenticate: true
})
.state('month', {
url: '/month',
templateUrl: 'app/calendar/month/month.html',
controller: 'CalendarCtrl',
authenticate: true
});
});
But when I login nothing happens although my console shows the login was successful -> http://gyazo.com/ddd066e0f399027d3cffa266f647a8e0