Manejo de tokens de actualización usando rxjs

Desde que comencé con angular2 configuré mis servicios para regresar a Observable of T. En el servicio, tendría la llamada a map (), y los componentes que usan estos servicios simplemente usarían subscribe () para esperar la respuesta. Para estos escenarios simples, realmente no necesitaba profundizar en rxjs, así que todo estaba bien.

Ahora quiero lograr lo siguiente: estoy usando autenticación Oauth2 con tokens de actualización. Quiero construir un servicio de API que usarán todos los demás servicios, y que tratará de forma transparente el token de actualización cuando se devuelva un error 401. Entonces, en el caso de un 401, primero obtengo un nuevo token del punto final OAuth2 y luego vuelvo a intentar mi solicitud con el nuevo token. A continuación se muestra el código que funciona bien, con promesas:

request(url: string, request: RequestOptionsArgs): Promise { var me = this; request.headers = request.headers || new Headers(); var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://'); if (isSecureCall === true) { me.authService.setAuthorizationHeader(request.headers); } request.headers.append('Content-Type', 'application/json'); request.headers.append('Accept', 'application/json'); return this.http.request(url, request).toPromise() .catch(initialError => { if (initialError && initialError.status === 401 && isSecureCall === true) { // token might be expired, try to refresh token. return me.authService.refreshAuthentication().then((authenticationResult:AuthenticationResult) => { if (authenticationResult.IsAuthenticated == true) { // retry with new token me.authService.setAuthorizationHeader(request.headers); return this.http.request(url, request).toPromise(); } return Promise.reject(initialError); }); } else { return Promise.reject(initialError); } }); } 

En el código anterior, authService.refreshAuthentication () buscará el nuevo token y lo almacenará en localStorage. authService.setAuthorizationHeader configurará el encabezado ‘Autorización’ para el token actualizado previamente. Si observa el método catch, verá que devuelve una promesa (para el token de actualización) que a su vez devolverá otra promesa (para el segundo bash real de la solicitud).

Intenté hacer esto sin recurrir a promesas:

 request(url: string, request: RequestOptionsArgs): Observable { var me = this; request.headers = request.headers || new Headers(); var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://'); if (isSecureCall === true) { me.authService.setAuthorizationHeader(request.headers); } request.headers.append('Content-Type', 'application/json'); request.headers.append('Accept', 'application/json'); return this.http.request(url, request) .catch(initialError => { if (initialError && initialError.status === 401 && isSecureCall === true) { // token might be expired, try to refresh token return me.authService.refreshAuthenticationObservable().map((authenticationResult:AuthenticationResult) => { if (authenticationResult.IsAuthenticated == true) { // retry with new token me.authService.setAuthorizationHeader(request.headers); return this.http.request(url, request); } return Observable.throw(initialError); }); } else { return Observable.throw(initialError); } }); } 

El código anterior no hace lo que esperaba: en el caso de una respuesta 200, devuelve la respuesta correctamente. Sin embargo, si captura el 401, recuperará con éxito el nuevo token, pero el subscriptor eventualmente recuperará un observable en lugar de la respuesta. Supongo que este es el Observable no ejecutado que debería hacer el rebash.

Me doy cuenta de que traducir la prometedora forma de trabajar en la biblioteca de rxjs probablemente no sea la mejor opción, pero no he podido entender el tema “todo es una secuencia”. He intentado algunas otras soluciones que implican flatmap, reintentar cuando etc … pero no llegué lejos, por lo que se agradece algo de ayuda.

De un vistazo rápido a su código, diría que su problema parece ser que no está nivelando el Observable que se devuelve desde el servicio de refresh .

El operador de catch espera que devuelva un Observable que concatenará al final del Observable fallido para que el Observer flujo descendente no conozca la diferencia.

En el caso que no sea 401, usted está haciendo esto correctamente al devolver un Observable que vuelve a generar el error inicial. Sin embargo, en el caso de actualización, está devolviendo un Observable produce más Observables lugar de valores únicos.

Sugeriría que cambies la lógica de actualización para que sea:

  return me.authService .refreshAuthenticationObservable() //Use flatMap instead of map .flatMap((authenticationResult:AuthenticationResult) => { if (authenticationResult.IsAuthenticated == true) { // retry with new token me.authService.setAuthorizationHeader(request.headers); return this.http.request(url, request); } return Observable.throw(initialError); }); 

flatMap convertirá los Observables intermedios en una sola transmisión.

En la última versión de RxJs, el operador de flatMap ha sido renombrado a mergeMap .

Creé esta demostración para descubrir cómo manejar el token de actualización usando rxjs. Hace esto:

  • Realiza una llamada API con token de acceso.
  • Si el token de acceso expiró (observable arroja el error apropiado), realiza otra llamada asincrónica para actualizar el token.
  • Una vez que el token se haya actualizado, volverá a intentar la llamada API.
  • Si aún error, darse por vencido.

Esta demostración no realiza llamadas HTTP reales (las simula utilizando Observable.create ).

En su lugar, catchError para aprender a usar catchError y catchError retry operadores para solucionar un problema (el token de acceso falló la primera vez), luego vuelva a intentar la operación fallida (la llamada API).