AngularJS- Login e autenticação em cada rota e controlador

Eu tenho um aplicativo AngularJS criado usando yeoman, grunhido e bower.

Eu tenho uma página de login que tem um controlador que verifica a autenticação. Se as credenciais estiverem corretas, eu redireciono para a página inicial.

app.js

'use strict'; //Define Routing for app angular.module('myApp', []).config(['$routeProvider', '$locationProvider', function($routeProvider,$locationProvider) { $routeProvider .when('/login', { templateUrl: 'login.html', controller: 'LoginController' }) .when('/register', { templateUrl: 'register.html', controller: 'RegisterController' }) .when('/forgotPassword', { templateUrl: 'forgotpassword.html', controller: 'forgotController' }) .when('/home', { templateUrl: 'views/home.html', controller: 'homeController' }) .otherwise({ redirectTo: '/login' }); // $locationProvider.html5Mode(true); //Remove the '#' from URL. }]); angular.module('myApp').factory("page", function($rootScope){ var page={}; var user={}; page.setPage=function(title,bodyClass){ $rootScope.pageTitle = title; $rootScope.bodylayout=bodyClass; }; page.setUser=function(user){ $rootScope.user=user; } return page; }); 

LoginControler.js

 'use strict'; angular.module('myApp').controller('LoginController', function($scope, $location, $window,page) { page.setPage("Login","login-layout"); $scope.user = {}; $scope.loginUser=function() { var username=$scope.user.name; var password=$scope.user.password; if(username=="admin" && password=="admin123") { page.setUser($scope.user); $location.path( "/home" ); } else { $scope.message="Error"; $scope.messagecolor="alert alert-danger"; } } }); 

Na home page eu tenho

  Logout 

No loginController , eu verifico as informações de login e, se tiver êxito, eu configuro o object de usuário na fábrica de serviços. Não sei se isso está correto ou não.

O que eu preciso é, quando o usuário está logado, ele define algum valor no object de usuário para que todas as outras páginas possam obter esse valor.

Sempre que ocorrer alguma alteração na rota, o controlador deve verificar se o usuário está logado ou não. Caso contrário, ele deve redirect para a página de login. Além disso, se o usuário já estiver conectado e voltar à página, ele deverá ir para a página inicial. O controlador também deve verificar as credenciais em todas as rotas.

Eu ouvi falar de ng-cookies, mas não sei como usá-los.

Muitos dos exemplos que vi não foram muito claros e usam algum tipo de function de access ou algo assim. Eu não quero isso. Eu só quero um filtro de login. Alguém pode me dar algumas idéias?

Minha solução divide-se em 3 partes: o estado do usuário é armazenado em um serviço, no método de execução que você assiste quando a rota muda e você verifica se o usuário tem permissão para acessar a página solicitada, no seu controlador principal você observa se o estado da mudança do usuário.

 app.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) { $rootScope.$on('$routeChangeStart', function (event) { if (!Auth.isLoggedIn()) { console.log('DENY'); event.preventDefault(); $location.path('/login'); } else { console.log('ALLOW'); $location.path('/home'); } }); }]); 

Você deve criar um serviço (eu vou chamar de Auth ), que irá lidar com o object do usuário e ter um método para saber se o usuário está logado ou não.

serviço :

  .factory('Auth', function(){ var user; return{ setUser : function(aUser){ user = aUser; }, isLoggedIn : function(){ return(user)? user : false; } } }) 

A partir do seu app.run , você deve ouvir o evento $routeChangeStart . Quando a rota vai mudar, ele irá verificar se o usuário está logado (o método isLoggedIn deve lidar com isso). Ele não carregará a rota solicitada se o usuário não estiver logado e redirectá o usuário para a página correta (no seu caso, login).

O loginController deve ser usado em sua página de login para manipular o login. Ele deve apenas interratar com o serviço Auth e definir o usuário como logado ou não.

loginController :

 .controller('loginCtrl', [ '$scope', 'Auth', function ($scope, Auth) { //submit $scope.login = function () { // Ask to the server, do your job and THEN set the user Auth.setUser(user); //Update the state of the user in the app }; }]) 

Do seu controlador principal, você pode ouvir se o estado do usuário mudar e reagir com um redirecionamento.

 .controller('mainCtrl', ['$scope', 'Auth', '$location', function ($scope, Auth, $location) { $scope.$watch(Auth.isLoggedIn, function (value, oldValue) { if(!value && oldValue) { console.log("Disconnect"); $location.path('/login'); } if(value) { console.log("Connect"); //Do something when the user is connected } }, true); 

Aqui está outra solução possível, usando o atributo resolve do $stateProvider ou o $routeProvider . Exemplo com $stateProvider :

 .config(["$stateProvider", function ($stateProvider) { $stateProvider .state("forbidden", { /* ... */ }) .state("signIn", { /* ... */ resolve: { access: ["Access", function (Access) { return Access.isAnonymous(); }], } }) .state("home", { /* ... */ resolve: { access: ["Access", function (Access) { return Access.isAuthenticated(); }], } }) .state("admin", { /* ... */ resolve: { access: ["Access", function (Access) { return Access.hasRole("ROLE_ADMIN"); }], } }); }]) 

Access resolve ou rejeita uma promise, dependendo dos direitos do usuário atual:

 .factory("Access", ["$q", "UserProfile", function ($q, UserProfile) { var Access = { OK: 200, // "we don't know who you are, so we can't say if you're authorized to access // this resource or not yet, please sign in first" UNAUTHORIZED: 401, // "we know who you are, and your profile does not allow you to access this resource" FORBIDDEN: 403, hasRole: function (role) { return UserProfile.then(function (userProfile) { if (userProfile.$hasRole(role)) { return Access.OK; } else if (userProfile.$isAnonymous()) { return $q.reject(Access.UNAUTHORIZED); } else { return $q.reject(Access.FORBIDDEN); } }); }, hasAnyRole: function (roles) { return UserProfile.then(function (userProfile) { if (userProfile.$hasAnyRole(roles)) { return Access.OK; } else if (userProfile.$isAnonymous()) { return $q.reject(Access.UNAUTHORIZED); } else { return $q.reject(Access.FORBIDDEN); } }); }, isAnonymous: function () { return UserProfile.then(function (userProfile) { if (userProfile.$isAnonymous()) { return Access.OK; } else { return $q.reject(Access.FORBIDDEN); } }); }, isAuthenticated: function () { return UserProfile.then(function (userProfile) { if (userProfile.$isAuthenticated()) { return Access.OK; } else { return $q.reject(Access.UNAUTHORIZED); } }); } }; return Access; }]) 

UserProfile copia as propriedades do usuário atual e implementa a lógica dos methods $hasRole , $hasAnyRole , $isAnonymous e $isAuthenticated (mais um método $refresh , explicado posteriormente):

 .factory("UserProfile", ["Auth", function (Auth) { var userProfile = {}; var clearUserProfile = function () { for (var prop in userProfile) { if (userProfile.hasOwnProperty(prop)) { delete userProfile[prop]; } } }; var fetchUserProfile = function () { return Auth.getProfile().then(function (response) { clearUserProfile(); return angular.extend(userProfile, response.data, { $refresh: fetchUserProfile, $hasRole: function (role) { return userProfile.roles.indexOf(role) >= 0; }, $hasAnyRole: function (roles) { return !!userProfile.roles.filter(function (role) { return roles.indexOf(role) >= 0; }).length; }, $isAnonymous: function () { return userProfile.anonymous; }, $isAuthenticated: function () { return !userProfile.anonymous; } }); }); }; return fetchUserProfile(); }]) 

Auth está encarregada de solicitar o servidor, para conhecer o perfil do usuário (vinculado a um token de access anexado à solicitação, por exemplo):

 .service("Auth", ["$http", function ($http) { this.getProfile = function () { return $http.get("api/auth"); }; }]) 

Espera-se que o servidor retorne um object JSON ao solicitar o GET api/auth :

 { "name": "John Doe", // plus any other user information "roles": ["ROLE_ADMIN", "ROLE_USER"], // or any other role (or no role at all, ie an empty array) "anonymous": false // or true } 

Finalmente, quando o Access rejeitar uma promise, se estiver usando o ui.router , o evento $stateChangeError será acionado:

 .run(["$rootScope", "Access", "$state", "$log", function ($rootScope, Access, $state, $log) { $rootScope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) { switch (error) { case Access.UNAUTHORIZED: $state.go("signIn"); break; case Access.FORBIDDEN: $state.go("forbidden"); break; default: $log.warn("$stateChangeError event catched"); break; } }); }]) 

Se estiver usando ngRoute , o evento $routeChangeError será acionado:

 .run(["$rootScope", "Access", "$location", "$log", function ($rootScope, Access, $location, $log) { $rootScope.$on("$routeChangeError", function (event, current, previous, rejection) { switch (rejection) { case Access.UNAUTHORIZED: $location.path("/signin"); break; case Access.FORBIDDEN: $location.path("/forbidden"); break; default: $log.warn("$stateChangeError event catched"); break; } }); }]) 

O perfil do usuário também pode ser acessado nos controladores:

 .state("home", { /* ... */ controller: "HomeController", resolve: { userProfile: "UserProfile" } }) 

UserProfile seguida, contém as propriedades retornadas pelo servidor ao solicitar o GET api/auth :

 .controller("HomeController", ["$scope", "userProfile", function ($scope, userProfile) { $scope.title = "Hello " + userProfile.name; // "Hello John Doe" in the example }]) 

UserProfile precisa ser atualizado quando um usuário entra ou sai, para que o Access possa manipular as rotas com o novo perfil de usuário. Você pode recarregar a página inteira ou chamar UserProfile.$refresh() . Exemplo ao entrar:

 .service("Auth", ["$http", function ($http) { /* ... */ this.signIn = function (credentials) { return $http.post("api/auth", credentials).then(function (response) { // authentication succeeded, store the response access token somewhere (if any) }); }; }]) 
 .state("signIn", { /* ... */ controller: "SignInController", resolve: { /* ... */ userProfile: "UserProfile" } }) 
 .controller("SignInController", ["$scope", "$state", "Auth", "userProfile", function ($scope, $state, Auth, userProfile) { $scope.signIn = function () { Auth.signIn($scope.credentials).then(function () { // user successfully authenticated, refresh UserProfile return userProfile.$refresh(); }).then(function () { // UserProfile is refreshed, redirect user somewhere $state.go("home"); }); }; }]) 

A maneira mais simples de definir o comportamento personalizado para rotas individuais seria bastante fácil:

1) routes.js : crie uma nova propriedade (como requireAuth ) para qualquer rota desejada

 angular.module('yourApp').config(['$routeProvider', function($routeProvider) { $routeProvider .when('/home', { templateUrl: 'templates/home.html', requireAuth: true }) .when('/login', { templateUrl: 'templates/login.html', }) .otherwise({ redirectTo: '/home' }); }]) 

2) Em um controlador de nível superior que não esteja vinculado a um elemento dentro do ng-view (para evitar conflito com o $routeProvider angular), verifique se o newUrl tem a propriedade requireAuth e aja de acordo

 angular.module('YourApp').controller('YourController', ['$scope', 'session', '$location', function($scope, session, $location) { $scope.$on('$routeChangeStart', function(angularEvent, newUrl) { if (newUrl.requireAuth && !session.user) { // User isn't authenticated $location.path("/login"); } }); } ]); 

Eu escrevi um post há alguns meses sobre como configurar o registro do usuário e funcionalidade de login com Angular, você pode verificá-lo em http://jasonwatmore.com/post/2015/03/10/AngularJS-User-Registration-and -Login-Example.aspx

Eu verifico se o usuário está logado no evento $locationChangeStart , aqui está meu app.js principal mostrando isso:

 (function () {    'use strict';    angular        .module('app', ['ngRoute', 'ngCookies'])        .config(config)        .run(run);    config.$inject = ['$routeProvider', '$locationProvider'];    function config($routeProvider, $locationProvider) {        $routeProvider            .when('/', {                controller: 'HomeController',                templateUrl: 'home/home.view.html',                controllerAs: 'vm'            })            .when('/login', {                controller: 'LoginController',                templateUrl: 'login/login.view.html',                controllerAs: 'vm'            })            .when('/register', {                controller: 'RegisterController',                templateUrl: 'register/register.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(), ['/login', '/register']) === -1;            var loggedIn = $rootScope.globals.currentUser;            if (restrictedPage && !loggedIn) {                $location.path('/login');            }        });    } })(); 

Eu sinto que este caminho é mais fácil, mas talvez seja apenas uma preferência pessoal.

Quando você especificar sua rota de login (e quaisquer outras rotas anônimas; ex: / register, / logout, / refreshToken, etc.), adicione:

 allowAnonymous: true 

Então, algo assim:

 $stateProvider.state('login', { url: '/login', allowAnonymous: true, //if you move this, don't forget to update //variable path in the force-page check. views: { root: { templateUrl: "app/auth/login/login.html", controller: 'LoginCtrl' } } //Any other config } 

Você não precisa especificar “allowAnonymous: false”, se não estiver presente, é falso, na verificação. Em um aplicativo em que a maioria dos URLs é autenticada, isso é menos trabalhoso. E mais seguro; Se você esquecer de adicioná-lo a um novo URL, o pior que pode acontecer é que um URL anônimo seja protegido. Se você fizer isso de outra maneira, especificando “requireAuthentication: true”, e se esquecer de adicioná-lo a uma URL, estará vazando uma página confidencial para o público.

Em seguida, execute isso onde quer que você se sinta melhor em seu design de código.

 //I put it right after the main app module config. Ie This thing: angular.module('app', [ /* your dependencies*/ ]) .config(function (/* you injections */) { /* your config */ }) //Make sure there's no ';' ending the previous line. We're chaining. (or just use a variable) // //Then force the logon page .run(function ($rootScope, $state, $location, User /* My custom session obj */) { $rootScope.$on('$stateChangeStart', function(event, newState) { if (!User.authenticated && newState.allowAnonymous != true) { //Don't use: $state.go('login'); //Apparently you can't set the $state while in a $state event. //It doesn't work properly. So we use the other way. $location.path("/login"); } }); }); 

app.js

 'use strict'; // Declare app level module which depends on filters, and services var app= angular.module('myApp', ['ngRoute','angularUtils.directives.dirPagination','ngLoadingSpinner']); app.config(['$routeProvider', function($routeProvider) { $routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'loginCtrl'}); $routeProvider.when('/home', {templateUrl: 'partials/home.html', controller: 'homeCtrl'}); $routeProvider.when('/salesnew', {templateUrl: 'partials/salesnew.html', controller: 'salesnewCtrl'}); $routeProvider.when('/salesview', {templateUrl: 'partials/salesview.html', controller: 'salesviewCtrl'}); $routeProvider.when('/users', {templateUrl: 'partials/users.html', controller: 'usersCtrl'}); $routeProvider.when('/forgot', {templateUrl: 'partials/forgot.html', controller: 'forgotCtrl'}); $routeProvider.otherwise({redirectTo: '/login'}); }]); app.run(function($rootScope, $location, loginService){ var routespermission=['/home']; //route that require login var salesnew=['/salesnew']; var salesview=['/salesview']; var users=['/users']; $rootScope.$on('$routeChangeStart', function(){ if( routespermission.indexOf($location.path()) !=-1 || salesview.indexOf($location.path()) !=-1 || salesnew.indexOf($location.path()) !=-1 || users.indexOf($location.path()) !=-1) { var connected=loginService.islogged(); connected.then(function(msg){ if(!msg.data) { $location.path('/login'); } }); } }); }); 

loginServices.js

 'use strict'; app.factory('loginService',function($http, $location, sessionService){ return{ login:function(data,scope){ var $promise=$http.post('data/user.php',data); //send data to user.php $promise.then(function(msg){ var uid=msg.data; if(uid){ scope.msgtxt='Correct information'; sessionService.set('uid',uid); $location.path('/home'); } else { scope.msgtxt='incorrect information'; $location.path('/login'); } }); }, logout:function(){ sessionService.destroy('uid'); $location.path('/login'); }, islogged:function(){ var $checkSessionServer=$http.post('data/check_session.php'); return $checkSessionServer; /* if(sessionService.get('user')) return true; else return false; */ } } }); 

sessionServices.js

 'use strict'; app.factory('sessionService', ['$http', function($http){ return{ set:function(key,value){ return sessionStorage.setItem(key,value); }, get:function(key){ return sessionStorage.getItem(key); }, destroy:function(key){ $http.post('data/destroy_session.php'); return sessionStorage.removeItem(key); } }; }]) 

loginCtrl.js

 'use strict'; app.controller('loginCtrl', ['$scope','loginService', function ($scope,loginService) { $scope.msgtxt=''; $scope.login=function(data){ loginService.login(data,$scope); //call login service }; }]); 

Por exemplo, um aplicativo tem dois usuários chamados ap e auc. Estou passando uma propriedade extra para cada rota e manipulando o roteamento com base nos dados que recebo em $ routeChangeStart.

Tente isto:

 angular.module("app").config(['$routeProvider', function ($routeProvider) { $routeProvider. when('/ap', { templateUrl: 'template1.html', controller: 'template1', isAp: 'ap', }). when('/auc', { templateUrl: 'template2.html', controller: 'template2', isAp: 'common', }). when('/ic', { templateUrl: 'template3.html', controller: 'template3', isAp: 'auc', }). when('/mup', { templateUrl: 'template4.html', controller: 'template4', isAp: 'ap', }). when('/mnu', { templateUrl: 'template5.html', controller: 'template5', isAp: 'common', }). otherwise({ redirectTo: '/ap', }); }]); 

app.js:

 .run(['$rootScope', '$location', function ($rootScope, $location) { $rootScope.$on("$routeChangeStart", function (event, next, current) { if (next.$$route.isAp != 'common') { if ($rootScope.userTypeGlobal == 1) { if (next.$$route.isAp != 'ap') { $location.path("/ap"); } } else { if (next.$$route.isAp != 'auc') { $location.path("/auc"); } } } }); }]); 

Todos sugeriram uma grande solução porque você está se preocupando com a session do lado do cliente. Quero dizer, quando state / url mudar, suponho que você esteja fazendo uma chamada ajax para carregar os dados para tempelate.

 Note :- To Save user's data you may use `resolve` feature of `ui-router`. Check cookie if it exist load template , if even cookies doesn't exist than there is no chance of logged in , simply redirect to login template/page. 

Agora os dados do ajax são retornados pelo servidor usando qualquer API. Agora o ponto entrou em jogo, retornar tipos de retorno padrão usando o servidor de acordo com o status de usuário logado. Verifique esses códigos de retorno e processe sua solicitação no controlador. Nota: – Para o controlador que não requer uma chamada ajax nativamente, você pode chamar um pedido em branco para o servidor como este server.location/api/checkSession.php e este é checkSession.php

  

No lado do cliente dentro do controlador ou através de qualquer serviço, conforme mostrado em outras respostas

  $http.get(dataUrl) .success(function (data){ $scope.templateData = data; }) .error(function (error, status){ $scope.data.error = { message: error, status: status}; console.log($scope.data.error.status); if(status == CODE_CONFIGURED_ON_SERVER_SIDE_FOR_NON_LOGGED_IN){ //redirect to login }); 

Nota: - Vou atualizar mais amanhã ou no futuro

Você pode usar resolve :

 angular.module('app',[]) .config(function($routeProvider) { $routeProvider .when('/', { templateUrl : 'app/views/login.html', controller : 'YourController', controllerAs : 'Your', resolve: { factory : checkLoginRedirect } }) } 

E a function da resolução:

 function checkLoginRedirect($location){ var user = firebase.auth().currentUser; if (user) { // User is signed in. if ($location.path() == "/"){ $location.path('dash'); } return true; }else{ // No user is signed in. $location.path('/'); return false; } } 

O Firebase também tem um método que ajuda você a instalar um observador, eu aconselho instalá-lo dentro de um .run :

 .run(function(){ firebase.auth().onAuthStateChanged(function(user) { if (user) { console.log('User is signed in.'); } else { console.log('No user is signed in.'); } }); } 

Você deve verificar a autenticação do usuário em dois sites principais.

  • Quando os usuários mudam de estado, verifique-os usando '$routeChangeStart' retorno de chamada '$routeChangeStart'
  • Quando uma solicitação $ http é enviada de um ângulo, usando um interceptor.