2017-08-10 3 views
1

Nous développons l'application Spring avec frontend React/Redux. Nous l'avons intégré avec succès au service d'authentification Keycloak. Cependant, nous avons rencontré un comportement indésirable après l'expiration du jeton d'accès. Notre restMiddleware ressemble à ceci (simplifié):Couvre-clé | Ne peut pas attendre sur updateToken() dans la fonction async

function restMiddleware() { 
    return (next) => async (action) => { 
     try{ 

      await keycloak.updateToken(5); 

      res = await fetch(restCall.url, { 
       ...restCall.options, ...{ 
        credentials: 'same-origin', 
        headers: { 
         Authorization: 'Bearer ' + keycloak.token 
        } 
       } 
      }); 

     }catch(e){} 
    } 

Le problème est que, après expiration du jeton et updateToken() est exécutée, la fonction async ne s'arrête pas et invoque fetch() immédiatement, avant nouveau jeton d'accès est reçu. Cela empêche bien sûr la requête d'extraction de réussir et provoque une réponse avec le code 401. updateToken() renvoie une promesse, donc je ne vois aucune raison pour laquelle l'attente ne fonctionnerait pas dessus, ce qui est certainement le cas. J'ai confirmé que la fonction dans updateToken().success(*function*) s'exécutera après un rafraîchissement de jeton réussi et que le placement de fetch() à l'intérieur résoudrait le problème, mais à cause de notre construction de middleware, je ne peux pas le faire. J'ai développé cette solution de contournement:

function refreshToken(minValidity) { 

     return new Promise((resolve, reject) => { 

      keycloak.updateToken(minValidity).success(function() { 
       resolve() 
      }).error(function() { 
       reject() 
      }); 
     }); 
    } 

    function restMiddleware() { 
    return (next) => async (action) => { 
     try{ 
      await refreshToken(5); 

      res = await fetch(restCall.url, { 
       ...restCall.options, ...{ 
        credentials: 'same-origin', 
        headers: { 
         Authorization: 'Bearer ' + keycloak.token 
        } 
       } 
      }); 

     }catch(e){} 
    } 

Et il fonctionne, mais il est pas élégant.

La question est: pourquoi la première solution n'a pas fonctionné? Pourquoi je ne peux pas attendre sur updateToken() et utiliser updateToken(). Success() à la place?

Je suppose que cela pourrait être un bug et de confirmer que c'est le but principal de cette question.

Répondre

0

La documentation de la méthode updateToken indique qu'elle renvoie une promesse, mais elle n'est en réalité pas enregistrable et le mot-clé await ne le considère donc pas comme une promesse valide. C'est comme ça que je le mettrais. :)

Votre solution me semble élégante et j'ai fait exactement la même chose pour continuer correctement sur une chaîne Promise après avoir appelé la fonction updateToken.

Keycloak JavaScript documentation de l'adaptateur: https://keycloak.gitbooks.io/documentation/securing_apps/topics/oidc/javascript-adapter.html

+1

Je viens de regarder dans les keycloak.js, où cette méthode est définie et je peux voir le point. Ce que renvoie updateToken() n'est pas du tout une promesse (à partir de la spécification ECMAScript). C'est un objet personnalisé ne contenant que des fonctions success() et error() que les créateurs de Keycloak ont ​​nommé ... "promettre l'objet" -.- "La méthode updateToken retourne un objet promesse qui permet d'invoquer facilement le service uniquement si le jeton a été actualisé avec succès et par exemple afficher une erreur à l'utilisateur si ce n'était pas. " Il est très déroutant, à mon avis, ils ne devraient pas utiliser cette description. – BJanusz

1

Oui, comme on dit, keycloak.js utilise une mise en œuvre de la promesse Homegrown qui est incompatible avec l'ES6 Promise. Ce que j'ai tendance à faire dans mes projets, c'est un petit assistant comme celui-ci (TypeScript).

export const keycloakPromise = <TSuccess>(keycloakPromise: KeycloakPromise<TSuccess, any>) => new Promise<TSuccess>((resolve, reject) => 
    keycloakPromise 
     .success((result: TSuccess) => resolve(result)) 
     .error((e: any) => reject(e)) 
); 

et de l'utiliser comme

keycloakPromise<KeycloakProfile>(keycloak.loadUserProfile()) 
    .then(userProfile => ...) 
    .catch(() => ...)