How to refresh data after refresh token refreshes jwt
Asked Answered
P

3

8

I've been trying to get my refresh token to work for a while now, and I hope I'm close. My token refreshes and triggers a subsequent 200 call to whatever call caused the 401, but my the data on my page doesn't refresh.

When an access token expires, the following happens:

enter image description here

After the 401, the GetListofCompanyNames returns 200 with a list of names using the correct updated access token. However, my dropdown does not refresh.

My interceptor:

  app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
        return {
            request: function(config) {

                config.headers = config.headers || {};

                var authData = localStorageService.get('authorizationData');
                if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                }
                return config;
            },
            responseError: function(rejection) {
                //var promise = $q.reject(rejection);
                var authService = $injector.get('authService');
                if (rejection.status === 401) {

                    // refresh the token
                    authService.refreshToken().then(function() {
                        // retry the request
                        var $http = $injector.get('$http');
                        return $http(rejection.config);
                    });
                }
                if (rejection.status === 400) {
                    authService.logOut();
                    $location.path('/login');
                }
                return $q.reject(rejection);
            }
        };
    }
]);

My return statement on the 401 rejection looks suspect here, but I'm not sure what to replace it with. Thereby my question is: How can I get my page to refresh it's data when I make the new call?

Update:

This gets me past when the 200 returns and I can get a dropdown to refresh, but I lose any state on the page (ex. selected dropdown) with the below.

authService.refreshToken().then(function() {
var $state = $injector.get('$state');
$state.reload();
});

Back to the drawing board!

Pinkham answered 12/5, 2017 at 21:21 Comment(9)
Do you use a angularjs router system ?Mcwherter
Yep, ui-router : github.com/angular-ui/ui-routerPinkham
Did you try to inject the $state service in your authInterceptorService and use $state.reload() ?Mcwherter
@Julien could it have been as simple as that? Looks like nixing the return statement and doing a state.reload might've done the trick. I've been stuck on this for a bit, I'll run with it as is with the state.reload in there. Once, I'm comfortable that it works (I don't see why it wouldn't), I'd be happy to accept your suggestion as the answer!Pinkham
@Julien looks like it was too good to be true, I'm able to refresh my dropdown when it's idle using state.reload(), but I then lose the dropdown in the workflow of the page in subsequent 401 based reloads and the workflow needs to be restarted.Pinkham
Damn, you're right, what a pity :/. At least you can turn your $location.path(...) to a $state.go('login', {message: 'You have been disconnected, please reconnect'}) to pass a message in a hidden route param. Concerning, the conservation of your workflow state, I do not see easy solution to it.Mcwherter
I made a plnk to reproduce it. I will try a few uiRouter options in it.Mcwherter
I wonder if the solution lies more along the lines of using deferred: github.com/tjoudeh/AngularJSAuthentication/blob/master/…. I've got that code in now and it seems to be behaving, I'll keep testing it however. My auth service also looks very similar to: github.com/tjoudeh/AngularJSAuthentication/blob/master/…Pinkham
where do you keep the data of the dropdown and what do you display in the dropdown?Byte
P
0

My final solution:

app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
        var $http;
        var retryHttpRequest = function(config, deferred) {
            $http = $http || $injector.get('$http');
            $http(config).then(function(response) {
                    deferred.resolve(response);
                },
                function(response) {
                    deferred.reject(response);
                });
        }

        return {
            request: function(config) {

                config.headers = config.headers || {};

                var authData = localStorageService.get('authorizationData');
                if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                }
                return config;
            },
            responseError: function(rejection) {
                var deferred = $q.defer();
                if (rejection.status === 401) {
                    var authService = $injector.get('authService');
                    authService.refreshToken().then(function() {
                            retryHttpRequest(rejection.config, deferred);
                        },
                        function () {
                            authService.logOut();
                            $location.path('/login');
                            deferred.reject(rejection);
                        });
                } else {
                    deferred.reject(rejection);
                }
                return deferred.promise;
            }
        };
    }
]);

Copied almost 1 for 1 from https://github.com/tjoudeh/AngularJSAuthentication/blob/master/AngularJSAuthentication.Web/app/services/authInterceptorService.js .

This one transparently handles all requests and refreshes them when necessary. It logs out users when the refresh token is expired and passes errors along to the controllers by properly rejecting them. However, it doesn't seem to work with multiple in flight requests, I'll look into that when I get a use case for it in my system.

Pinkham answered 22/5, 2017 at 14:47 Comment(0)
T
2

Try putting up your retry call in $timeout, it should work.

Here's the updated code:

app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
        return {
            request: function(config) {

                config.headers = config.headers || {};

                var authData = localStorageService.get('authorizationData');
                if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                }
                return config;
            },
            responseError: function(rejection) {
                //var promise = $q.reject(rejection);
                var authService = $injector.get('authService');
                if (rejection.status === 401) {

                    // refresh the token
                    authService.refreshToken().then(function() {
                        // retry the request
                  return $timeout(function() {
                        var $http = $injector.get('$http');
                        return $http(rejection.config);
                    }});
                }
                if (rejection.status === 400) {
                    authService.logOut();
                    $location.path('/login');
                }
                return $q.reject(rejection);
            }
        };
    }
]);

$timeout returns a promise that is completed with what is returned from the function parameter, so we can conveniently just return the $http call wrapped in $timeout.

Thanks.

Thrippence answered 18/5, 2017 at 12:25 Comment(0)
S
1

I think you may want to change up how you go about this. One way to go about this would be to inject the $rootScope into your authInterceptorService and then once you successfully refresh the token, call something like $rootScope.broadcast('tokenRefreshed').

I don't quite know how you have set up the view and controller that handles your dropdown, but I would set up a listener for that 'tokenRefreshed' event. From here, you can do another call to GetListofCompanyNames. If you do it this way you can easily control and ensure that the model gets updated.

Stoichiometric answered 17/5, 2017 at 15:1 Comment(0)
P
0

My final solution:

app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
        var $http;
        var retryHttpRequest = function(config, deferred) {
            $http = $http || $injector.get('$http');
            $http(config).then(function(response) {
                    deferred.resolve(response);
                },
                function(response) {
                    deferred.reject(response);
                });
        }

        return {
            request: function(config) {

                config.headers = config.headers || {};

                var authData = localStorageService.get('authorizationData');
                if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                }
                return config;
            },
            responseError: function(rejection) {
                var deferred = $q.defer();
                if (rejection.status === 401) {
                    var authService = $injector.get('authService');
                    authService.refreshToken().then(function() {
                            retryHttpRequest(rejection.config, deferred);
                        },
                        function () {
                            authService.logOut();
                            $location.path('/login');
                            deferred.reject(rejection);
                        });
                } else {
                    deferred.reject(rejection);
                }
                return deferred.promise;
            }
        };
    }
]);

Copied almost 1 for 1 from https://github.com/tjoudeh/AngularJSAuthentication/blob/master/AngularJSAuthentication.Web/app/services/authInterceptorService.js .

This one transparently handles all requests and refreshes them when necessary. It logs out users when the refresh token is expired and passes errors along to the controllers by properly rejecting them. However, it doesn't seem to work with multiple in flight requests, I'll look into that when I get a use case for it in my system.

Pinkham answered 22/5, 2017 at 14:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.