20

What I am looking to do I when a user comes to the index.html page I need the login module to do 2 things:

  1. I need this to check if a user is authenticated ( which I think I already started with the "function authService" ) if the user has a valid token then change the ui-view to dashboard/dashboard.html and if the key is not valid or there is no key at all then load login/login.html into ui-view.

  2. Once they have successfully logged in I want them to be routed to "dashboard/dashboard.html"

Here is my login script:

function authInterceptor(API) {
  return {
    request: function(config) {
      if(config.url.indexOf(API) === 0) {
        request.headers = request.headers || {};
        request.headers['X-PCC-API-TOKEN'] = localStorage.getItem('token');
      }
      return config;
    }
  } 
}

function authService(auth) {
  var self = this;
  self.isAuthed = function() {
    localStorage.getItem('token');
  }
}

function userService($http, API) {

  $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;';
  $http.defaults.transformRequest = [function(data) {
      return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
    }];

  var self = this;

  self.login = function(username, pwd, ctrl) {
    ctrl.requestdata = API + '/winauth' + '; with ' + username;
    return $http.post(API + '/winauth', {
        username: username,
        pwd: pwd
      })
  };

  var param = function(obj) {
    var query = '', name, value, fullSubName, subName, subValue, innerObj, i;

    for(name in obj) {
      value = obj[name];

      if(value instanceof Array) {
        for(i=0; i<value.length; ++i) {
          subValue = value[i];
          fullSubName = name + '[' + i + ']';
          innerObj = {};
          innerObj[fullSubName] = subValue;
          query += param(innerObj) + '&';
        }
      }
      else if(value instanceof Object) {
        for(subName in value) {
          subValue = value[subName];
          fullSubName = name + '[' + subName + ']';
          innerObj = {};
          innerObj[fullSubName] = subValue;
          query += param(innerObj) + '&';
        }
      }
      else if(value !== undefined && value !== null)
        query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
    }

    return query.length ? query.substr(0, query.length - 1) : query;
  };

}

function LoginCtrl(user) {
  var self = this;

  function handleRequest(res) {
    self.responsedata = res;
    self.message = res.data.message;

    var authToken = res.data.auth_token;
    localStorage.setItem('token', authToken);
  }

  self.login = function() {
    this.requestdata = 'Starting request...';
    user.login(self.username, self.pwd, self)
      .then(handleRequest, handleRequest)
  }
}

// Login Module
var login = angular.module('login', ["ui.router"])

login.factory('authInterceptor', authInterceptor)
login.service('user', userService)
login.service('auth', authService)
login.constant('API', 'http://myserver.com/api')

EDIT - I added this into my login controller to provide the login routes

login.config(function($httpProvider, $stateProvider, $urlRouterProvider) {

  $httpProvider.interceptors.push('authInterceptor');

  $urlRouterProvider.otherwise('/login');

  $stateProvider
  // HOME STATES AND NESTED VIEWS ========================================
  .state('login', {
    url: '/login',
    templateUrl: 'login/login.html',
    controller: "mainLogin",
    controllerAs: "log"
  })           
  // nested list with just some random string data
  .state('dashboard', {
    url: '/dashboard',
    templateUrl: 'dashboard/dashboard.html',
  })

})

login.controller('mainLogin', LoginCtrl)

Here is my index.html:

EDIT - I removed "ng-include" and added "ng-view" to control the routes.

<body ng-app="login" ng-controller="mainLogin as log" class="loginPage">

  <div class="main" ui-view></div>

</body>

As you can see I have a function that is checking for the token in the users local storage:

function authService(auth) {
  var self = this;
  self.isAuthed = function() {
    localStorage.getItem('token');
  }
}

And I am loading it in the module as a service:

login.service('auth', authService)

This is where I am stuck. I don't know where to go from here. I don't even know if I am using my authService function properly. I am still learning a lot about AngularJS so its easy for me to get stuck. :)

Another thing you will notice is in my index.html file I am just loading the "login/login.html" partial as default. I need it to load either login.html or dashboard.html depending if they are logged in or not. And then also route them to dashboard.html once they have successfully logged in.

The script works great as far as hitting the auth API, authenticating the user and then storing a valid auth key on their local storage.

Anyone know how I can accomplish this?

agon024
  • 1,007
  • 1
  • 13
  • 37
  • I wouldn't recommend doing this in Front End, it's unsafe and can have rifts for the user. – bzim Aug 01 '16 at 21:38
  • @BrunoCorrêaZimmermann well this is AngularJS and it is authenticating thru a REST API on a private server. So I don't see why you think this is unsafe. The only unsafe thing that can really be said is that I am storing their authentication token on the users local storage. But this app is also an internal app for employees only that are given access to it. – agon024 Aug 01 '16 at 21:49

9 Answers9

2

There are two separate concerns that you are dealing with. The first, is to be able to determine if you are logged in. Assuming the user needs to be logged in for any state except the login state, you would implement it like so by listening for $stateChangeState events and verifying that the user is logged in:

login.run(function($state, authService) {
    $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        var authToken = authService.isAuthed();
        if (!authToken && toState !== 'login') {
            //not logged in, so redirect to the login view instead of the view
            //the user was attempting to load
            event.preventDefault();
            $state.go('login');

        } 
    })
});

This will put them on the login state if they haven't already logged in.

The second part is to redirect to the correct view after they login, which you would do in your login controller:

function LoginCtrl(user, $state) {
    var self = this;

    function handleRequest(res) {
        self.responsedata = res;
        self.message = res.data.message;

        var authToken = res.data.auth_token;
        localStorage.setItem('token', authToken);

        //after successful login, redirect to dashboard
        $state.go('dashboard');
    }

    self.login = function() {
        this.requestdata = 'Starting request...';
        user.login(self.username, self.pwd, self)
                .then(handleRequest, handleRequest)
    }
}
mcgraphix
  • 2,723
  • 1
  • 11
  • 15
2

ok I see you are using ui.router so let's work within this framework.

You want to

  1. check if a user is logged in
  2. redirect user to a view

What you're looking for is resolve:{loggedIn: checkLoggedInFn}

so your route for dashboard could be something like

.state('dashboard', {
 url: '/dashboard',
 templateUrl: 'dashboard/dashboard.html',
 resolve: {
    loggedIn: function(){
       //do your checking here
    }
  }
})

what this does basically is that the controller will not instantiate until every resolve is resolved (so you can use a promise here for example), and then the value is passed into the controller as a parameter, so you could then do something like:

if(!loggedIn){
   $state.go('login');
}
Dacheng
  • 156
  • 4
1

You would handle the logic inside your login controller specifically here:

 self.login = function() {
    this.requestdata = 'Starting request...';
    user.login(self.username, self.pwd, self)
      .then(handleRequest, handleRequest)
  }

Inside the callback for the success on login, you would simple do a state change to send the user to the correct state.

Garuuk
  • 2,153
  • 6
  • 30
  • 59
  • I wouldn't even know with what logic to handle this with. This is the first time that I have done token based authentication in AngularJS. Please forgive my ignorance. :( – agon024 Jul 29 '16 at 19:55
  • I would highly suggest you read up on the ui-router module https://github.com/angular-ui/ui-router. When you have your routers set up, then you just inject the $state provider into your controller and do $state.go('state'). If you're still having trouble after reading the ui.router module then let me know and I can help you further – Garuuk Jul 29 '16 at 20:07
  • I added the routes to the controller and changed the ng-include to ng-view (see my edits above) and this does load the login template by default which is what I want it to do. But now how do I inject the $state provider into the controller and change it with $state.go('state). I tried a couple of things but they broke the app :) – agon024 Jul 29 '16 at 22:54
  • I have tried " self.login.$inject = ['$state']; " and then changed the function to " self.login = function($state) " and then added $state.go('dashboard') after " .then " but that doesn't work – agon024 Aug 02 '16 at 21:31
1

In authInterceptor add code for response. So:

return {
    request: function(config) {

        if(config.url.indexOf(API) === 0) {
        request.headers = request.headers || {};
        request.headers['X-PCC-API-TOKEN'] =  localStorage.getItem('token');
        }

        return config;
    },
    response: function(response) {

        //ok response - code 200

        return response;
    },
    responseError: function(response){

       //wrong response - different response code
    }
};

On server side check http header X-PCC-API-TOKEN and if is wrong ( no authentication) response should have different code like 403. So responseError method will run in interceptor.

    responseError: function(response){

       //wrong response - different response code
       if (response.status === 403) {
            alert("No rights to page");//your code for no auth

            //redirect to different route
            $injector.get('$state').transitionTo('login');//use injector service            
            return $q.reject(response);//return rejection - use $q
        }

    }
Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50
1

Your service is fine and it's on the loginModule but you are not using it anywhere where i can see. You need to inject your service into controller to do stuff you want. In your authService you are getting item from localstorage but you are not returning anything for example you have your login service

function authService(auth) {
  var self = this;
  self.isAuthed = function() {       
   return localStorage.getItem('token');
  }
}
 //here you can inject your auth service to get it work as you want
function LoginCtrl(user, auth) {
  var self = this;

  function handleRequest(res) {
    self.responsedata = res;
    self.message = res.data.message;

    var authToken = res.data.auth_token;
    localStorage.setItem('token', authToken);
  }

  self.login = function() {
    this.requestdata = 'Starting request...';
    user.login(self.username, self.pwd, self)
      .then(handleRequest, handleRequest)
  }
}
login.service('auth', authService)
Jorawar Singh
  • 7,463
  • 4
  • 26
  • 39
1
function authService(auth) {
var self = this;
self.isAuthed = function() {
**localStorage.getItem('token');**
}  
}

Where are you getting the localstorage item into? The LValue is missing.

At the most basic level, you could handle a check for this item - token - in the Dashboard page, at the time of loading the page and if it is null ie. empty, then redirect/route the user to the login page. Btw, use the sessionStorage rather than the localStorage as the former will flush as soon as the browser session is closed.

There are more elegant and simpler ways of accomplishing it like Passport. Have you checked it? It is as simple as this:

app.post('/login', passport.authenticate('local', { successRedirect: '/',
failureRedirect:'/login'}));
user2347763
  • 469
  • 2
  • 10
  • Thank you for pointing out the sessionStorage. But as far as Passport JS it looks like that is for a node.js server. My company isn't using node. – agon024 Aug 03 '16 at 16:05
1

Your code isn't checking on url changes or affecting routes in a cross-cutting way.

Remember that authentication and authorization are cross-cutting concerns. That being said, Angular has a way for you to intercept routing calls by listening on $routeChangeStart. Your "interceptor" should be added there. You can then redirect the router to the required view by manually routing there. Have a look as the solution from a previous stack overflow thread.

Community
  • 1
  • 1
1

There is a simple way you can achieve what you want for your application, using PassportJs.

The documentation is pretty simple and easy to implement.

You can also refer this tutorial to implement authentication using Passport. This tutorial teaches in very simple way, how to do authentication for your application.

Ravi Shankar Bharti
  • 8,922
  • 5
  • 28
  • 52
0

Simple way to do that is just use https://github.com/Emallates/ng-enoa-auth package. You just need to include it in your app, nothing else.

9me
  • 1,078
  • 10
  • 36