Hmorv
Hmorv

Reputation: 146

How to refresh expired jwt token in AngularJS

I'm developing an Angular webapp which uses an API in order to get some data. To get that data, I must send a JWT. I created a service that calls to a PHP file in order to get the token:

(function () {
'use strict';

angular
.module('MyApp')
.factory('TokenService', Service);

function Service( $http, $localStorage ) {

    var service = {};

    service.getToken = getToken;

    return service;

    function getToken() {
        return $http.post('gettoken.php');
    }
}})();

And what I do is in .run block I call it and store the token received:

function run( $rootScope, $http, $location, $localStorage, TokenService ) {
    TokenService.getToken().then(function(response) {
        $localStorage.token = response.data.token;
    });
    console.log('token: ' + $localStorage.token);
    $http.defaults.headers.common.Authorization = 'Bearer ' + $localStorage.token;
}})();

Every few minutes the token expires, so I must request for another token so the user can continue interacting with the API.

To solve this, my first approach was to use .config block to intercept 401 error (token expired) and ask for another token, but this is not possible because as far as I know, is not possible to inject services in .config block:

function config2( $httpProvider ) {
    $httpProvider.interceptors.push(function ($q, $rootScope) {
        return {
            'response': function (response) {
            //Will only be called for HTTP up to 300
            console.log(response);
            return response;
        },
        'responseError': function (rejection) {
            if(rejection.status === 401) {
                var deferred = $q.defer();
                TokenService.getToken(function() {
                    retryHttpRequest(response.config, deferred);
                });
                return deferred.promise;
            } else {
                return response;
            }
            function retryHttpRequest(config, deferred){
              function successCallback(response){
                deferred.resolve(response);
              }
              function errorCallback(response){
                deferred.reject(response);
              }
              var $http = $injector.get('$http');
              $http(config).then(successCallback, errorCallback);
            }
        }
    };
});
}

Indeed, when I execute the webapp, it says that TokenService is not defined, and if I inject the TokenService, obviously it creates a circle dependancy:

    angular.js:66 Uncaught Error: [$injector:cdep] Circular dependency found: $http <- TokenService <- $http <- $templateRequest <- $route
http://errors.angularjs.org/1.6.4/$injector/cdep?p0=%24http%20%3C-%20TokenService%20%3C-%20%24http%20%3C-%20%24templateRequest%20%3C-%20%24route
    at angular.js:66
    at getService (angular.js:4936)
    at injectionArgs (angular.js:4969)
    at Object.invoke (angular.js:4995)
    at Object.enforcedReturnValue [as $get] (angular.js:4836)
    at Object.invoke (angular.js:5003)
    at angular.js:4795
    at getService (angular.js:4944)
    at injectionArgs (angular.js:4969)
    at Object.invoke (angular.js:4995)

That's a very silly situation, but I've spent the whole day trying to find an optimal solution. I tried to simply reload page once the interceptor detects 401 responseError, but this is not optimal, because the user sees the web refreshing and he loses all data inserted for the request is doing.

It would be great if any of you have had to deal with this kind of problem and can share any approach in order to solve it.

Upvotes: 0

Views: 1513

Answers (1)

Nikolaj Dam Larsen
Nikolaj Dam Larsen

Reputation: 5674

This may be because the $http service depends on your interceptor, your TokenService depends on the $http service, and your interceptor depends on the TokenService. This causes a circular dependency.

It seems you're already using the $injector to inject the $http-service just-in-time, so to solve your circular dependency, using the same method to inject your TokenService might solve your problem.

var TokenService = $injector.get('TokenService');

I've stumbled upon the same problem before, so if it's any help you can check my solution on GitHub.

Upvotes: 2

Related Questions