2

I have a backend with jwt auth and I want to handle expired tokens.

The following flow is required :

  1. Make a request with token attached (and expect a promise)
  2. If it goes fine, then just return the promise (and the then/fail method of the caller are executed)
  3. If it fails (with 401 unauthorized) then a request is made to refresh the token and token is updated locally
  4. If step 3 is successful, return a promise for original request
  5. If step 3 fails with 401 (token cannot be refreshed) error redirect to login page

Problem : In step 4, the original function is called (again) but the caller's then/fail method are not triggered.

Following is my method to append jwt token to the url and send the http request :

var AuthenticatedRequest = function(url, data, method) {
  return (function tryRequest(){
    console.log('calling tryRequest');
    return reqwest({
        url: ApiUtil.tokenUrlTo(url),
        method: method,
        crossOrigin: true,
        type: 'json',
        data: data
      })
      .fail(function(err) {
        if (err.status === 401) {
          return post('/auth/refresh-token')
            .then(function(response){
              console.log('assume token set');
              //code to update token locally
            })
            .then(tryRequest)
            .fail(function(err){
              // Can't refresh token. Send to login page
            })
          ;      
        }
      })
    ;
  })();
};

And here is the caller :

fetchModules: function() {
    get('/admin/modules')
      .then(function(response) {
        Actions.modulesFetchSuccess(response.collection);
      })
      .fail(function(err) {
        Actions.modulesFetchError(ApiUtil.errorArrayForResponse(err));
      })
    ;
  },

Now if I get a 401 because the token expired, I trigger a new cycle to refresh the token as suggested in this question Restart a promise after fail.

Note : post and get function is just a wrapper around the AuthenticatedRequest function with method set to POST or GET.

The AuthenticatedRequest function returns a promise, and if the token is not expired, this runs fine, however, when the token is expired, I get an error in my console and new token is fetched and the function is called again, screenshot of my console - https://i.stack.imgur.com/hJdId.png

But the then method of fetchModules does not get fired after token is updated. What am I doing wrong ?

Possible Duplicates :

Update September 13, 2015

@Bergi's answer worked when I replaced reqwest.js and used q.js with vanilla ajax as show in this gist

Community
  • 1
  • 1
Shivek Khurana
  • 2,056
  • 1
  • 19
  • 16
  • What promise library are you using, what is `reqwest`? The `fail` method often doesn't chain (e.g. in jQuery) – Bergi Sep 12 '15 at 13:59
  • @Bergi : I'm using [reqwest.js](https://github.com/ded/reqwest) – Shivek Khurana Sep 12 '15 at 15:47
  • Ah, well, that library doesn't seem to support real promises. It does offer a promise-like interface, but it's not working as one might expect. You should use a proper promise library (choose any) and [cast the `reqwest` to one like here](http://stackoverflow.com/a/31327725/1048572). – Bergi Sep 12 '15 at 16:04
  • If you've chosen a lib, I'd be happy to assist in applying it if you still have problems. Please [edit] your question to add the new information. – Bergi Sep 12 '15 at 16:05
  • No, I can change the library if required. – Shivek Khurana Sep 13 '15 at 14:42

1 Answers1

1

The problem is that .fail is always catching your error, not only the first time. Your recursive call to tryRequest will include retries itself, and never return a failed promise.
If you only want one retry, you'll need to put it externally:

function AuthenticatedRequest(url, data, method) {
  function tryRequest() {
    console.log('calling tryRequest');
    return reqwest({
      url: ApiUtil.tokenUrlTo(url),
      method: method,
      crossOrigin: true,
      type: 'json',
      data: data
    });
  }
  return tryRequest().fail(function(err) {
    if (err.status !== 401) throw err;
    return post('/auth/refresh-token')
    .then(function(response) {
      console.log('assume token set');
      // code to update token locally
    })
    .then(tryRequest)
    .fail(function(err) {
      // Can't refresh token. Send to login page
    });
  });
}

Notice that sending the user to another page from the AuthenticatedRequest function might not be a good design, maybe consider just rethrowing the error (after invalidating the token?) and put the redirect and everything in the error handler of the caller.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375