2

I am using token based authentication in my application. My backend is developed using restful service(spring).The backend code is very well generating the required the access token and refresh tokens with timelines, So I have overidden the http class with following:

export class customHttp extends Http {
   headers: Headers = new Headers({ 'Something': 'Something' });
    options1: RequestOptions = new RequestOptions({ headers: this.headers });
    private refreshTokenUrl = AppSettings.REFRESH_TOKEN_URL;
    constructor(backend: ConnectionBackend,
        defaultOptions: RequestOptions,private refresh:OauthTokenService) {
        super(backend, defaultOptions);
    }
    request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    console.log("custom http ");
        return super.request(url, options)
            .catch((err) => {
                if (err.status === 401) {
                    console.log(" custome http 401 ");
                    //   refresh the token
                    this.refresh.refresh().subscribe((tokenObj)=>{
                              console.log("tokenobj ");
                    })
                 } else {
                    console.log("err " + err);
                }
            }); } } 

I am getting stuck in refreshing the token at refresh() method as I am getting cyclic dependency error so I tried to use refresh service in another module but no luck. I am using the same approach as mentioned in this Handling refresh tokens using rxjs Any help would be great!

Community
  • 1
  • 1
Ali
  • 97
  • 2
  • 8

3 Answers3

6

This is what worked for me:

 request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    //adding access token to each http request before calling super(..,..)
    let token = this.authenticationService.token;
    if (typeof url === 'string') {
        if (!options) {
            options = { headers: new Headers() };
        }
        options.headers.set('Authorization', `Bearer ${token}`);
    }
    else {
        url.headers.set('Authorization', `Bearer ${token}`);
    }
    return super.request(url, options)
      .catch((error) => {
            //if got authorization error - try to update access token
            if (error.status = 401) {
                return this.authenticationService.updateToken()
                    .flatMap((result: boolean) => {
                        //if got new access token - retry request
                        if (result) {
                            return this.request(url, options);
                        }
                        //otherwise - throw error
                        else {
                            return Observable.throw(new Error('Can\'t refresh the token'));
                        }

                    })
            }
            else {
                Observable.throw(error);
            }
        })
}

UPDATE: authenticationService.updateToken() implementation should depend on authorization provider/authorization mechanism you use. In my case it is OAuth Athorization Server, so implementation basically sends post request with refresh token in the body to configured token url and returns updated access and refresh tokens. tokenEndPointUrl is configured by OAuth and issues access and refresh tokens (depending on grant_type sent). Because i need to refresh token i set grant_type to refresh_token. Code looks similar to:

updateToken(): Observable<boolean> {
    let body: string = 'refresh_token=' + this.refreshToken + '&grant_type=refresh_token';

    return this.http.post(tokenEndPointUrl, body, this.options)
        .map((response: Response) => {
            var returnedBody: any = response.json();
            if (typeof returnedBody.access_token !== 'undefined'){
              localStorage.setItem(this.tokenKey, returnedBody.access_token);
              localStorage.setItem(this.refreshTokenKey, returnedBody.refresh_token);
            return true;
        }
        else {
            return false;
        }
        })
}

Hope it helps

Dragonfly
  • 189
  • 2
  • 4
  • 1
    but a 401 doesn't necessarily mean invalid token, the token is valid but not for that request, this means the 401 will continue to fire, and you would go into a loop to re-create the refresh token... am I correct? – Ayyash May 09 '17 at 11:21
  • I guess it is a confusion between authentication and authorization here. 401 UNAUTHORIZED - The request has not been applied because it lacks valid authentication credentials for the target resource. 403 FORBIDDEN - The server understood the request but refuses to authorize it. So if you get 401 from server - your credentials are stale. If you received 403 - server authenticated you but wasn't able to authorize your access because of lack of rights. So 403 is more about your privileges in the system than about your token. You will never get access using token until you have rights. – Dragonfly May 10 '17 at 16:08
  • Of course it depends on your server authorization provider, but ideally it should be 401 - stale token and 403 - lack of permissions. – Dragonfly May 10 '17 at 16:08
1

For those who get to this page and don't understand a thing from it.

The easy to understand:

Create your refresh token method: //retrieve the refresh token somehow

refreshToken(){

    let refreshToken = sessionStorage.getItem('refresh_token');

    let body = 'grant_type=refresh_token&refresh_token=' + refreshToken;

    var headers = new Headers();
    headers.append('Content-Type', 'application/x-www-form-urlencoded');
    headers.append('Authorization','Basic ' + btoa('something:something'));

    return this.http.post('your auth url',body,{headers: headers})

  }

Than in your http request (mine uses the angular-jwt authHttp instead of http)

testService(){
    this.authHttp.get('your secured service url')
    .map(res => {
            return res;
        })
    .catch(error=> {
            if (error.status === 401) {
                return this.refreshToken().flatMap((newToken) => {
                  let newAccessToken = newToken.json();
                  sessionStorage.setItem('id_token', newAccessToken['access_token']);
                  sessionStorage.setItem('refresh_token', newAccessToken['refresh_token']);

                  return this.authHttp.request('your secured service url');
                })
            } else {
                return Observable.throw(error);
            }
        })
    .subscribe(res => console.log(res));

  }

Do not forget to import what you need like:

import { AuthHttp } from 'angular2-jwt';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

Do not subscribe in your refresh token method. If you do you will see a big error in the flatmap of the service call method.

Avram Virgil
  • 1,170
  • 11
  • 22
  • Hi, won't this cause multiple refresh requests if multiple https request were sent in close proximity or will flatmap prevent that. The reason am asking is because i have a similar setup, but i am using subscribe instead of flatmap. While refresh is successful, the user experience was awful as it ends up redirecting to signin, then back. I would like to adopt your solution. Thanks –  Sep 27 '17 at 12:45
0

thanks for replying @dragonfly, this worked for me

    post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
        //check if the token is expired then set the latest token
                if (this.isTokenExpired) {
                    options.headers.set('Authorization', 'Bearer ' + localStorage.getItem("accessToken"));
   }

  return super.post(url, body, options)
         .catch((err) => {
  //if authentication error
      if (err.status === 401) {
       this.isTokenExpired = true;
      options.headers.set('Authorization', 'Bearer ' + localStorage.getItem("accessToken"));
    //refresh the token
 let refreshUrl = this.refreshTokenUrl;
    //pass the refresh token 
 refreshUrl = refreshUrl.replace(':refreshToken', localStorage.getItem("refreshToken"));
 //rest the access token
  return super.get(refreshUrl).mergeMap((tokenObj) => {
  localStorage.setItem("accessToken", tokenObj.json().value);
   // reset the headers
    options.headers.set('Authorization', 'Bearer ' + localStorage.getItem("accessToken"));
//retry the request with the new access token
return this.post(url, body, options)
 })
  .catch((refreshErr) => {
    if (refreshErr.status == 400) {
           console.log("refesh err");
    window.location.href = 'request=logout';
   }
 return Observable.throw(refreshErr);
  })
  } else {
  return err;
  }
 })
}

Can you tell how are you updating the token(this.authenticationService.updateToken())?

Ali
  • 97
  • 2
  • 8