Handling update tokens using rxjs

Since I started with angular2, I had the settings of my services to return Observable of T. In the service, I will have a map () call, and the components using these services will just use subscribe () to wait for an answer. For these simple scenarios, I really don't need to delve into rxjs, so everything was fine.

Now I want to achieve the following: I use Oauth2 authentication with update tokens. I want to create an api service that all other services will use and which will transparently handle the update token when error 401 is returned. So, in the case of 401, I first retrieve the new token from the OAuth2 endpoint and then repeat my request with the new token . Below is the code that works great with promises:

request(url: string, request: RequestOptionsArgs): Promise<Response> { 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 <any>Promise.reject(initialError); }); } else { return <any>Promise.reject(initialError); } }); } 

In the above code, authService.refreshAuthentication () will select a new token and store it in localStorage. authService.setAuthorizationHeader will set the Authorization header to a previously updated token. If you look at the catch method, you will see that it returns a promise (for the update token), which in turn will return another promise (for the actual 2nd request attempt).

I tried to do this without resorting to promises:

 request(url: string, request: RequestOptionsArgs): Observable<Response> { 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); } }); } 

The code above does not fulfill what I expect: in the case of a 200 response, it correctly returns the answer. However, if he catches 401, he will successfully extract the new token, but the wil subscription will eventually receive the observable instead of the answer. I guess this is an unexplored Observable that should try again.

I understand that translating the promised way to work with the rxjs library is probably not the best way, but I could not understand that "this is all a stream." I tried several other solutions, including flatmap, retryWhen, etc., but didn’t get far, so some help to evaluate.

+38
angular rxjs
Jan 20 '16 at
source share
2 answers

From a quick overview of your code, I would say that your problem is that you are not smoothing the Observable that returns from the refresh service.

The catch statement expects you to return the Observable , which it will connect at the end of the failed Observable so that the Observer downstream does not know the difference.

In the case of non-401, you are doing it right by returning an Observable that restarts the initial error. However, in the updated case, you return Observable , and instead of single values, more Observables .

I would suggest you change the update logic:

  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 converts the intermediate Observables into a single thread.

+22
Jan 21 '16 at 7:18
source share

In the latest version of RxJs, the flatMap operator was renamed to mergeMap .

+9
Sep 27 '16 at 9:27
source share



All Articles