wingmatt wingmatt - 29 days ago 19
Javascript Question

Access control with Angular & UI-Router. Call Stack Size errors & not reaching Express auth route

I'm trying to add access control to my Angular app and running into some puzzling troubles...

On the front-end, the authentication function is being called repeatedly until it is stopped for being too large of a call stack. Each time, the $http request within the function is triggering the errorCallback function. The expected behavior is for the auth function to fire once every time ui-router's state changes, changing $rootScope values that indicate the browser's level of authentication.

First, the factory responsible for making the GET request:

.factory ('authRequest', function ($http) {
return {
authStatus : function() {
$http.get('/auth', {'withCredentials' : true}).then(function successCallback(response) {
console.log("Successful authorization check.");
return response.status;

}, function errorCallback(response) {
if (response.status) {
console.log("Failed to authenticate.");
return response.status;
}
console.log("Failed to receive a response.");
return 'errNoResponse';
});
}
}

})


Then, the ng-controller for processing the factory's response:

//Navbar controller, set to fire upon each state change and verify authorization.
.controller('navCtrl', function ($rootScope, $state, authRequest) {
$rootScope.$on('$stateChangeStart', function (event, toState) {
console.log('authRequest value: ' + [authRequest]);

if (authRequest.authStatus === 202) {
console.log('Auth check succeeded, assigned user privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = false;
if (toState = 'users' || 'login') {
event.preventDefault();
}
} else if (authRequest.authStatus === 222) {
console.log('Auth check succeeded, assigned admin privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = true;
if (toState = 'login') {
event.preventDefault();
}
} else {
console.log('Auth check failed.');
$rootScope.loggedIn = false;
$rootScope.loggedInAdmin = false;
event.preventDefault();
$state.go('login');
}
});
})


Meanwhile, on the back-end, I'm not seeing evidence of the /auth Express route being reached with any of the requests. I have a console log set to go off when /auth receives a GET request, but I'm not seeing any activity in the console. Every other Express route is being accessed without issue. The expected behavior is to receive the request, decode the request's JWT cookie header, then send a response code back according to what sort of user privileges are listed. Here's the Express route for /auth:

// GET /auth. Fired every time the app state changes. Verifies JWT authenticity and sends a response based on the user's privileges. 202 is an auth'd user, 222 is an auth'd admin. 401 is no token or error.
app.get('/auth', function (req, res, next) {
console.log('Authorization request received.')
var decoded = jwt.verify(req.cookie, [JWTAuthSecret]);
if (decoded) {
if (decoded.admin === true) {
return res.status(222).send(res.status);
console.log(decoded.sub + ' is an admin, responded with code 222');
} else {
return res.status(202).send(res.status);
console.log(decoded.sub + ' is not an admin, responded with code 202');
}
} else {
return res.status(401).send(res.status);
console.log('Decode failed, responded with code 401');
};
});


With the current setup, the app is hanging indefinitely. As mentioned earlier, a ton of auth requests are being produced upon each state change. Each one logs an "authRequest value: [object Object]" then "Auth check failed." Eventually I get the following error:

angular.js:13550RangeError: Maximum call stack size exceeded
at angular.js:10225
at n.$broadcast (angular.js:17554)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)


So there seems to be a problem with the frequency of the calls on the front end, as well as a problem with actually getting the data sent to the /auth route.

This is my first time working with Angular factories, so my instinct is to assume my factory implementation is wonky... I haven't figured out how it might be fixed on my own, though.

Thanks for reading, hope I can get some advice on what to change to make this work.

Answer

I see a couple issues. This might not solve everything, but will hopefully help narrow things down.

One is that authRequest.authStatus is a function but you're never calling it. In your controller, you need to call the function. That's part of the reason nothing's pinging the backend.

authRequest.authStatus().then(function(status) {
    if (status === 202) {
        //do stuff
    } else if (status === 222) {
        //do other stuff
    }
});

Now, in your factory, you're not returning anything to the function, so make sure you do that.

.factory ('authRequest', function ($http) {
    return {
        authStatus : function() {
            return $http.get('url').then(callbacks);
        }
    }
})