web-dev-qa-db-ja.com

Angular JS UIルーターログイン認証

私はAngularJSに慣れていないので、次のような状況でAngularJiをどのように使用できるかについて少し混乱しています。

私は2つのセクションからなるWebアプリケーションを構築しています。最初のセクションはログインビューとサインアップビューのあるホームページで、2番目のセクションはダッシュボードです(ログイン成功後)。

私はホームセクション用のindex.htmlをそのangle appとui-routerおよび/loginビューを扱うための/signup configで作成しました。また、ダッシュボードセクション用の別のdashboard.htmlというアプリとui-router configで多くのサブビューを処理します。

今、私はダッシュボードセクションを終えました、そして、2つのセクションを彼らの異なった角度のあるアプリと組み合わせる方法を知りません。ホームアプリにダッシュボードアプリにリダイレクトするように指示するにはどうすればよいですか。

370
Ahmed Hashem

私の意見では、これまでに投稿された解決策は不必要に複雑です。もっと簡単な方法があります。 ui-routerのドキュメント$locationChangeSuccessを監視し、$urlRouter.sync()を使って状態遷移の確認、停止、再開を行います。しかし、それでも実際には機能しません。

ただし、これには2つの簡単な方法があります。一つを選ぶ:

解決策1:$locationChangeSuccessをリ​​ッスンする

あなたは$locationChangeSuccessを聞くことができ、そこでは非同期ロジックでさえも何らかのロジックを実行することができます。そのロジックに基づいて、関数にundefinedを返させることができます。これにより、状態遷移を通常どおり継続させることができます。あるいは、ユーザーを認証する必要がある場合は$state.go('logInPage')を実行できます。これが例です:

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')
      })
  })
})

これは実際にはターゲット状態の読み込みを妨げるものではありませんが、ユーザーが承認されていない場合はログインページにリダイレクトされます。とにかく、本当の保護がサーバーにあるのでそれは大丈夫です。

解決策2:状態resolveを使う

このソリューションでは、 ui-router resolve feature を使用します。

ユーザーが認証されていない場合は、基本的にresolveの約束を拒否してからログインページにリダイレクトします。

これがどうやって行くのか:

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()
      }
    }
  }
)

最初の解決策とは異なり、この解決策は実際にはターゲット状態のロードを防ぎます。

117
M.K. Safi

最も簡単な解決策は、ユーザが認証されていないときに$stateChangeStartevent.preventDefault()を使用して状態の変更をキャンセルし、ログインページである auth 状態にリダイレクトすることです。

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');
        }
      });
    }]
  );
42
sebest

認証プロセス(およびその記憶域)を処理するserviceが必要だと思います。

このサービスでは、いくつかの基本的な方法が必要になります。

  • isAuthenticated()
  • login()
  • logout()
  • など.

このサービスは各モジュールのあなたのコントローラーに注入されるべきです:

  • ダッシュボードのセクションで、このサービスを使用してユーザーが認証されているかどうかを確認します(service.isAuthenticated()メソッド)。そうでなければ、/ loginにリダイレクトします。
  • あなたのログインセクションで、あなたのservice.login()メソッドを通してユーザーを認証するためにフォームデータを使うだけです

この振る舞いの良い頑強な例はproject angular-app そして特に securityモジュール です。これは素晴らしい HTTP Auth Interceptor Module を基にしています。

お役に立てれば

22
Cétia

このプロセスを簡単にするためにこのモジュールを作成しました

次のようなことができます。

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

または

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

新品ですが、チェックする価値があります。

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

21
Rafael Vidaurre

私は、UIルーター1.0.0.Xで動作する別の解決策を共有したいと思いました

ご存じのとおり、stateChangeStartとstateChangeSuccessは現在は推奨されていません。 https://github.com/angular-ui/ui-router/issues/2655

代わりに、$ transition http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html を使用してください。

これが私が達成した方法です。

まず私が持っていると AuthService いくつかの便利な機能

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;
                    }]);

それから私はこの設定をしています:

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");
                    }
                });
            }]);

私が使っているのがわかります

data: {
   authRequired: true
}

認証された場合にのみアクセス可能状態にマークを付けます。

それから、 .run に遷移を使って認証状態をチェックします

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

この例は、$ transitionのドキュメントにあるコードを使って作成します。私はUIルーターではかなり新しいですが、それは動作します。

誰かに役立つことを願っています。

14

これが、無限ルーティングループから抜け出し、$state.goの代わりに$location.pathを使用した方法です。

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

私はもう一つの解決策を持っています:あなたがログインしているときあなたが見せたいコンテンツだけを持っているときその解決策は完璧に働きます。

$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');
   }
});

私の例では、私がログインしておらず、私がルーティングしたい現在の経路が `/ login 'の一部ではないかどうかを尋ねます。

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

だから私はこの2つのルートへの即時アクセスを持っており、あなたがオンラインであれば他のすべてのルートがチェックされます。

これはログインモジュールのための私の全体のルーティングファイルです。

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 */ }はES6の構文です。代わりにfunction() { /* code */ }を使用してください。

2
Chris Incoqnito

まず、アプリの認証状態について何らかのアイデアを持った、コントローラーに注入できるサービスが必要です。ローカルストレージで認証情報の詳細を保持することは、それに取り組むための適切な方法です。

次に、状態が変わる直前にauthの状態を確認する必要があります。認証される必要があるページと認証されないページがあるので、authをチェックする親ルートを作成し、それを必要とする他のすべてのページをその親の子にします。

最後に、現在ログインしているユーザーが特定の操作を実行できるかどうかを判断するための何らかの方法が必要になります。これはあなたの認証サービスに 'can'関数を追加することで達成できます。次の2つのパラメータを取ることができます。 - action - required - (つまり、「manage_dashboards」または「create_new_dashboard」) - object - オプション - 操作されているオブジェクト。たとえば、ダッシュボードオブジェクトがある場合は、dashboard.ownerId ===loggedInUser.idかどうかを確認します。 (もちろん、クライアントから渡された情報は決して信頼されるべきではなく、データベースに書き込む前に必ずサーバー上でこれを確認する必要があります)。

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');
      }
   }
}]

**免責事項:上記のコードは擬似コードであり、保証は付属していません**

2
colefner

$ httpインターセプタを使用する

$ httpインターセプターを使用することで、バックエンドまたは他の方法でヘッダーを送信し、その方法でチェックを行うことができます。

$ httpインターセプターに関する素晴らしい記事

例:

$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);
            }
        };
    });

これをあなたの.configまたは.run関数に入れてください。

2
TSlegaitis