Angularjs routing choppy when using authentication - javascript

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.
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.
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'
}
}
},

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.

The ui-router has a resolve option that should clear up the chop. https://github.com/angular-ui/ui-router/wiki. Anything that is contained in the resolve section will be completely resolved before the rest of the route changes happen and the pages load.
isLoggedIn: function( event, toState ) {
var user_api = {};
if( "user" in $rootScope ) {
user_api = { "api": $rootScope.user.api };
}
return $http.post('http://bac-api/authenticated/', user_api);
},
resolve: {
var logged = Auth.isLoggedIn();
logged.then(function(data){
//success
}, function(data){
//failure
});
}

Related

Is it bad practice to set an "isAuthenticated" flag in LocalStorage?

This is more of general frontend question, but for context, I'm using Vue 3 and vue-router.
I'm constructing functionality for authentication with Vue 3. Because I'm not using Vuex, I couldn't find a way to access state from the file where the router is instantiated. So I thought maybe I'd stick a flag called isAuthenticated into LocalStorage.
When the user logs in, isAuthenticated gets set to true, and then the router reads from this to validate subsequent redirects. This is very different from storing a token in LocalStorage – I store my tokens in httpOnly cookies.
For those that know Vue3/Vue-router, it looks like this:
Router.beforeEach(async (to, from) => {
// localStorage.isAuthenticated = false
const isAuthenticated = localStorage.isAuthenticated;
if (
// make sure the user is authenticated
isAuthenticated !== 'true' &&
to.meta.requiresAuth &&
// Avoid an infinite redirect
to.path !== '/login'
) {
// redirect the user to the login page
return '/login'
}
})
It would be no big deal if someone hacked this and changed the flag to true; my backend will reject any API call that doesn't have the right JWT token.
But is this bad practice? It feels a little flimsy, and I'm looking to write a robust and secure app.

Initialize Current User Service on Application Start in AngularJS

I’m developing a Single Page Application with AngularJS.
When a user successfully logs in, a security token is stored in a cookie. Now, when he refreshes the page, the token will be sent to the backend, which returns a JSON object "currentUser" containing all the relevant information about the current user (as name, access-groups, profile picture, etc.).
The problem is, this is an asynchronous process of course, so when the controller starts another operation, say, just alerting the user’s name, this value will be undefined at that time.
Of course, I could set a timeout but is there a better solution?
I thought about a "currentUserService", which initializes first (sending the cookie and filling the user information with the backend response) and can only be processed after this initialization is completed.
But how can this be done? Or are there any other possibilities?
edit:
Hi guys,
thanks for the input!
Both of your suggestions seem to be very promising for asynchronous requests in general, but I think they might not fit perfectly for my concern:
The information about the current user only have to be requested once, so I would like to store them for the whole application (e.g. in the rootScope or a service) accessible from any controller without having to request them again in every controller (as in the callback or resolve-solution) but make sure that there won’t be any „timeout“ problems. Do you have any ideas?
You can resolve the user's data before the view loads either with ng-route or ui-router:
This example is written for ui-router:
.state('profile', {
url: '/profile',
controller: 'profileCtrl as vm',
resolve: {
user: function(AuthService) {
//Return a promise or an object to be resolved.
return AuthService.getUserFromToken(); //Say this is asynchronous and returns a promise
}
}
});
//In controller:
.controller('profileCtrl', function(... , user) {
//User data available here
this.user = user;
});
Please note if any errors arise during the resolve stage the state will not be loaded so you'll have to take care of the errors!
If a user refreshes you have to initialize everything. I assume the token is stored in localstorage or something and I assume this is angular 1.*. To do this I think you should call user-related functions from your http call-callback:
$scope.user = {};
$scope.getUser = function(){
$http({
method: 'GET',
url: '/someUrl'
}).then(function (res) {
$scope.user = res.data; //or whatever the response is
$scope.handleUserRelatedThings();
}).catch(function(err) {
//handle error
})
}
$scope.handleUserRelatedThings = function(){
//do things with $scope.user
alert($scope.user.name);
}
//on init
$scope.getUser();

Authentication logic for sessions that last while tab is open

Assume you are working on a front end application that performs authentication through 3rd party api. Successful authentication returns a json web token.
What would be best practices to store such token and create some sort of session for user while he is active on the website i.e. didn't close a tab or browser, however refreshing / reloading a page should not destroy such session.
Also, how can this session be used to protect routes? I am working with a stack consisting of react / redux / node / express and quiet a few other libraries. I believe I can perform certain checks within my react-router, however wouldn't it be better to do these on the express side?
You can store the token in localStorage or sessionStorage, and include it in every API request.
Local storage outlives the tab, it's stored there until you explicitly delete from it, so refreshing a page won't be a problem. Even closing a tab and then coming back won't be.
Session storage allows you to store data. Page refreshes are fine, but tab closing isn't, which is closer to the behavior you want.
As for protecting routes, the server should obviously check the token on requests to all protected API routes.
On the browser side, you will probably want to show a login form if a user tries to visit a protected route but the token isn't there (or is invalid).
With react-router, you could do it like the official repo shows in the example, via onEnter hooks: https://github.com/reactjs/react-router/blob/master/examples/auth-flow/app.js
An alternative would be to create two top-level components, one for protected routes, one for public routes (like a landing page or the sign in/sign up forms). The protected handler will then in componentWillMount check if there's a token:
- PublicHandler
+ SignIn
+ SignUp
+ Index
- ProtectedHandler
+ Dashboard
+ MoneyWithdrawal
it may looks like that , with sessionStorage (JWT token is accesseble, untill browser or tab closed)
///action creator redux
export const signupUser = creds => dispatch =>{
dispatch(requestSignup());
return API.auth.signup(creds)
.then(res => {
sessionStorage.setItem('token', res.token);// <------------------
dispatch(receiveSignup(res));
return res;
})
.catch(err => {
dispatch(SignupError(err));
);
});
};
On client : handling auth through HOC redux-auth-wrapper
On server on server you can use passport-jwt strategy
passport.use('jwt',new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({where:{ id: jwt_payload.user.id }}).then(user=>{
if (user) {
done(null, jwt_payload.user);
} else {
done(null, false);
// or you could create a new account
}
},err=>{
console.log('Error ',err);
return done(err,false);
});
}));
then just add route handler
var checkJWT = passport.authenticate('jwt')
router.get('/protected',checkJWT, (req, res) =>{
res.json(req.user);
});
You don't need sessions on server for that

Defer creation of controllers/services on app run angularjs

every time the route change, I check if is set a variable with the current logged user details, if this variable is not set, I simply redirect to the login page.
Now I'm trying to achieve some kind of "remember me" functionality, so every time the route changes, if the variable user doesn't exist instead of redirecting to the login page, I check the local storage for an "authtoken", if is set I call a check function passing the authtoken to the server, that returns the user and the app will works the same way as after the manual login (that returns the same user as the check function).
I'm pretty sure this is not the best way to do that.
If I reload the page, first thing I run the check function that sends the authtoken to the server and wait for a response, if the user exists that value is assigned to a variable in the rootscope.
I have different services that use the variable in the rootscope, for example
angular.module('myApp')
.service('AdminService', function AdminService($rootScope, $http, StorageService) {
var authtoken = StorageService.get('authtoken');
var clientId = $rootScope.loggedUser.id;
and of course when the check function runs it waits for the response, but the service is being instantiated and $rootScope.loggedUser.id does not exists.
How can I tell the app to wait until the check function receive the response?
This is my code
....
}).run(function($rootScope, $location, AuthService, StorageService) {
var intended = $location.path();
AuthService.check().success(function(data) {
if(data.user) {
$rootScope.loggedUser = data.user;
$location.path(intended);
} else $location.path('login');
});
$rootScope.$on('$routeChangeStart', function() {
if($rootScope.loggedUser) {
....
For example if the user bookmarks the page "myapp.com/#/admin/users", I don't want to redirect to the login, if I have in local storage the authtoken, but this causes the controller to be instantiated, that uses the service, that needs the $rootScope.loggedUser.id that is not yet populated.
And I want to run the function check only when the page (re)loads (not every time the user change route).
I would advise to re-examine your design if you need a service call to check your auth token. Auth tokens typically have expiration time stored in them, so you could just check whether you are within the expiration period without calling the server. You are not compromising security, since auth tokens are signed by the server, and validated when you make server calls to do anything useful. So, under normal circumstances, no separate check call is needed.
But, if you insist, this use case is best handled with the resolve property of the route. This means, however, that every route that cares about the user's logged-in state would have to have a resolve property defined. This does not mean that you have to call the service on each route change.
Instead of using $rootScope.loggedUser to store the user, have it be cached by the AuthService and injected via the resolve parameter.
So, you could do the following:
$routeProvider
.when("some/secure/route", {
controller: "SecureCtrl",
resolve: {
user: AuthService.checkUser
}
});
And in your AuthService:
...
checkUser: function(){
var deferred = $q.defer();
if (cachedUser){
deferred.resolve(cachedUser);
} else {
AuthService.check().success(
function(data){
// cachedUseris defined at AuthService's scope
cachedUser = data.user;
deferred.resolve(data.user);
});
}
return deferred.promise;
}
Then, in your controllers:
.controller("SecureCtrl", function(user){
$scope.userId = user.id;
}

backbone.js - handling if a user is logged in or not

Firstly, should the static page that is served for the app be the login page?
Secondly, my server side code is fine (it won't give any data that the user shouldn't be able to see). But how do I make my app know that if the user is not logged in, to go back to a login form?
I use the session concept to control user login state.
I have a SessionModel and SessionCollection like this:
SessionModel = Backbone.Model.extend({
defaults: {
sessionId: "",
userName: "",
password: "",
userId: ""
},
isAuthorized: function(){
return Boolean(this.get("sessionId"));
}
});
On app start, I initialize a globally available variable, activeSession. At start this session is unauthorized and any views binding to this model instance can render accordingly. On login attempt, I first logout by invalidating the session.
logout = function(){
window.activeSession.id = "";
window.activeSession.clear();
}
This will trigger any views that listen to the activeSession and will put my mainView into login mode where it will put up a login prompt. I then get the userName and password from the user and set them on the activeSession like this:
login = function(userName, password){
window.activeSession.set(
{
userName: userName,
password: password
},{
silent:true
}
);
window.activeSession.save();
}
This will trigger an update to the server through backbone.sync. On the server, I have the session resource POST action setup so that it checks the userName and password. If valid, it fills out the user details on the session, sets a unique session id and removes the password and then sends back the result.
My backbone.sync is then setup to add the sessionId of window.activeSession to any outgoing request to the server. If the session Id is invalid on the server, it sends back an HTTP 401, which triggers a logout(), leading to the showing of the login prompt.
We're not quite done implementing this yet, so there may be errors in the logic, but basically, this is how we approach it. Also, the above code is not our actual code, as it contains a little more handling logic, but it's the gist of it.
I have a backend call that my client-side code that my static page (index.php) makes to check whether the current user is logged in. Let's say you have a backend call at api/auth/logged_in which returns HTTP status code 200 if the user is logged in or 400 otherwise (using cookie-based sessions):
appController.checkUser(function(isLoggedIn){
if(!isLoggedIn) {
window.location.hash = "login";
}
Backbone.history.start();
});
...
window.AppController = Backbone.Controller.extend({
checkUser: function(callback) {
var that = this;
$.ajax("api/auth/logged_in", {
type: "GET",
dataType: "json",
success: function() {
return callback(true);
},
error: function() {
return callback(false);
}
});
}
});
Here is a very good tutorial for it http://clintberry.com/2012/backbone-js-apps-authentication-tutorial/
I think you should not only control the html display but also control the display data. Because user can use firefox to change your javascript code.
For detail, you should give user a token after he log in and every time he or she visit your component in page such as data grid or tree or something like that, the page must fetch these data (maybe in json) from your webservice, and the webservice will check this token, if the token is incorrect or past due you shouldn't give user data instead you should give a error message. So that user can't crack your security even if he or she use firebug to change js code.
That might be help to you.
I think you should do this server sided only... There are many chances of getting it hacked unit and unless you have some sort of amazing api responding to it

Categories

Resources