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 });
}
Related
So I have this interceptor class:
export class AddHeaderInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const headers = new HttpHeaders({
'app': getPackage().name,
'app-version': getPackage().version,
});
const cloneReq = req.clone({ headers });
req.clone({ headers: req.headers });
if (req.method !== 'GET' && !req.headers.has('Content-Type')) {
// Clone the request to add the new header
req = req.clone({
setHeaders: {
'Content-Type': 'application/json',
'app': getPackage().name,
'app-version': getPackage().version,
},
});
// Pass the cloned request instead of the original request to the next handle
return next.handle(req);
}
return next.handle(cloneReq);
}
}
It is meant to add those header properties to every request. Having to repeat the header properties twice is obviously bad code, but that's the only way I've been able to get it to work (For both GET and POST requests). Please how do I adjust it to call header properties once for both GET and POST requests?
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I need an advice how to make my code better.
I have a simple class that gets data from backend that is using jwt token auth.
export class RepositoryService {
constructor(private http: HttpClient, private envUrl: EnvironmentUrlService) { }
public getData = (route: string) => {
return this.http.get(this.createCompleteRoute(route, this.envUrl.urlAddress), this.generateHeaders());
}
private createCompleteRoute = (route: string, envAddress: string) => {
return `${envAddress}/${route}`;
}
private generateHeaders = () => {
return {
headers: new HttpHeaders({
"Content-Type": "application/json",
"Authorization": `Bearer ${localStorage.getItem("token")}`
}),
};
};
It works fine but the problem starts when I get a lot more of http methods. How can I change createCompleteRoute so I won't have to use generateHeaders() in every http method?
I though about doing something like:
private createCompleteRoute = (route: string, envAddress: string) => {
return `${envAddress}/${route}`, this.generateHeaders();
}
so http methods could look like this:
public getData = (route: string) => {
return this.http.get(this.createCompleteRoute(route, this.envUrl.urlAddress));
}
But have no idea how to write a valid function.
The best way to do what you ask for, could be to bring your logic for creating headers to an interceptor, which is going to automatically add the header parameters to every http call.
It could be something like this:
Your interceptor file (is kinda service, but have to implement HttpInterceptor:
import { Injectable } from '#angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from '#angular/common/http';
// The service/way you use to get your token
import { AuthService } from '../services/auth.service';
#Injectable()
export class MyInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const url="\yourAPI\endpoint";
// Get your token
cont myToken = this.authService.getToken(); // or localStorage.getItem("token") or whatever your way to get your token
// Add authorization header with token if available
if (myToken) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.user.api_token}`,
'Content-Type': 'application/json',
},
url,
});
}
...
}
EXTRA: More info about how to adding and updating headers and how to Use the interceptor for Intercepting requests and responses:
Adding & Updating Headers
Intercepting request & responses
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.
I am using angular 8 to make a SPA.
Firebase is used to authenticate the user both in the client as well as in the backend, so I need to send the jwt token in http.get request to the backend to authenticate the user.
Backend is an API made with django 2.2 and django rest framework which sends the api to be consumed in client application.
auth.service.ts
#Injectable({
providedIn: 'root'
})
export class AuthService {
userData: any; // Save logged in user data
public userToken: string;
constructor(
public afs: AngularFirestore, // Inject Firestore service
public afAuth: AngularFireAuth, // Inject Firebase auth service
public router: Router,
public ngZone: NgZone // NgZone service to remove outside scope warning
) {
/* Saving user data in localstorage when
logged in and setting up null when logged out */
this.afAuth.authState.subscribe(user => {
if (user) {
this.userData = user;
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
}
GetToken(): string {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
// this shows the userToken
console.log('token inside getToken method ' + this.userToken);
});
}
});
// this shows userToken as undefined
console.log('before return ' + this.userToken);
return this.userToken;
}
}
api.service.ts
#Injectable({
providedIn: 'root'
})
export class ApiService {
private url = environment.baseUrl;
token: any;
data: any;
constructor(
private http: HttpClient,
private authService: AuthService,
) {}
// old method to get emloyees data
// public getEmployees(): Observable<Employee[]> {
// return this.http.get<Employee[]>(`${this.url}/employee/`);
// }
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + this.authService.GetToken()
}),
};
public getEmployees(): Observable<Employee[]> {
// token is undefined here
console.log('token inside getEmployees method ' + this.token);
return this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions);
}
}
The backend is working perfectly which I verified by adding the token in the httpOptions, like so:
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + 'ey.....'
}),
};
But when I try doing the same as given in code it doesn't work.
The user token remains undefined.
Peter's answer has the crux of it: getIdToken() is asynchronous, so by the time your return this.userToken; runs, the this.userToken = idToken; hasn't run yet. You should be able to see this from the output of your console.log statements.
For more on this see How to return value from an asynchronous callback function? I highly recommend studying this answer for a while, as this asynchronous behavior is incredibly common when dealing with web APIs.
The fix for your code is to return a Promise, instead of trying to return the value:
GetToken(): Promise<string> {
return new Promise((resolve, reject) => {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
resolve(idToken);
});
}
});
})
}
In words: GetToken returns a promise that resolves once an ID token is available. If you know the user is already signed in when you call this function, you can simplify it to:
GetToken(): string {
const user = firebase.authentication().currentUser;
return user.getIdToken()
}
The difference is that the second function does not wait for the user to be signed in, so will fail if there is no signed in user.
You then use either of the above functions like this in getEmployees:
public getEmployees(): Observable<Employee[]> {
return new Promise((resolve, reject) =>
this.authService.GetToken().then((idToken) => {
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + idToken
}),
};
this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions)
.then(resolve).catch(reject);
})
})
}
It is undefined here console.log('before return ' + this.userToken); because getIdToken() returns a Promise which means it is asynchronous, therefore the only way to access the userToken is inside the then() method.
I have this fragment of code (simplified), I try to send a custom header "foo-custom" using http.post, but the request in the browser is sent as a 200 OPTIONS. What is it due?
Import
import { Http, Headers, Response, RequestOptions } from '#angular/http';
App
const myheaders = new Headers();
myheaders.append('Content-Type', 'application/json');
myheaders.append('foo-custom', 'var');
const options = new RequestOptions({headers: myheaders});
this.http.post(
this._url+'search', {}, options
).subscribe(data => {
console.log("CORRECT!");
console.log(data);
}, error => {
console.log("ERROR!");
console.log(error);
});
What is the correct way to do it?
Thanks
It looks like a preflighted request, can you check this comment
Possible duplicated thread?