Authentification de connexion UI-Routeur angulaire

Je suis nouveau chez AngularJS, et je suis un peu confus de la façon dont je peux utiliser angular-"ui-router" dans le scénario suivant:

Je crée une application Web qui comprend deux sections. La première section est la page d'accueil avec ses vues de connexion et d'inscription, et la deuxième section est le tableau de bord (après une connexion réussie).

J'ai créé un index.html pour la section de la maison avec sa configuration angulaire et la configuration de ui-router pour gérer les vues de /login et d' /signup , et il existe un autre fichier dashboard.html pour la section du tableau de bord avec son application et ui-router config à Gérer plusieurs sous-vues.

Maintenant, j'ai terminé la section du tableau de bord et je ne sais pas comment combiner les deux sections avec leurs différentes applications angulaires. Comment puis-je dire à l'application domestique de rediriger vers l'application du tableau de bord?

Je suis en train de faire une démonstration plus agréable ainsi que de nettoyer certains de ces services dans un module utilisable, mais voici ce que j'ai trouvé. Il s'agit d'un processus complexe pour contourner certaines réserves, alors attendez-vous. Vous devrez décomposer cela en plusieurs morceaux.

Jetez un coup d'oeil à cette plunk .

D'abord, vous avez besoin d'un service pour stocker l'identité de l'utilisateur. J'appelle ce principal . On peut vérifier si l'utilisateur est connecté et, sur demande, il peut résoudre un objet qui représente les informations essentielles sur l'identité de l'utilisateur. Cela peut être tout ce dont vous avez besoin, mais l'essentiel serait un nom d'affichage, un nom d'utilisateur, éventuellement un courrier électronique, et les rôles auxquels un utilisateur appartient (si cela s'applique à votre application). Principal a également des méthodes pour effectuer des contrôles de rôle.

 .factory('principal', ['$q', '$http', '$timeout', function($q, $http, $timeout) { var _identity = undefined, _authenticated = false; return { isIdentityResolved: function() { return angular.isDefined(_identity); }, isAuthenticated: function() { return _authenticated; }, isInRole: function(role) { if (!_authenticated || !_identity.roles) return false; return _identity.roles.indexOf(role) != -1; }, isInAnyRole: function(roles) { if (!_authenticated || !_identity.roles) return false; for (var i = 0; i < roles.length; i++) { if (this.isInRole(roles[i])) return true; } return false; }, authenticate: function(identity) { _identity = identity; _authenticated = identity != null; }, identity: function(force) { var deferred = $q.defer(); if (force === true) _identity = undefined; // check and see if we have retrieved the // identity data from the server. if we have, // reuse it by immediately resolving if (angular.isDefined(_identity)) { deferred.resolve(_identity); return deferred.promise; } // otherwise, retrieve the identity data from the // server, update the identity object, and then // resolve. // $http.get('/svc/account/identity', // { ignoreErrors: true }) // .success(function(data) { // _identity = data; // _authenticated = true; // deferred.resolve(_identity); // }) // .error(function () { // _identity = null; // _authenticated = false; // deferred.resolve(_identity); // }); // for the sake of the demo, fake the lookup // by using a timeout to create a valid // fake identity. in reality, you'll want // something more like the $http request // commented out above. in this example, we fake // looking up to find the user is // not logged in var self = this; $timeout(function() { self.authenticate(null); deferred.resolve(_identity); }, 1000); return deferred.promise; } }; } ]) 

Deuxièmement, vous avez besoin d'un service qui vérifie l'état auquel l'utilisateur veut aller, s'assure qu'ils sont connectés (si nécessaire, pas nécessaire pour la signature, la réinitialisation du mot de passe, etc.), puis effectue une vérification des rôles (si votre application A besoin de cela). S'ils ne sont pas authentifiés, envoyez-les à la page de connexion. S'ils sont authentifiés, mais échouez à une vérification des rôles, envoyez-les à une page refusée par l'accès. J'appelle cette authorization service.

 .factory('authorization', ['$rootScope', '$state', 'principal', function($rootScope, $state, principal) { return { authorize: function() { return principal.identity() .then(function() { var isAuthenticated = principal.isAuthenticated(); if ($rootScope.toState.data.roles && $rootScope.toState .data.roles.length > 0 && !principal.isInAnyRole( $rootScope.toState.data.roles)) { if (isAuthenticated) { // user is signed in but not // authorized for desired state $state.go('accessdenied'); } else { // user is not authenticated. Stow // the state they wanted before you // send them to the sign-in state, so // you can return them when you're done $rootScope.returnToState = $rootScope.toState; $rootScope.returnToStateParams = $rootScope.toStateParams; // now, send them to the signin state // so they can log in $state.go('signin'); } } }); } }; } ]) 

Maintenant, tout ce que vous devez faire, c'est écouter le ui-router $stateChangeStart ui-router . Cela vous donne l'occasion d'examiner l'état actuel, l'état auquel ils souhaitent accéder et d'insérer votre contrôle d'autorisation. Si cela échoue, vous pouvez annuler la transition de l'itinéraire ou passer à un autre itinéraire.

 .run(['$rootScope', '$state', '$stateParams', 'authorization', 'principal', function($rootScope, $state, $stateParams, authorization, principal) { $rootScope.$on('$stateChangeStart', function(event, toState, toStateParams) { // track the state the user wants to go to; // authorization service needs this $rootScope.toState = toState; $rootScope.toStateParams = toStateParams; // if the principal is resolved, do an // authorization check immediately. otherwise, // it'll be done when the state it resolved. if (principal.isIdentityResolved()) authorization.authorize(); }); } ]); 

La partie délicate sur le suivi de l'identité d'un utilisateur est de le rechercher si vous avez déjà authentifié (par exemple, vous visitez la page après une session précédente et avez enregistré un jeton d'authentification dans un cookie, ou peut-être avez-vous réinitialisé une page? Déposé sur une URL à partir d'un lien). En raison de la façon dont ui-router fonctionne, vous devez résoudre votre identité une fois, avant vos vérifications d'authentification. Vous pouvez le faire en utilisant l'option de resolve dans votre configuration d'état. J'ai un état parent pour le site sur lequel tous les états héritent, ce qui oblige le principal à être résolu avant toute autre chose.

 $stateProvider.state('site', { 'abstract': true, resolve: { authorize: ['authorization', function(authorization) { return authorization.authorize(); } ] }, template: '<div ui-view />' }) 

Il y a un autre problème ici … la resolve n'est appelée qu'une seule fois. Une fois que votre promesse de recherche d'identité est terminée, elle ne fonctionnera pas à nouveau. Nous devons donc faire vos vérifications d'authentification en deux endroits: une fois que votre promesse d'identité resolve en resolve , qui couvre la première fois que votre application est chargée, et une fois dans $stateChangeStart si la résolution a été effectuée, qui couvre tout moment de navigation États.

D'accord, alors, qu'avons-nous fait jusqu'ici?

  1. Nous vérifions si l'application est chargée si l'utilisateur est connecté.
  2. Nous suivons les informations sur l'utilisateur connecté.
  3. Nous les redirigons vers un état de connexion pour les états qui exigent que l'utilisateur soit connecté.
  4. Nous les redirigons vers un état refusé d'accès s'ils n'ont pas l'autorisation d'y accéder.
  5. Nous avons un mécanisme pour rediriger les utilisateurs vers l'état d'origine qu'ils demandent, si nous avions besoin d'eux pour nous connecter.
  6. Nous pouvons signer un utilisateur (doit être connecté de concert avec n'importe quel code client ou serveur qui gère votre ticket d'authentification).
  7. Nous n'avons pas besoin d'envoyer les utilisateurs à la page de connexion chaque fois qu'ils rechargent leur navigateur ou déposés sur un lien.

Où allons-nous à partir d'ici? Eh bien, vous pouvez organiser vos états dans des régions nécessitant une connexion. Vous pouvez exiger des utilisateurs authentifiés / autorisés en ajoutant des data avec des roles à ces états (ou un parent d'entre eux, si vous souhaitez utiliser les héritages). Ici, nous restreignons une ressource aux administrateurs:

 .state('restricted', { parent: 'site', url: '/restricted', data: { roles: ['Admin'] }, views: { 'content@': { templateUrl: 'restricted.html' } } }) 

Maintenant, vous pouvez contrôler l'état par état de ce que les utilisateurs peuvent accéder à une route. Des autres préoccupations? Peut-être ne modifie-t-il qu'une partie d'une vue en fonction de la connexion ou non? Pas de problème. Utilisez le principal.isAuthenticated() ou même principal.isInRole() avec l'une des nombreuses façons dont vous pouvez afficher conditionnellement un modèle ou un élément.

Tout d'abord, injectez le principal dans un contrôleur ou autre, et ajoutez-le à la portée afin que vous puissiez l'utiliser facilement dans votre vue:

 .scope('HomeCtrl', ['$scope', 'principal', function($scope, principal) { $scope.principal = principal; }); 

Afficher ou masquer un élément:

 <div ng-show="principal.isAuthenticated()"> I'm logged in </div> <div ng-hide="principal.isAuthenticated()"> I'm not logged in </div> 

Etc., ainsi de suite, ainsi de suite. Quoi qu'il en soit, dans votre application d'exemple, vous auriez un état pour la page d'accueil qui laisserait tomber les utilisateurs non authentifiés. Ils pourraient avoir des liens vers les états d'inscription ou d'inscription, ou avoir ces formulaires intégrés dans cette page. Tout ce qui vous convient.

Les pages du tableau de bord pourraient tous hériter d'un état nécessitant la connexion des utilisateurs et, par exemple, un membre du rôle User . Toutes les choses d'autorisation que nous avons discutées déboucheraient à partir de là.

Les solutions affichées jusqu'à présent sont inutilement compliquées, à mon avis. Il y a un moyen plus simple. La documentation d' ui-router dit écouter $locationChangeSuccess et utiliser $urlRouter.sync() pour vérifier une transition d'état, l'arrêter ou le reprendre. Mais même cela ne fonctionne pas réellement.

Cependant, voici deux alternatives simples. Choisissez un:

Solution 1: écoute sur $locationChangeSuccess

Vous pouvez écouter $locationChangeSuccess et vous pouvez effectuer une certaine logique, même une logique asynchrone. Sur la base de cette logique, vous pouvez laisser la fonction retourner indéfinie, ce qui entraînera la transition d'état pour continuer normalement, ou vous pouvez faire $state.go('logInPage') , si l'utilisateur doit être authentifié. Voici un exemple:

 angular.module('App', ['ui.router']) // In the run phase of your Angular application .run(function($rootScope, user, $state) { // Listen to '$locationChangeSuccess', not '$stateChangeStart' $rootScope.$on('$locationChangeSuccess', function() { user .logIn() .catch(function() { // log-in promise failed. Redirect to log-in page. $state.go('logInPage') }) }) }) 

Gardez à l'esprit que cela n'empêche pas l'état cible de le charger, mais il redirige vers la page d'ouverture de session si l'utilisateur n'est pas autorisé. C'est bon, car une protection réelle est sur le serveur, de toute façon.

Solution 2: utilisation de la resolve état

Dans cette solution, vous utilisez la fonction de résolution ui-router .

Vous refusez fondamentalement la promesse de resolve si l'utilisateur n'est pas authentifié puis les redirige vers la page de connexion.

Voici comment cela se passe:

 angular.module('App', ['ui.router']) .config( function($stateProvider) { $stateProvider .state('logInPage', { url: '/logInPage', templateUrl: 'sections/logInPage.html', controller: 'logInPageCtrl', }) .state('myProtectedContent', { url: '/myProtectedContent', templateUrl: 'sections/myProtectedContent.html', controller: 'myProtectedContentCtrl', resolve: { authenticate: authenticate } }) .state('alsoProtectedContent', { url: '/alsoProtectedContent', templateUrl: 'sections/alsoProtectedContent.html', controller: 'alsoProtectedContentCtrl', resolve: { authenticate: authenticate } }) function authenticate($q, user, $state, $timeout) { if (user.isAuthenticated()) { // Resolve the promise successfully return $q.when() } else { // The next bit of code is asynchronously tricky. $timeout(function() { // This code runs after the authentication promise has been rejected. // Go to the log-in page $state.go('logInPage') }) // Reject the authentication promise to prevent the state from loading return $q.reject() } } } ) 

Contrairement à la première solution, cette solution empêche l'état cible de charger.

La solution la plus simple est d'utiliser $stateChangeStart et event.preventDefault() pour annuler le changement d'état lorsque l'utilisateur n'est pas authentifié et le rediriger vers l'état auth qui est la page de connexion.

 angular .module('myApp', [ 'ui.router', ]) .run(['$rootScope', 'User', '$state', function ($rootScope, User, $state) { $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { if (toState.name !== 'auth' && !User.authenticaded()) { event.preventDefault(); $state.go('auth'); } }); }] ); 

Je pense que vous avez besoin d'un service qui gère le processus d'authentification (et son stockage).

Dans ce service, vous aurez besoin de certaines méthodes de base:

  • isAuthenticated()
  • login()
  • logout()
  • etc …

Ce service devrait être injecté dans vos contrôleurs de chaque module:

  • Dans votre section de tableau de bord, utilisez ce service pour vérifier si l'utilisateur est authentifié (méthode service.isAuthenticated() ). Sinon, rediriger vers / connexion
  • Dans votre section de connexion, utilisez simplement les données du formulaire pour authentifier l'utilisateur via votre méthode service.login()

Un exemple bon et robuste pour ce comportement est l' application angulaire du projet et plus précisément son module de sécurité qui repose sur le superbe module HTTP Auth Interceptor

J'espère que cela t'aides

J'ai créé ce module pour aider à faire de ce processus un morceau de gâteau

Vous pouvez faire des choses comme:

 $routeProvider .state('secret', { ... permissions: { only: ['admin', 'god'] } }); 

Ou aussi

 $routeProvider .state('userpanel', { ... permissions: { except: ['not-logged-in'] } }); 

C'est tout nouveau mais ça vaut le coup d'être vérifié!

https://github.com/Narzerus/angular-permission

Je voulais partager une autre solution avec le routeur ui 1.0.0.X

Comme vous le savez peut-être, stateChangeStart et stateChangeSuccess sont maintenant obsolètes. https://github.com/angular-ui/ui-router/issues/2655

Au lieu de cela, vous devriez utiliser $ transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html

C'est ainsi que je l'ai atteint:

D'abord, j'ai et AuthService avec quelques fonctions utiles

 angular.module('myApp') .factory('AuthService', ['$http', '$cookies', '$rootScope', function ($http, $cookies, $rootScope) { var service = {}; // Authenticates throug a rest service service.authenticate = function (username, password, callback) { $http.post('api/login', {username: username, password: password}) .success(function (response) { callback(response); }); }; // Creates a cookie and set the Authorization header service.setCredentials = function (response) { $rootScope.globals = response.token; $http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token; $cookies.put('globals', $rootScope.globals); }; // Checks if it's authenticated service.isAuthenticated = function() { return !($cookies.get('globals') === undefined); }; // Clear credentials when logout service.clearCredentials = function () { $rootScope.globals = undefined; $cookies.remove('globals'); $http.defaults.headers.common.Authorization = 'Bearer '; }; return service; }]); 

Ensuite, j'ai cette configuration:

 angular.module('myApp', [ 'ui.router', 'ngCookies' ]) .config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/resumen'); $stateProvider .state("dashboard", { url: "/dashboard", templateUrl: "partials/dashboard.html", controller: "dashCtrl", data: { authRequired: true } }) .state("login", { url: "/login", templateUrl: "partials/login.html", controller: "loginController" }) }]) .run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService', function ($rootScope, $transitions, $state, $cookies, $http, AuthService) { // keep user logged in after page refresh $rootScope.globals = $cookies.get('globals') || {}; $http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals; $transitions.onStart({ to: function (state) { return state.data != null && state.data.authRequired === true; } }, function () { if (!AuthService.isAuthenticated()) { return $state.target("login"); } }); }]); 

Vous pouvez voir que j'utilise

 data: { authRequired: true } 

Pour marquer l'état accessible seulement s'il est authentifié.

Alors, sur le .run, j'utilise les transitions pour vérifier l'état autolaté

 $transitions.onStart({ to: function (state) { return state.data != null && state.data.authRequired === true; } }, function () { if (!AuthService.isAuthenticated()) { return $state.target("login"); } }); 

Je crée cet exemple en utilisant un code trouvé sur la documentation $ transitions. Je suis assez nouveau avec le routeur ui mais ça marche.

J'espère que cela peut aider n'importe qui.

Voici comment nous sommes sortis de la boucle de routage infinie et nous avons toujours utilisé $state.go au lieu de $location.path

 if('401' !== toState.name) { if (principal.isIdentityResolved()) authorization.authorize(); } 

J'ai une autre solution: cette solution fonctionne parfaitement lorsque vous ne disposez que de contenu que vous souhaitez afficher lorsque vous êtes connecté. Définissez une règle dans laquelle vous vérifiez si vous êtes connecté et ce n'est pas un chemin d'accès aux listes blanches.

 $urlRouterProvider.rule(function ($injector, $location) { var UserService = $injector.get('UserService'); var path = $location.path(), normalized = path.toLowerCase(); if (!UserService.isLoggedIn() && path.indexOf('login') === -1) { $location.path('/login/signin'); } }); 

Dans mon exemple, je demande si je ne suis pas connecté et que l'itinéraire actuel que je souhaite parcourir ne fait pas partie de `/ login ', car mes listes de liste blanche sont les suivantes

 /login/signup // registering new user /login/signin // login to app 

Alors j'ai un accès instantané à ces deux routes et toutes les autres routes seront vérifiées si vous êtes en ligne.

Voici mon fichier de routage complet pour le module de connexion

 export default ( $stateProvider, $locationProvider, $urlRouterProvider ) => { $stateProvider.state('login', { parent: 'app', url: '/login', abstract: true, template: '<ui-view></ui-view>' }) $stateProvider.state('signin', { parent: 'login', url: '/signin', template: '<login-signin-directive></login-signin-directive>' }); $stateProvider.state('lock', { parent: 'login', url: '/lock', template: '<login-lock-directive></login-lock-directive>' }); $stateProvider.state('signup', { parent: 'login', url: '/signup', template: '<login-signup-directive></login-signup-directive>' }); $urlRouterProvider.rule(function ($injector, $location) { var UserService = $injector.get('UserService'); var path = $location.path(); if (!UserService.isLoggedIn() && path.indexOf('login') === -1) { $location.path('/login/signin'); } }); $urlRouterProvider.otherwise('/error/not-found'); } 

() => { /* code */ } est la syntaxe ES6, utilisez plutôt la function() { /* code */ }

D'abord, vous aurez besoin d'un service que vous pouvez injecter dans vos contrôleurs qui ont une idée de l'état d'authentification de l'application. La persistance des détails d'authentification avec le stockage local est un moyen décent de l'aborder.

Ensuite, vous devrez vérifier l'état d'auth directement avant les changements d'état. Étant donné que votre application dispose de certaines pages qui doivent être authentifiées et d'autres personnes qui ne le font pas, créez une route parent qui vérifie l'authentification et crée toutes les autres pages qui exigent que le même enfant soit l'un de ces parents.

Enfin, vous aurez besoin de savoir si votre utilisateur actuellement connecté peut effectuer certaines opérations. Cela peut être obtenu en ajoutant une fonction 'can' à votre service d'authentification. Can prend deux paramètres: – action – requise – (c.-à-d. 'Manage_dashboards' ou 'create_new_dashboard') – objet – optionnel – objet activé. Par exemple, si vous aviez un objet de tableau de bord, vous voudrez peut-être vérifier si dashboard.ownerId === loggedInUser.id. (Bien sûr, les informations transmises par le client ne doivent jamais être approuvées et vous devez toujours vérifier cela sur le serveur avant de l'écrire sur votre base de données).

 angular.module('myApp', ['ngStorage']).config([ '$stateProvider', function( $stateProvider ) { $stateProvider .state('home', {...}) //not authed .state('sign-up', {...}) //not authed .state('login', {...}) //not authed .state('authed', {...}) //authed, make all authed states children .state('authed.dashboard', {...}) }]) .service('context', [ '$localStorage', function( $localStorage ) { var _user = $localStorage.get('user'); return { getUser: function() { return _user; }, authed: function() { return (_user !== null); }, // server should return some kind of token so the app // can continue to load authenticated content without having to // re-authenticate each time login: function() { return $http.post('/login.json').then(function(reply) { if (reply.authenticated === true) { $localStorage.set(_userKey, reply.user); } }); }, // this request should expire that token, rendering it useless // for requests outside of this session logout: function() { return $http.post('logout.json').then(function(reply) { if (reply.authenticated === true) { $localStorage.set(_userKey, reply.user); } }); }, can: function(action, object) { if (!this.authed()) { return false; } var user = this.getUser(); if (user && user.type === 'admin') { return true; } switch(action) { case 'manage_dashboards': return (user.type === 'manager'); } return false; } } }]) .controller('AuthCtrl', [ 'context', '$scope', function( context, $scope ) { $scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { //only require auth if we're moving to another authed page if (toState && toState.name.indexOf('authed') > -1) { requireAuth(); } }); function requireAuth() { if (!context.authed()) { $state.go('login'); } } }] 

** AVERTISSEMENT: Le code ci-dessus est pseudo-code et ne comporte aucune garantie **

Utilisez $ http Interceptor

En utilisant un intercepteur $ http, vous pouvez envoyer des en-têtes à Back-end ou à l'inverse et faire vos chèques de cette façon.

Grand article sur les intercepteurs $ http

Exemple:

 $httpProvider.interceptors.push(function ($q) { return { 'response': function (response) { // TODO Create check for user authentication. With every request send "headers" or do some other check return response; }, 'responseError': function (reject) { // Forbidden if(reject.status == 403) { console.log('This page is forbidden.'); window.location = '/'; // Unauthorized } else if(reject.status == 401) { console.log("You're not authorized to view this page."); window.location = '/'; } return $q.reject(reject); } }; }); 

Mettez ceci dans votre fonction .config ou .run.