web-dev-qa-db-fra.com

Angular - Définir des en-têtes pour chaque demande

Je dois définir des en-têtes d'autorisation après la connexion de l'utilisateur, pour chaque requête ultérieure.


Pour définir des en-têtes pour une requête particulière, 

import {Headers} from 'angular2/http';
var headers = new Headers();
headers.append(headerName, value);

// HTTP POST using these headers
this.http.post(url, data, {
  headers: headers
})
// do something with the response

Référence

Mais il ne serait pas possible de définir manuellement les en-têtes de demande pour chaque demande de cette manière. 

Comment définir les en-têtes une fois que l'utilisateur est connecté et les supprimer lors de la déconnexion?

271
Avijit Gupta

L'extension de BaseRequestOptions peut être d'une grande aide dans ce scénario. Découvrez le code suivant:

import {provide} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS, Headers, Http, BaseRequestOptions} from 'angular2/http';

import {AppCmp} from './components/app/app';


class MyRequestOptions extends BaseRequestOptions {
  constructor () {
    super();
    this.headers.append('My-Custom-Header','MyCustomHeaderValue');
  }
} 

bootstrap(AppCmp, [
  ROUTER_PROVIDERS,
  HTTP_PROVIDERS,
  provide(RequestOptions, { useClass: MyRequestOptions })
]);

Cela devrait inclure "My-Custom-Header" dans chaque appel.

Mettre à jour:

Pour pouvoir modifier l'en-tête à tout moment, au lieu du code ci-dessus, vous pouvez également utiliser le code suivant pour ajouter un nouvel en-tête:

this.http._defaultOptions.headers.append('Authorization', 'token');

pour supprimer tu peux faire

this.http._defaultOptions.headers.delete('Authorization');

Il existe également une autre fonction que vous pouvez utiliser pour définir la valeur:

this.http._defaultOptions.headers.set('Authorization', 'token');

La solution ci-dessus n'est toujours pas complètement valide dans le contexte TypeScript. _defaultHeaders est protégé et n'est pas censé être utilisé comme ceci. Je recommanderais la solution ci-dessus pour une solution rapide, mais à long terme, il est préférable d'écrire votre propre wrapper autour des appels http, qui gère également l'authentification. Prenons l'exemple suivant de auth0 qui est meilleur et propre.

https://github.com/auth0/angular2-jwt/blob/master/angular2-jwt.ts

Mise à jour - Juin 2018 Je vois beaucoup de gens aller pour cette solution mais je conseillerais le contraire. L'ajout global d'en-tête enverra un jeton d'authentification à à chaque appel api sortant de votre application. Ainsi, les appels api destinés à des plugins tiers tels que intercom, zendesk ou tout autre api porteront également votre en-tête d'autorisation. Cela pourrait entraîner une grave faille de sécurité. Donc, utilisez plutôt interceptor globalement, mais vérifiez manuellement si l'appel sortant est dirigé vers le point de terminaison api de votre serveur ou non, puis joignez un en-tête d'authentification.

76
anit

Bien que je réponde très tard, mais cela pourrait aider quelqu'un d'autre. Pour injecter des en-têtes dans toutes les demandes lorsque @NgModule est utilisé, vous pouvez procéder comme suit:

(J'ai testé cela dans Angular 2.0.1)

/**
 * Extending BaseRequestOptions to inject common headers to all requests.
 */
class CustomRequestOptions extends BaseRequestOptions {
    constructor() {
        super();
        this.headers.append('Authorization', 'my-token');
        this.headers.append('foo', 'bar');
    }
}

Maintenant, dans @NgModule, procédez comme suit:

@NgModule({
    declarations: [FooComponent],
    imports     : [

        // Angular modules
        BrowserModule,
        HttpModule,         // This is required

        /* other modules */
    ],
    providers   : [
        {provide: LocationStrategy, useClass: HashLocationStrategy},
        // This is the main part. We are telling Angular to provide an instance of
        // CustomRequestOptions whenever someone injects RequestOptions
        {provide: RequestOptions, useClass: CustomRequestOptions}
    ],
    bootstrap   : [AppComponent]
})
24
Shashank Agrawal

Dans Angular 2.1.2 j'ai abordé cela en étendant le Http angulaire:

import {Injectable} from "@angular/core";
import {Http, Headers, RequestOptionsArgs, Request, Response, ConnectionBackend, RequestOptions} from "@angular/http";
import {Observable} from 'rxjs/Observable';

@Injectable()
export class HttpClient extends Http {

  constructor(protected _backend: ConnectionBackend, protected _defaultOptions: RequestOptions) {

    super(_backend, _defaultOptions);
  }

  _setCustomHeaders(options?: RequestOptionsArgs):RequestOptionsArgs{
    if(!options) {
      options = new RequestOptions({});
    }
    if(localStorage.getItem("id_token")) {

      if (!options.headers) {

        options.headers = new Headers();


      }
      options.headers.set("Authorization", localStorage.getItem("id_token"))
    }
    return options;
  }


  request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
    options = this._setCustomHeaders(options);
    return super.request(url, options)
  }
}

puis, dans mes fournisseurs d’applications, j’ai pu utiliser une fabrique personnalisée pour fournir un "Http"

import { RequestOptions, Http, XHRBackend} from '@angular/http';
import {HttpClient} from './httpClient';
import { RequestOptions, Http, XHRBackend} from '@angular/http';
import {HttpClient} from './httpClient';//above snippet

function httpClientFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions): Http {
  return new HttpClient(xhrBackend, requestOptions);
}

@NgModule({
  imports:[
    FormsModule,
    BrowserModule,
  ],
  declarations: APP_DECLARATIONS,
  bootstrap:[AppComponent],
  providers:[
     { provide: Http, useFactory: httpClientFactory, deps: [XHRBackend, RequestOptions]}
  ],
})
export class AppModule {
  constructor(){

  }
}

maintenant, je n'ai pas besoin de déclarer chaque méthode HTTP et je peux utiliser http comme d'habitude dans toute mon application.

15
jonnie

Créez une classe Http personnalisée en développant le fournisseur Http angulaire 2 et en substituant simplement les méthodes constructor et request dans votre classe Http personnalisée. L'exemple ci-dessous ajoute l'en-tête Authorization dans chaque demande http.

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 HttpService extends Http {

  constructor (backend: XHRBackend, options: RequestOptions) {
    let token = localStorage.getItem('auth_token'); // your custom token getter function here
    options.headers.set('Authorization', `Bearer ${token}`);
    super(backend, options);
  }

  request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
    let token = localStorage.getItem('auth_token');
    if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
      if (!options) {
        // let's make option object
        options = {headers: new Headers()};
      }
      options.headers.set('Authorization', `Bearer ${token}`);
    } else {
    // we have to add the token to the url object
      url.headers.set('Authorization', `Bearer ${token}`);
    }
    return super.request(url, options).catch(this.catchAuthError(this));
  }

  private catchAuthError (self: HttpService) {
    // we have to pass HttpService's own instance here as `self`
    return (res: Response) => {
      console.log(res);
      if (res.status === 401 || res.status === 403) {
        // if not authenticated
        console.log(res);
      }
      return Observable.throw(res);
    };
  }
}

Configurez ensuite votre app.module.ts principal pour fournir la XHRBackend en tant que fournisseur ConnectionBackend et la RequestOptions à votre classe Http personnalisée:

import { HttpModule, RequestOptions, XHRBackend } from '@angular/http';
import { HttpService } from './services/http.service';
...
@NgModule({
  imports: [..],
  providers: [
    {
      provide: HttpService,
      useFactory: (backend: XHRBackend, options: RequestOptions) => {
        return new HttpService(backend, options);
      },
      deps: [XHRBackend, RequestOptions]
    }
  ],
  bootstrap: [ AppComponent ]
})

Après cela, vous pouvez maintenant utiliser votre fournisseur http personnalisé dans vos services. Par exemple:

import { Injectable }     from '@angular/core';
import {HttpService} from './http.service';

@Injectable()
class UserService {
  constructor (private http: HttpService) {}

  // token will added automatically to get request header
  getUser (id: number) {
    return this.http.get(`/users/${id}`).map((res) => {
      return res.json();
    } );
  }
}

Voici un guide complet - http://adonespitogo.com/articles/angular-2-extending-http-provider/

12
Adones Pitogo

Mieux vaut tard que jamais ... =)

Vous pouvez utiliser le concept de BaseRequestOptions étendu (à partir d'ici https://angular.io/docs/ts/latest/guide/server-communication.html#!#override-default-request-options ) et actualiser le en-têtes "à la volée" (pas seulement dans le constructeur). Vous pouvez utiliser la propriété "en-têtes" de getter/setter comme ceci:

import { Injectable } from '@angular/core';
import { BaseRequestOptions, RequestOptions, Headers } from '@angular/http';

@Injectable()
export class DefaultRequestOptions extends BaseRequestOptions {

    private superHeaders: Headers;

    get headers() {
        // Set the default 'Content-Type' header
        this.superHeaders.set('Content-Type', 'application/json');

        const token = localStorage.getItem('authToken');
        if(token) {
            this.superHeaders.set('Authorization', `Bearer ${token}`);
        } else {
            this.superHeaders.delete('Authorization');
        }
        return this.superHeaders;
    }

    set headers(headers: Headers) {
        this.superHeaders = headers;
    }

    constructor() {
        super();
    }
}

export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions };

Pour Angular 5 et supérieur, nous pouvons utiliser HttpInterceptor pour généraliser les opérations de demande et de réponse. Cela nous aide à éviter la duplication:

1) en-têtes communs 

2) Spécifier le type de réponse

3) Demande d'interrogation

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpResponse,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';

@Injectable()
export class AuthHttpInterceptor implements HttpInterceptor {

  requestCounter: number = 0;
  constructor() {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    request = request.clone({
      responseType: 'json',
      setHeaders: {
        Authorization: `Bearer token_value`,
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
      }
    });

    return next.handle(request).do((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse) {
        // do stuff with response if you want
      }
    }, (err: any) => {
      if (err instanceof HttpErrorResponse) {
        // do stuff with response error if you want
      }
    });
  }
}

Nous pouvons utiliser cette classe AuthHttpInterceptor en tant que fournisseur pour les HttpInterceptors:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app.routing-module';
import { AuthHttpInterceptor } from './services/auth-http.interceptor';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    BrowserAnimationsModule,
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthHttpInterceptor,
      multi: true
    }
  ],
  exports: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}
7
Prachi

Voici une version améliorée de la réponse acceptée, mise à jour pour Angular2 final:

import {Injectable} from "@angular/core";
import {Http, Headers, Response, Request, BaseRequestOptions, RequestMethod} from "@angular/http";
import {I18nService} from "../lang-picker/i18n.service";
import {Observable} from "rxjs";
@Injectable()
export class HttpClient {

    constructor(private http: Http, private i18n: I18nService ) {}

    get(url:string):Observable<Response> {
        return this.request(url, RequestMethod.Get);
    }

    post(url:string, body:any) {   
        return this.request(url, RequestMethod.Post, body);
    }

    private request(url:string, method:RequestMethod, body?:any):Observable<Response>{

        let headers = new Headers();
        this.createAcceptLanguageHeader(headers);

        let options = new BaseRequestOptions();
        options.headers = headers;
        options.url = url;
        options.method = method;
        options.body = body;
        options.withCredentials = true;

        let request = new Request(options);

        return this.http.request(request);
    }

    // set the accept-language header using the value from i18n service that holds the language currently selected by the user
    private createAcceptLanguageHeader(headers:Headers) {

        headers.append('Accept-Language', this.i18n.getCurrentLang());
    }
}

Bien sûr, il devrait être étendu aux méthodes telles que delete et put si nécessaire (je n'en ai pas encore besoin à ce stade de mon projet).

L'avantage est qu'il y a moins de code dupliqué dans les méthodes get/post/....

Notez que dans mon cas, j'utilise des cookies pour l'authentification. J'avais besoin de l'en-tête pour i18n (l'en-tête Accept-Language) car de nombreuses valeurs renvoyées par notre API sont traduites dans la langue de l'utilisateur. Dans mon application, le service i18n contient la langue actuellement sélectionnée par l'utilisateur.

6
Pierre Henry

Après quelques recherches, j’ai trouvé que le moyen le plus simple et final était d’étendre BaseRequestOptions que je préfère.
Voici comment j'ai essayé et abandonné pour une raison quelconque:
1. étendez BaseRequestOptions et ajoutez des en-têtes dynamiques dans constructor(). Cela ne peut pas fonctionner si je me connecte. Il sera créé une fois. Donc ce n'est pas dynamique.
2. étendre Http. Même raison que ci-dessus, je ne peux pas ajouter d’en-têtes dynamiques dans constructor(). Et si je réécris la méthode request(..) et que je définis les en-têtes, comme ceci:

request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
 let token = localStorage.getItem(AppConstants.tokenName);
 if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
  if (!options) {
    options = new RequestOptions({});
  }
  options.headers.set('Authorization', 'token_value');
 } else {
  url.headers.set('Authorization', 'token_value');
 }
 return super.request(url, options).catch(this.catchAuthError(this));
}

Vous devez juste écraser cette méthode, mais pas toutes les méthodes get/post/put. 

3.Et ma solution préférée est d’étendre BaseRequestOptions et d’écraser merge():

@Injectable()
export class AuthRequestOptions extends BaseRequestOptions {

 merge(options?: RequestOptionsArgs): RequestOptions {
  var newOptions = super.merge(options);
  let token = localStorage.getItem(AppConstants.tokenName);
  newOptions.headers.set(AppConstants.authHeaderName, token);
  return newOptions;
 }
}

cette fonction merge() sera appelée pour chaque demande.

5
Mavlarn

Que diriez-vous de garder un service séparé comme suit

            import {Injectable} from '@angular/core';
            import {Headers, Http, RequestOptions} from '@angular/http';


            @Injectable()
            export class HttpClientService extends RequestOptions {

                constructor(private requestOptionArgs:RequestOptions) {
                    super();     
                }

                addHeader(headerName: string, headerValue: string ){
                    (this.requestOptionArgs.headers as Headers).set(headerName, headerValue);
                }
            }

et lorsque vous appelez cela depuis un autre endroit, utilisez this.httpClientService.addHeader("Authorization", "Bearer " + this.tok);

et vous verrez l'en-tête ajouté, par exemple: - Authorization comme suit

 enter image description here

5
co2f2e

Bien que je réponde très tard, mais si quelqu'un cherche une solution plus facile. 

Nous pouvons utiliser angular2-jwt. angular2-jwt est utile pour attacher automatiquement un jeton Web JSON (JWT) comme en-tête d'autorisation lors de la création de demandes HTTP à partir d'une application Angular 2.

Nous pouvons définir des en-têtes globaux avec l'option de configuration avancée

export function authHttpServiceFactory(http: Http, options: RequestOptions) {
  return new AuthHttp(new AuthConfig({
    tokenName: 'token',
        tokenGetter: (() => sessionStorage.getItem('token')),
        globalHeaders: [{'Content-Type':'application/json'}],
    }), http, options);
}

Et envoi par jeton de requête comme  

    getThing() {
  let myHeader = new Headers();
  myHeader.append('Content-Type', 'application/json');

  this.authHttp.get('http://example.com/api/thing', { headers: myHeader })
    .subscribe(
      data => this.thing = data,
      err => console.log(error),
      () => console.log('Request Complete')
    );

  // Pass it after the body in a POST request
  this.authHttp.post('http://example.com/api/thing', 'post body', { headers: myHeader })
    .subscribe(
      data => this.thing = data,
      err => console.log(error),
      () => console.log('Request Complete')
    );
}
5
KNimhan

C'est ce que j'ai fait pour définir un jeton à chaque demande.

import { RequestOptions, BaseRequestOptions, RequestOptionsArgs } from '@angular/http';

export class CustomRequestOptions extends BaseRequestOptions {

    constructor() {
        super();
        this.headers.set('Content-Type', 'application/json');
    }
    merge(options?: RequestOptionsArgs): RequestOptions {
        const token = localStorage.getItem('token');
        const newOptions = super.merge(options);
        if (token) {
            newOptions.headers.set('Authorization', `Bearer ${token}`);
        }

        return newOptions;
    }
}

Et enregistrez-vous sur app.module.ts

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule
    ],
    providers: [
        { provide: RequestOptions, useClass: CustomRequestOptions }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }
5
Rajkeshwar Prasad

J'aime l'idée de remplacer les options par défaut, cela semble être une bonne solution.

Toutefois, si vous êtes prêt à étendre la classe Http. Assurez-vous de lire ceci à travers!

Certaines réponses ici montrent en fait une surcharge incorrecte de la méthode request(), ce qui pourrait provoquer des erreurs difficiles à détecter et un comportement étrange. Je suis tombé sur cela moi-même.

Cette solution est basée sur l’implémentation de la méthode request() dans Angular 4.2.x, mais devrait être compatible à l’avenir:

import {Observable} from 'rxjs/Observable';
import {Injectable} from '@angular/core';

import {
  ConnectionBackend, Headers,
  Http as NgHttp,
  Request,
  RequestOptions,
  RequestOptionsArgs,
  Response,
  XHRBackend
} from '@angular/http';


import {AuthenticationStateService} from '../authentication/authentication-state.service';


@Injectable()
export class Http extends NgHttp {

  constructor (
    backend: ConnectionBackend,
    defaultOptions: RequestOptions,
    private authenticationStateService: AuthenticationStateService
  ) {
    super(backend, defaultOptions);
  }


  request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> {

    if ('string' === typeof url) {

      url = this.rewriteUrl(url);
      options = (options || new RequestOptions());
      options.headers = this.updateHeaders(options.headers);

      return super.request(url, options);

    } else if (url instanceof Request) {

      const request = url;
      request.url = this.rewriteUrl(request.url);
      request.headers = this.updateHeaders(request.headers);

      return super.request(request);

    } else {
      throw new Error('First argument must be a url string or Request instance');
    }

  }


  private rewriteUrl (url: string) {
    return environment.backendBaseUrl + url;
  }

  private updateHeaders (headers?: Headers) {

    headers = headers || new Headers();

    // Authenticating the request.
    if (this.authenticationStateService.isAuthenticated() && !headers.has('Authorization')) {
      headers.append('Authorization', 'Bearer ' + this.authenticationStateService.getToken());
    }

    return headers;

  }

}

Notez que j'importe la classe d'origine de cette façon import { Http as NgHttp } from '@angular/http'; afin d'éviter des conflits de noms.

Le problème abordé ici est que la méthode request() a deux signatures d'appel différentes. Lorsque l'objet Request est transmis à la place de l'URL string, l'argument options est ignoré par Angular. Donc, les deux cas doivent être traités correctement.

Et voici l'exemple de la procédure à suivre pour enregistrer cette classe remplacée avec un conteneur DI:

export const httpProvider = {
  provide: NgHttp,
  useFactory: httpFactory,
  deps: [XHRBackend, RequestOptions, AuthenticationStateService]
};


export function httpFactory (
  xhrBackend: XHRBackend,
  requestOptions: RequestOptions,
  authenticationStateService: AuthenticationStateService
): Http {
  return new Http(
    xhrBackend,
    requestOptions,
    authenticationStateService
  );
}

Avec une telle approche, vous pouvez injecter la classe Http normalement, mais votre classe annulée sera injectée comme par magie. Cela vous permet d'intégrer facilement votre solution sans changer les autres parties de l'application (polymorphisme en action).

Ajoutez simplement httpProvider à la propriété providers de vos métadonnées de module.

4
Slava Fomin II

Le plus simple de tous

Créer un fichier config.ts

import { HttpHeaders } from '@angular/common/http';

export class Config {
    url: string = 'http://localhost:3000';
    httpOptions: any = {
        headers: new HttpHeaders({
           'Content-Type': 'application/json',
           'Authorization': JSON.parse(localStorage.getItem('currentUser')).token
        })
    }
}

Ensuite, sur votre service, importez simplement le fichier config.ts

import { Config } from '../config';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class OrganizationService {
  config = new Config;

  constructor(
    private http: HttpClient
  ) { }

  addData(data): Observable<any> {
     let sendAddLink = `${this.config.url}/api/addData`;

     return this.http.post(sendAddLink , data, this.config.httpOptions).pipe(
       tap(snap => {
      return snap;
        })
    );
 } 

Je pense que c'était le plus simple et le plus sûr.

0
Joshua Fabillar

J'ai pu choisir une solution plus simple> Ajouter un nouvel en-tête aux options par défaut fusionner ou charger par votre fonction api get (ou autre). 

get(endpoint: string, params?: any, options?: RequestOptions) {
  if (!options) {
    options = new RequestOptions();
    options.headers = new Headers( { "Accept": "application/json" } ); <<<<
  }
  // [...] 
}

Bien sûr, vous pouvez externaliser ces en-têtes dans les options par défaut ou quoi que ce soit dans votre classe. C’est dans l’API.ts de la classe d’exportation @Injectable () générée par Ionic {}

C'est très rapide et ça marche pour moi. Je ne voulais pas le format JSON/LD.

0
Paul Leclerc

Il y a eu quelques changements pour les versions 2.0.1 et supérieures angulaires:

    import {RequestOptions, RequestMethod, Headers} from '@angular/http';
    import { BrowserModule } from '@angular/platform-browser';
    import { HttpModule }     from '@angular/http';
    import { AppRoutingModule } from './app.routing.module';   
    import { AppComponent }  from './app.component';

    //you can move this class to a better place
    class GlobalHttpOptions extends RequestOptions {
        constructor() { 
          super({ 
            method: RequestMethod.Get,
            headers: new Headers({
              'MyHeader': 'MyHeaderValue',
            })
          });
        }
      }

    @NgModule({

      imports:      [ BrowserModule, HttpModule, AppRoutingModule ],
      declarations: [ AppComponent],
      bootstrap:    [ AppComponent ],
      providers:    [ { provide: RequestOptions, useClass: GlobalHttpOptions} ]
    })

    export class AppModule { }
0
Carlos Casallas