I have to put a token inside the 'Authorization' header for every HTTP request.
So I have developed and registered an HttpInterceptor :
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(public authService: AuthService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let modifiedReq;
const token = this.authService.getToken();
// we need the heck clone because the HttpRequest is immutable
// https://angular.io/guide/http#immutability
if (token) {
modifiedReq = request.clone();
modifiedReq.headers.set('Authorization', `Bearer ${token}`);
}
return next.handle(modifiedReq ? modifiedReq : request).pipe(tap(() => {
// do nothing
},
(err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 0) {
alert('what the heck, 0 HTTP code?');
}
if (err.status !== 401) {
return;
}
this.authService.goToLogin();
}
}));
}
}
But the header seems never to be put on the request sent. What am I doing wrong?
Also, sometimes an errorcode '0' gets caught by the interceptor; what does it mean?
Angular 8.2.11
EDIT 1: ------------------------
I've also tried like this:
request = request.clone({
setHeaders: {
authorization: `Bearer ${token}`
}
});
but still no header has been set.
Also, the module is correctly registered in app.module
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor ,
multi: true,
}..
EDIT 2 : ------------------------
Check this image... I'm going crazy.
It's working for me like this:
const headersConfig = {
'Accept': 'application/json', //default headers
};
...
if (token) {
headersConfig['Authorization'] = `Bearer ${token}`;
}
...
return next
.handle(request.clone({
setHeaders: headersConfig
}))
maybe you forget to put in app.module this:
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor ,
multi: true,
}..
the final part write in this way:
return next.handle(modifiedReq);
I was wrong. When doing update of the clone request, angular will put the new headers in fields called "lazyUpdate" and not direcly inside the headers.
The requests were failing because of other reasons.
Related
I have a little issue from an Angular app to get a code from my own server.
I´ve built up a little Spotify App for learning more about Angular 10 and have a little backend that I only use for get the Bearer code to call the Spotify API, but the fact is that in my Angular front I can´t save the code.
Tis is my service call code to the back:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment';
#Injectable({
providedIn: 'root'
})
export class SpotifyService {
token: any;
constructor(private http: HttpClient) {
console.log('Spotify service ready');
this.getAccesToken().subscribe(data => this.token = data['access_token']);
}
getAccesToken(){
return this.http.get(environment.server + `${environment.client_id}/${environment.client_secret}`)
.pipe(
map(res => res)
);
}
getQuery(query: any){
const headers = new HttpHeaders({
'Authorization': `Bearer ${this.token}`
});
const url = `https://api.spotify.com/v1/${query}`;
return this.http.get(url, {headers});
}
I´ve checked the recibed data and I get the request perfectly, but can´t save the data of the suscriber into a variable.
Thanks in advance!
Assuming your component code looks like below, you can make some adjustments and try it.
export class SomeComponent implements OnInit {
spotifyData;
user;
token;
constructor(private spotifyService: SpotifyService, private http: HttpClient) { }
ngOnInit() {
this.user = JSON.parse(localStorage.getItem('user'));
console.log(this.user);
this.token = this.user.token;
this.getSpotifyData();
}
getSpotifyData() {
this.spotifyService.getQuery(this.user, { headers: new HttpHeaders().set('Authorization', 'Bearer ' + this.token) }).subscribe(res => {
console.log(res);
if (res) {
spotifyData = res;
} else {
spotifyData = []
}
});
}
I'm building the front-end of an application in Angular 8. This application uses an OAuth 2 implementation to manage authentication (password grant) so any HTTP request (with the exception of ones to the token endpoint) needs to have on its header a valid access_token.
To provide said token I've made an Angular interceptor that retrieve the token from another service and then attach it to the intercepted HTTP request. The token retrieval method doesn't give directly the token but an observable which eventually resolves to a valid token, I made this choice because the access token may not be instantly available, if the token is expired the application needs to refresh it with an HTTP call and then the refreshed token can be passed to the HTTP interceptor.
The problem which I encounter is that despite my many attempts the interceptor doesn't wait for the token to be retrieved so at the end the interceptor is skipped and the HTTP request is made without any token attached.
This is the code of my interceptor, retrieveValidToken is the Observable which returns the token.
import { Injectable } from '#angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { FacadeService } from './facade.service';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class HttpInterceptorService implements HttpInterceptor {
constructor(private facadeService: FacadeService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.includes('localhost:3000') && !req.url.endsWith('token')) {
this.facadeService.retrieveValidToken()
.subscribe(
(res) => {
const clone = req.clone({ setHeaders: { Authorization: `Bearer ${res}` } });
return next.handle(clone);
},
(err) => {
const clone = req.clone({ setHeaders: { Authorization: `Bearer ` } });
return next.handle(clone);
}
);
} else {
return next.handle(req);
}
}
}
Observables are asynchronous. The code outside the subscribe method will not wait for the code inside.
You should return observable by itself, not only result inside its subscription:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.includes('localhost:3000') && !req.url.endsWith('token')) {
return this.facadeService.retrieveValidToken()
.subscribe(
res => {
const clone = req.clone({ setHeaders: { Authorization: `Bearer ${res}` } });
return next.handle(clone);
}
);
} else {
return next.handle(req);
}
}
Something similar:
How use async service into angular httpClient interceptor
The problem is that 'intercept' method should return observable immediately, so instead of subscribing to 'this.facadeService.retrieveValidToken()' use the following code:
return this.facadeService.retrieveValidToken().pipe(
mergeMap(token =>
next.handle(req.clone({ setHeaders: { Authorization: 'Bearer ${token}' }))
)
)
I have upgraded my angular app from 4 to latest version 7. After so many errors I finally got it to work but now there is one problem: services are not working, like it is not giving any error in the console but not working as well.
I think the problem is with my Http interceptor and the factory in which I am missing something.
Can someone tell me what the issue is, exactly?
Http interceptor
export class InterceptedHttp extends HttpClient
constructor(
backend: HttpBackend,
private store: Store<any>,
private spinnerService: SpinnerService
) {
super(backend);
}
request( url: string | HttpRequest<any>, options?: any): Observable<any> {
this.showLoader();
return this.tryCatch(super.request(this.getRequestOptionArgs(options)))
}
get(url: string, options?: any): Observable<any> {
url = this.updateUrl(url);
return this.tryCatch(super.get(url));
}
post(url: string, body: string, options?: any): Observable<any> {
url = this.updateUrl(url);
return this.tryCatch(
super.post(url, body)
);
}
put(url: string, body: string, options?: any): Observable<any> {
url = this.updateUrl(url);
return this.tryCatch(
super.put(url, body)
);
}
delete(url: string, options?: any): Observable<any> {
url = this.updateUrl(url);
return this.tryCatch(super.delete(url));
}
patch(url: string, body: any, options?: any): Observable<any> {
url = this.updateUrl(url);
return this.tryCatch(
super.patch(url, body)
);
}
private updateUrl(req: string) {
return environment.origin + req;
}
private getRequestOptionArgs(options?: any): any {
if (options.headers == null) {
options.headers = new HttpHeaders();
}
options.headers.append('Content-Type', 'application/json');
options.headers.append(
'Authorization',
` Bearer ${sessionStorage.AccessToken}`
);
return options;
}
private tryCatch(obs: Observable<any>) {
return obs.pipe(catchError((error: HttpResponse<any>, caught) => {
if (error.status === 401 && sessionStorage.AccessToken) {
sessionStorage.clear();
this.store.dispatch({type: 'LOGOUT'});
}
this.hideLoader();
return observableThrowError(error);
}));
}
Http factory
export function httpFactory(xhrBackend: HttpXhrBackend,
store: Store<any>, spinnerService: SpinnerService): HttpClient {
return new InterceptedHttp(xhrBackend, store, spinnerService);
}
provider in app module
{
provide: HttpClient,
useFactory: httpFactory,
deps: [HttpXhrBackend, Store, SpinnerService]
},
Whenever I login it just starts loading, nothing else, no error or anything and when I comment out the provider in the app module it says "404 not found error".
Any help?
Thanks
Can't comment on how you did interceptors in Angular 4. But since 4.3 HttpInterceptor was introduced so here is an example on how you do http interceptors in Angular 7:
#Injectable()
export class ApiInterceptor implements HttpInterceptor {
constructor(private someService: SomeService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return this.someService.getBaseUrl().pipe(
mergeMap(baseUrl => {
const apiRequest = data
? request.clone({ url: `${baseUrl}${request.url}` })
: request;
return next.handle(apiRequest);
})
);
}
}
Here is an example of a intercept that does nothing but returning the request unchanged:
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request);
});
How to provide it:
import { HTTP_INTERCEPTORS } from '#angular/common/http';
// ...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true }
]
You can inspect the request object to check the request type (POST, GET) and do what ever you want with it.
This might be obvious (or not) but HttpInterceptor only works if your requests are made with the HttpClient introduced in Angular 4.3. If your requests are made by other http client (the older client from HttpModule, native code or other library) it will not work, AFAIK.
Also if you try to lazy load the interceptor it will not be straightforward (not even sure if its possible), so first try to get it to work like this.
I'm trying to make a Http request in NestJS
As it's inspired from angular I jave append my Headers
import { Injectable, HttpService} from '#nestjs/common';
...
const headersRequest = new Headers();
headersRequest.append('Content-Type', 'application/json');
headersRequest.append('Authorization', `Basic ${encodeToken}`);
Then call the api
const result = await this.httpService.post(apiUrl, newDevice, { headers: headersRequest });
I get an error
ReferenceError: Headers is not defined
And when I ass Headers to import
I get this message waring in VScode
Only a void function can be called with the 'new' keyword.
NestJS uses axios under the hood to make http requests, take a look at its documentation for request configuration:
https://github.com/axios/axios#request-config
Looks like there is no interface for headers, just pass a plain JS dictionary object:
const headersRequest = {
'Content-Type': 'application/json', // afaik this one is not needed
'Authorization': `Basic ${encodeToken}`,
};
const result = await this.httpService.post(apiUrl, newDevice, { headers: headersRequest });
Another option (since nest v5 introduced HttpModule.registerAsync) if your encodeToken is pretty static or hardcoded from your config is setting it up in the module level:
import { Module, HttpModule } from '#nestjs/common';
import { ConfigModule } from '..';
import { ConfigService } from '../config/config.service';
#Module({
imports: [
ConfigModule,
HttpModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
baseURL: configService.get('vendors.apiEndpoint'),
headers: {
'Authorization': 'Basic ' + configService.get('vendors.encodeToken')
},
timeout: 7000,
maxRedirects: 5
}),
inject: [ConfigService]
})
],
// ... other module stuff
})
export class MyModule {}
I think this method false
in for read headers parameter just req.headers
example
#Get()
findHeaderexample(#Res() res,#Req req) {
return req.headers;
}
If you want to set a custom header, you can use #ApiHeader()
#ApiHeader({
name: 'api-key',
description: 'api-key',
})
You could use withCredentials option as below:
export const HttpMessagingProvider = HttpModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
baseURL: configService.get('vendors.apiEndpoint'),
timeout: 5000,
maxRedirects: 5,
withCredentials: true,
}),
inject: [ConfigService]
});
I am using a restapi and it requires that I add a token to the header before I can create a new record.
Right now I have a service to create a new record which looks like this:
service.ts
create(title, text) {
let headers: HttpHeaders = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
headers = headers.append('Authorization', token); // Not added yet as this is the reason for the question
return this.http.post('http://myapi/api.php/posts', {
title: 'added title',
text: 'added text'
}, { headers });
}
app.component.ts
add() {
this.service.create('my title', 'body text').subscribe(result => {
console.log(result);
});
}
The problem with this is that it won't let me add the new record because it requires a token and in order to get a token I need to run this:
getToken() {
let headers: HttpHeaders = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
return this.http.post('http://myapi/api.php/user', {
username: 'admin',
password: 'password'
}, { headers });
}
My question is...How do I get this two together into one call instead of two...or when is the best way to do this?
Apart from what #Pardeep Jain already mentioned, you can add an interceptor (> Angular version 4, you mentioned you're using 5) for your HttpClient that will automatically add Authorization headers for all requests.
If you need top be authenticated for only one request, it's better to keep things simple and use Pardeep's solution.
If you want to be authenticated for most of your requests, then add an interceptor.
module, let's say app.module.ts
#NgModule({
//...
providers: [
//...
{
provide: HTTP_INTERCEPTORS,
useClass: JwtInterceptor,
multi: true
},
//...
]
//...
})
and your jwt interceptor, let's say jwt.interceptor.ts
#Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private injector: Injector, private router: Router) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authReq = req.clone({
headers: req.headers.set('Authorization', /* here you fetch your jwt */this.getToken())
.append('Access-Control-Allow-Origin', '*')
});
return next.handle(authReq).do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}, (response: HttpErrorResponse) => { });
}
getToken() {
let headers: HttpHeaders = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
return this.http.post('http://myapi/api.php/user', {
username: 'admin',
password: 'password'
}, { headers });
}
}
If you want to read something, more here: https://medium.com/#ryanchenkie_40935/angular-authentication-using-the-http-client-and-http-interceptors-2f9d1540eb8
My question is...How do I get this two together into one call instead
of two...or when is the best way to do this?
You should not.
Authentication is one thing that should be performed a single time for the client or as the authentication ticket has expired.
Posting some content is another thing that you should not mix with authentication.
So authenticate the client once and store the ticket.
Then pass the ticket in the header for any request to a secured endpoints/methods. Or use a transverse way as an interceptor to set it in the send requests if you don't want to repeat the code.
The code should be like this -
create(title, text) {
let headers: HttpHeaders = new HttpHeaders();
headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
headers.append('Authorization', token);
return this.http.post('http://myapi/api.php/posts', {
title: 'added title',
text: 'added text'
}, { headers });
}