I am trying to modify response header or an API request that goes from my Angular Application. What I have done is I have created a RequestTracker interceptor which extends HttpInterceptor and adds Correlation-Id to request header. What I want is the same Correlation-Id to be part of the response header. I tried the below interceptor but it isn't working for me. Is there anything I am missing?
import * as uuid from 'uuid';
import {
HttpEvent,
HttpHandler,
HttpHeaders,
HttpInterceptor,
HttpRequest,
HttpResponse
} from '#angular/common/http';
import { filter, map } from 'rxjs/operators';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
#Injectable()
export class RequestTracker implements HttpInterceptor {
intercept(
httpRequest: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const correlationId = uuid.v4();
const httpRequestClone = httpRequest.clone({
setHeaders: {
'Correlation-Id': correlationId
}
});
return next.handle(httpRequestClone).pipe(
filter((response) => response instanceof HttpResponse),
map((response: HttpResponse<any>) => {
const modifiedResponse = response.clone({
headers: response.headers.set('Correlation-Id', correlationId)
});
return modifiedResponse;
})
);
}
}
In Request Headers, the Correlation-Id is getting appended but not in Response Headers.
The response that you have posted, is from the network tab and it refers to the response sent from the server. You are attaching the header once Angular starts processing that response. So it won't be shown in the network tab. Try logging the response inside the code. And correlationId will be part of the response header.
Related
I am trying to make an interceptor to refresh the token, but it throws me this error and I don't know why
ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
token-interceptor.service.ts
import { Injectable } from '#angular/core';
import { AuthService } from './auth.service';
import { HttpClient, HttpErrorResponse, HttpHandler, HttpInterceptor, HttpRequest } from '#angular/common/http';
import { environment } from 'src/environments/environment';
import { catchError, map} from 'rxjs/operators';
import { throwError } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class TokenInterceptorService implements HttpInterceptor {
constructor(
private auth: AuthService,
private http: HttpClient
) { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(
catchError((err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.url.includes('signin') || err.url.includes('refreshToken')) {
return next.handle(req)
}
//if error is not about authorization
if (err.status !== 401) {
return next.handle(req)
}
this.renewToken(req).subscribe(request => {
return next.handle(request)
})
} else {
return throwError(err)
}
})
)
}
renewToken(req: HttpRequest<any>) {
return this.http.get(`${environment.API_URL}/refreshToken`, { withCredentials: true }).pipe(
map((res: any) => {
//update access token
this.auth.setToken(res.token)
return req.clone({
setHeaders: {
authorization: `Bearer ${res.token}`
}
})
})
)
}
}
Ignore this: It looks like your post is mostly code; please add some more details. It looks like your post is mostly code; please add some more details.
this piece of code is wrong:
this.renewToken(req).subscribe(request => {
return next.handle(request)
})
istead it should be:
return this.renewToken(req).pipe(switchMap(request => next.handle(request)));
you are just returning nothing in your variant, that is why it doesn't work.
also the whole logic of token interpceptor seems weird to me. I believe you should rethink about how you want it to work. for now as I see you sending request without token and in almost all cases you are sending it again unmodified, and the one that I fixed above will send it again with token. Wouldn't it be right to add token every time, and only send it 2nd time if token is outdated?
I am developing an application with Ionic Framework Version 3.19.1, I am making a request via post, the URL data, and necessary parameter information is all ok, however, it is returning an error that I can not solve, I have tried many ways, imports into the project, but without success. below is my post function.
const req = this.http.post(url, {
options: {
headers:[header],
params:[postData]
}
}).subscribe(
res => {
console.log(res);
},
err => {
console.log('Ocorreu um erro');
}
)
Below are my imports inside the .ts file (TypeScript)
import { Component } from '#angular/core';
import { TranslateService } from '#ngx-translate/core';
import { IonicPage, NavController, ToastController } from 'ionic-angular';
import { HttpClient, HttpParams, HttpHeaders, HttpErrorResponse } from '#angular/common/http';
import { IonicStorageModule } from '#ionic/storage';
import { User } from '../../providers/providers';
import { MainPage } from '../pages';
Well, as I said I'm doing a post request and on the console, it returns an OPTIONS 500 (Internal Server Error)
Failed to load (URL): Response to preflight request does not pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http: // localhost: 8100' is therefore not allowed access. The response had HTTP status code 500.
by what I understand is reporting a problem regarding the Header, but I have already informed the correct one and left the requests open, but it still does not work, here is my header below.
const header = new HttpHeaders();
header.set('Access-Control-Allow-Origin', '*');
header.set('Content-type', 'application/json');
change the const req
return new Promise((resolve, reject) => {
this.http.post(url, postData,
{headers: this.header})
.subscribe(
data => {
console.log('success');
resolve(data);
},
err => {
reject(err);
console.log(err);}
);
});
I am using events in my Ionic3 app.
For e.g I am using events to redirect the user to login screen whenever any API response gives HTTP 401.
So in my app.component.ts file I am doing:
import { Component, ViewChild } from '#angular/core';
import { StatusBar } from '#ionic-native/status-bar';
import { Events } from 'ionic-angular';
import { Network } from '#ionic-native/network';
import { Toast } from '../utilities/toast';
import { LocalStorage } from '../utilities/localstorage';
import { Platform, MenuController, Nav } from 'ionic-angular';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild(Nav) nav: Nav;
rootPage: any;
pages: Array<{title: string, pageName: string}>;
guardian: any;
constructor(
public platform: Platform,
public menu: MenuController,
public statusBar: StatusBar,
public events: Events,
public network: Network,
public toast: Toast,
public storage: LocalStorage)
{
console.log('before unauthorised'); //This line works when a 401 occurs
events.subscribe('unauthorised', () => {
console.log('user unauthorised take to login page'); //While this doesn't
this.storage.clear();
this.nav.setRoot('LoginPage');
});
}
}
And in my api services file I am publishing the event:
import { Http } from '#angular/http';
import { Injectable } from '#angular/core';
import { Toast } from '../utilities/toast';
import { Events } from 'ionic-angular';
import { LocalStorage } from '../utilities/localstorage';
#Injectable()
export class ServiceProvider {
constructor(public http: Http,
private toast: Toast,
public events: Events,
private storage: LocalStorage) {
}
getErrorMessages(errors) {
if (errors.status == 401) { //<= unauthorised
this.toast.present('You need to login first!');
this.events.publish('unauthorised');
}
let error_messages = [];
if (errors.status == 422) { //<= validation error
let validation_messages = JSON.parse(errors.text())
for (var key in validation_messages) {
if (validation_messages.hasOwnProperty(key)) {
var messages = validation_messages[key];
error_messages.push(...messages);
}
}
} else { //<= timeout or http code 500, 405 etc.
error_messages.push('Technical error occured... please try again later.');
}
return error_messages;
}
}
What could have been the problem? The code looks correct as per the ionic documentation.
EDIT I am adding the child service code. So basically the service provider is the parent class for all the api services. For e.g the auth service class extends the service class above and has the following method for fetching auth user:
getAuthUser() {
console.log('will fetch auth');
let headers = new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer ' + this.getAuthToken()
});
let options = new RequestOptions({ headers: headers });
return new Promise((resolve, reject) => {
this.http.get(this.getApiUrl() + '/me', options)
.timeout(this.getTimeOut())
.map(res => res.json())
.subscribe(response => {
resolve(response);
this.events.publish('auth_user_fetched');
}, errors => {
reject(this.getErrorMessages(errors));
});
});
}
Not that I am not using try catch here.
What is happening is that app.component.ts acts as a Parent and your provider as a child. Therefore, the event cannot be published and subscribed.
In your code console.log('before unauthorised'); //This line works when a 401 occurs works because app.component.ts is a file that gets called everytime you do some activity. And this console is written in the constructor(resulting in it called everytime).
What you can do is instead of using event for unauthorized functionality. Create a function in your provider itself that will do
unauthorized() {
console.log('user unauthorised take to login page');
this.storage.clear();
this.nav.setRoot('LoginPage');
}
I'm trying to set a token value in all request headers using angular 5 new HTTP client. Below is my code:
import {Injectable} from '#angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '#angular/common/http';
import {Observable} from "rxjs/Observable";
import { Storage } from '#ionic/storage';
import {Globals} from '../globals/globals';
#Injectable()
export class Interceptor implements HttpInterceptor {
token: string;
constructor(private storage: Storage, private global: Globals){
this.storage.get('token').then((val) => {
this.token = val;
});
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log(this.token) //undefined "only for first time on app start"
req = req.clone({
setHeaders: {
'Token': this.token,
'Version': this.global.version,
}
});
return next.handle(req);
}
}
While adding the token in request header works, but there is a bad exception. It doesn't work for the first time. The problem is with js async nature, req.clone gets executed before getting token from the storage. Because Ionic storage returns promise, so how to handle this situation for the first time?
You can merge both async request (getting the token and handling the request) to execute the later when the token is ready (instead of getting it in the constructor):
// -------------------------------------------------------------------------
// Please note that I'm using lettable/pipeable operators (RxJS > 5.5.x)
// https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
// -------------------------------------------------------------------------
import { Injectable } from '#angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '#angular/common/http';
import { Observable } from "rxjs/Observable";
import { Storage } from '#ionic/storage';
import { Globals } from '../globals/globals';
// New imports!
import { fromPromise } from 'rxjs/observable/fromPromise';
import { mergeMap } from 'rxjs/operators/mergeMap';
#Injectable()
export class Interceptor implements HttpInterceptor {
constructor(private storage: Storage, private global: Globals){ }
getToken(): Promise<any> {
return this.storage.get('token');
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return fromPromise(this.getToken()).pipe(
mergeMap(token => {
// Use the token in the request
req = req.clone({
setHeaders: {
'Token': token,
'Version': this.global.version,
}
});
// Handle the request
return next.handle(req);
}));
}
}
This is going to be a long question.
For windows authentication to work with angular I have wrapper for the http calls as shown below
import { Injectable } from '#angular/core';
import { Http, Response, RequestOptions, Headers, RequestOptionsArgs } from '#angular/http';
import { Config } from '../_helpers/config';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class HTTPUtility {
public baseApiUrl: string;
constructor(private http: Http, private config: Config) {
this.baseApiUrl = this.config.getByKey('baseApiUrl') || '';
}
public getApiUrl(url) {
return this.baseApiUrl + url;
}
public get(url: string, options?: RequestOptions) {
if (!options) {
options = this.getDefaultHeaders();
}
return this.http.get(this.getApiUrl(url), options)
.catch(this.handleError);
}
private getDefaultHeaders() {
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
headers.append('Accept', 'application/json');
return new RequestOptions({ headers, withCredentials: true });
}
public handleError(response: Response) {
return Observable.throw(JSON.parse(response.json().Message) || 'Server error');
}
}
If you observe new RequestOptions({ headers, withCredentials: true }); is allowing browser to send credentials to server for windows authentication.
And it's working great for everything.
Now coming to the issue, I have sampleComponent in which i'm using ServerDataSource as shown below:
import { Component, OnInit, NgZone } from '#angular/core';
import { Http, RequestOptions } from '#angular/http';
import { Ng2SmartTableModule, ServerDataSource } from 'ng2-smart-table';
#Component({
selector: 'criteria',
templateUrl: './criteria.component.html',
styles: [require('./criteria.scss')],
})
export class SampleComponent implements OnInit {
Source: ServerDataSource;
settings: any;
constructor(
private http: Http) {
this.Source = new ServerDataSource(http, { endPoint: 'https://xxxxxx.org/yyy/GetCriterias'});
}
ngOnInit(): void {
// this.settings = {}// assigning the settings.
}
}
As you can see ServerDataSource is accepting Http instance and I have checked there documentation and haven't found any way to pass to RequestOptions. So the web api call made by ng2-smart-table fails with 401 status as credentials is not passed.
To resolve this issue I have made changes directly to ng2-smart-table source file to be specific 'server.data-source.js' and here is the change
ServerDataSource.prototype.createRequestOptions = function () {
var requestOptions = {withCredentials : true}; // this where I have added the withCredntials flag
requestOptions.params = new URLSearchParams();
requestOptions = this.addSortRequestOptions(requestOptions);
requestOptions = this.addFilterRequestOptions(requestOptions);
return this.addPagerRequestOptions(requestOptions);
};
With this change everything is working fine as of now.
But I could have issue in future, if we upgrade the package in that case I have to again make changes.
So if any one can help me to fix the issue in some other way please let me know.
Links: https://github.com/akveo/ng2-smart-table/blob/master/src/app/pages/examples/server/advanced-example-server.component.ts
Thanks.