Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2: oauth2 with token headers

I'm new to angular2. In 1.* everything was fine with interceptors, just add them: and you have everywhere your headers, and you can handle your requests, when token became invalid...

In angular2 i'm using RxJs. So i get my token:

  getToken(login: string, pwd: string): Observable<boolean> {

    let bodyParams = {
      grant_type: 'password',
      client_id: 'admin',
      scope: AppConst.CLIENT_SCOPE,
      username: login,
      password: pwd
    };

    let params = new URLSearchParams();
    for (let key in bodyParams) {
      params.set(key, bodyParams[key])
    }

    let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
    let options = new RequestOptions({headers: headers});

    return this.http.post(AppConst.IDENTITY_BASE_URI + '/connect/token', params.toString(), options)
      .map((response: Response) => {
        let data = response.json();

        if (data) {
          this.data = data;
          localStorage.setItem('auth', JSON.stringify({
            access_token: data.access_token,
            refresh_token: data.refresh_token
          }));
          return true;
        } else {
          return false;
        }
      });
  }

and then how can i use this token in every request? i don't want to set .header in every request. It's a bad practice.

And then: for example when i do any request, and get 401-error, how can i intercept, and get a new token, and then resume all requests, like it was in angular 1?

i tried to use JWT from here jwt, but it doesn't meet my requirements, btw in first angular i was using Restangular - and everything was fine there (also with manual on tokens:https://github.com/mgonto/restangular#seterrorinterceptor)

like image 841
brabertaser19 Avatar asked Mar 13 '17 19:03

brabertaser19


1 Answers

You can either extend the default http service and use the extended version, or you could create a method that gets some parameters (if necessary) and return a RequestOptions objects to pass default http service.

Option 1

You can create a service:

@Injectable()
export class HttpUtils {
  constructor(private _cookieService: CookieService) { }

  public optionsWithAuth(method: RequestMethod, searchParams?: URLSearchParams): RequestOptionsArgs {
    let headers = new Headers();
    let token = 'fancyToken';
    if (token) {
      headers.append('Auth', token);
    }
    return this.options(method, searchParams, headers);
  }

  public options(method: RequestMethod, searchParams?: URLSearchParams, header?: Headers): RequestOptionsArgs {
    let headers = header || new Headers();
    if (!headers.has('Content-Type')) {
      headers.append('Content-Type', 'application/json');
    }
    let options = new RequestOptions({headers: headers});
    if (method === RequestMethod.Get || method === RequestMethod.Delete) {
      options.body = '';
    }
    if (searchParams) {
      options.params = searchParams;
    }
    return options;
  }

  public handleError(error: Response) {
    return (res: Response) => {
      if (res.status === 401) {
        // do something
      }
      return Observable.throw(res);
    };
  }
}

Usage example:

this._http
  .get('/api/customers', this._httpUtils.optionsWithAuth(RequestMethod.Get))
  .map(res => <Customer[]>res.json())
  .catch(err => this._httpUtils.handleError(err));

This example is using cookies to store and access the token. You could use a parameter as well.


Option 2

Second option is to extend http service, for example like this:

import { Injectable } from '@angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class MyHttp extends Http {

  constructor (backend: XHRBackend, options: RequestOptions) {
    let token = 'fancyToken';
    options.headers.set('Auth', token);
    super(backend, options);
  }

  request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
    let token = 'fancyToken';
    if (typeof url === 'string') {
      if (!options) {
        options = {headers: new Headers()};
      }
      options.headers.append('Auth', token);
    } else {
      url.headers.append('Auth', token);
    }
    return super.request(url, options).catch(this.handleError(this));
  }

  private handleError (self: MyHttp) {
    return (res: Response) => {
      if (res.status === 401) {
        // do something
      }
      return Observable.throw(res);
    };
  }
}

And in your @NgModule:

@NgModule({
  // other stuff ...
  providers: [
    {
      provide: MyHttp,
      useFactory: (backend: XHRBackend, options: RequestOptions) => {
        return new MyHttp(backend, options);
      },
      deps: [XHRBackend, RequestOptions]
    }
  ]
  // a little bit more other stuff ...
})

Usage:

@Injectable()
class CustomerService {

  constructor(private _http: MyHttp) {
  }

  query(): Observable<Customer[]> {
    return this._http
      .get('/api/customers')
      .map(res => <Customer[]>res.json())
      .catch(err => console.log('error', err));
  }
}

Extra:

If you want to use refresh token to obtain a new token you can do something like this:

private handleError (self: MyHttp, url?: string|Request, options?: RequestOptionsArgs) {
  return (res: Response) => {
    if (res.status === 401 || res.status === 403) {
      let refreshToken:string = 'fancyRefreshToken';
      let body:any = JSON.stringify({refreshToken: refreshToken});
      return super.post('/api/token/refresh', body)
        .map(res => {
          // set new token
        }) 
        .catch(err => Observable.throw(err))
        .subscribe(res => this.request(url, options), err => Observable.throw(err));
    }
    return Observable.throw(res);
  };
}

To be honest, I haven't tested this, but it could provide you at least a starting point.

like image 162
s.alem Avatar answered Oct 06 '22 23:10

s.alem