Search code examples
angularjspassport.jsjwtexpress-jwt

Authentication: forced to refresh page to be authorised


I'm using the code from this blog to make an authentication module for my angular app.

I have a test page that includes a login form, a submit button and a "profile" button that will query a restricted route.

<div class="container-fluid" ng-controller="AuthCtrl as auth">
    <input type="text" ng-model="auth.user.username">
    <input type="text" ng-model="auth.user.password">
    <button ng-click="auth.login(auth.user)">LOGIN</button>
    <button ng-click="auth.profile()">PROFILE</button>
</div>

The restricted route I'm querying after login is defined as:

const authenticate = expressJwt({
  secret: SECRET
});
app.get('/me', authenticate, function(req, res) {
  res.status(200).json(req.user);
});

The login works fine. I receive a token that I put in sessionStorage. When I click on the profile button (request to /me) I get an unauthorized error.

If I refresh the page and click again on profile I do get the excpected behaviour. (/me returns user data with no error)

If I delete the token manually after that, I still have access to /me until I refresh the page.

This is my service:

function loginService($http) {
    this.login = function(user) {
        return $http.post('/auth', user).then(
            function(response) {
                return response.data;
            },
            function(response) {
                return response;
            });
    };
    this.profile = function() {
        return $http.get('/me').then(
            function(response) {
                return response.data;
            },
            function(response) {
                return response;
            });
    };
}

angular
    .module('app')
    .service('loginService', loginService);

And this is my controller with the httpProvider:

function AuthCtrl($window, $http, loginService) {
    this.user = {username: "", password: ""};
    this.login = function(user) {
        loginService.login(user).then(function(data) {
            $window.sessionStorage.token = data.token;
        });
    };
    this.profile = function() {
        loginService.profile().then(function(data) {
            console.log(data);
        });
    };
}

function config($httpProvider, $windowProvider) {
    var window = $windowProvider.$get();
    if(window.sessionStorage.token) {
        var token =  window.sessionStorage.token;
        $httpProvider.defaults.headers.common.Authorization = 'Bearer ' + token;
    }
};

angular
    .module('app')
    .config(config)
    .controller('AuthCtrl', AuthCtrl);

Could the problem come from storing the token in sessionStorage or from the http provider?

Eventually I will implement the secure cookie method but I'd like to get this one solved before proceding further.


Solution

  • I finally figured out it was coming for the $httpProvider code.

    function config($httpProvider, $windowProvider) {
        var window = $windowProvider.$get();
        if(window.sessionStorage.token) {
            var token =  window.sessionStorage.token;
            $httpProvider.defaults.headers.common.Authorization = 'Bearer ' + token;
        }
    };
    

    The provider options wer set only once during app load. Reloading the page would rerun the config code and enter the if(window.sessionStorage.token) condition as expected.

    To make this config dynamic, I had to create an interceptor (factory) like so:

    function config($httpProvider) {
        $httpProvider.interceptors.push('authInterceptor');
    };
    
    function authInterceptor($rootScope, $q, $window) {
        return {
            request: function (config) {
                config.headers = config.headers || {};
                if ($window.sessionStorage.token) {
                    config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
                }
                return config;
            },
            responseError: function (rejection) {
                if (rejection.status === 401) {
                    console.log("not authorised");
                }
                return $q.reject(rejection);
            }
        };
    };
    
    angular
        .module('app')
        .config(config)
        .controller('AuthCtrl', AuthCtrl)
        .factory('authInterceptor', authInterceptor);