Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angularjs routing choppy when using authentication

I am looking for a better way to do the transitions between route states in angularjs. Currently I have been following a couple different tutorials to configure angular with a backend api server using authentication on the server side.

http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/ https://vickev.com/#!/article/authentication-in-single-page-applications-node-js-passportjs-angularjs

Both of these describe a similar to identical solution to authentication on server side rather than on the client side. I have a Auth service in angular that uses a method to post to the api server checking whether the user isLoggedIn using an api key.

isLoggedIn: function( event, toState ) {
        var user_api = {};

        if( "user" in $rootScope ) {
            user_api = { "api": $rootScope.user.api };
        } 

        $http.post('http://bac-api/authenticated/', user_api)
            .success( function( user ) {
                if( toState.url === '/login' && user.authenticated === true) {
                    $state.transitionTo('dashboard');
                }
            })
            .error( function( user ) {
                if( user.authenticated === false ) {
                    event.preventDefault();
                    $state.transitionTo('login');
                }
            });
    },

If the api key is not present of course the user is not logged in and the server will send a {"autheticated": false} 401 response. If the user has an api key it uses that api key to check on the server whether it is valid (logged in) or not valid (not logged in).

In order to catch the routes and check whether a user is authenticated I and using "stateChangeStart"

$rootScope.$on("$stateChangeStart", function ( event, toState, toParams, fromState, fromParams ) {

    Auth.isLoggedIn( event, toState );

});  

The first problem with this is that a state change is not triggered on the intial page load and results in using the routeProvider to transition to the login page. And then throughout the app if a route that is requested is not in the configured routes it will transition to the login page but not trigger a stateChangeStart. So the user could be logged in and sitting at the login page. I would rather it transfer to the dashboard as my configured route.

For the most part this setup seems to be working ok in theory but the routes are choppy. So what will happen is in between checking if the user is logged in the route will start to change and start to show the page but then when the application realizes the user is not logged it will make the switch to the login page. I would like to resolve this choppiness. And instead of getting a glipse of the other pages be able to transfer correctly.

I am using ui-router and states in the application for everything. And then using the Route Provider only to do $routeProvider.otherwise('login') when there isn't a route for the requested route.

  1. I would like to figure out how to stop showing part of new pages (the choppiness) during route transitions while the application is checking for the user being authenticated. Whether that is in a different event that I'm unaware of or whatever.

  2. Also a better way to use routeProvider.otherwise() to trigger a state change and check whether the user isLoggedIn. If logged in transfer to the dashboard ifnot logged in stay on login page until finished logging in.

Sorry if this is so confusing. I am new to angularjs and have been reading everything I can find about routes, states and authentication. This is a new concept for me to learn how to manage authentication on the server side with angular.

-- Edit

After taking the recommendation of MBielski I have implemented resolve into the states to use the Auth.isLoggedIn() service. But this still has not removed the choppiness of switching routes and I even fear that once this is not in local development the choppiness will become worse waiting on the api response. Here is the resolve code for my parent dashboard state.

'dashboard-shell': {
            name: 'dashboard-shell',
            resolve: {
                Auth: function( Auth ) {
                    return Auth.isLoggedIn();
                }
            },
            views: {
              "dashboard-shell": {
                controller: 'DashboardController',
                templateUrl: 'app/modules/template-manager/partials/dashboard.tpl.html'
              }
            }
        },
like image 381
lumberjacked Avatar asked Oct 04 '22 01:10

lumberjacked


1 Answers

Your problem seems to be that every state change results in a POST request that must be handled before you can switch states. I assume you do that to handle session expiry, or because you don't have an authentication cookie to inspect.

You've basically implemented a pessimistic approach to ensure the user is authenticated before doing any other HTTP callbacks.

Another approach might be to use the angular-http-auth HTTP interceptor. This interceptor provides an optimistic approach to ensure the user is authenticated: it just passes every HTTP call on to the backend, but you give it a special callback; when the backend returns a 403 Unauthorized this callback is used to e.g. show a login dialog so the user can (re)authenticate. After that, the interceptor replays all HTTP calls that resulted in a 403.

like image 63
publysher Avatar answered Oct 10 '22 19:10

publysher