I am working in an Angular 6 application and I was wondering what should be the best practice when customizing the url while sending requests to the server.
Here is the scenario:
- In my Angular project I have the environment.ts and environment.prod.ts where I added a "host" which contains the url:port of the http server (project with the controllers).
- I am creating Services to be injected in my components which will be responsible for sending requests (GETs and POSTs) to the server to retrieve data or to send updates.
- I want to use the "host" from the environment.ts as part of the request url. So ALL my requests will have the "host" as the base url and then i can concatenate to the desired path.
I already checked a few solutions and I already implemented one of them, but I am not sure this is the right practice. I will write below what i implemented so far and then i will write some ideas, please help me understand what is the best solution (I am new at angular)
Currently implemented:
-> In my feature services, like LoginService, I inject the angular HttpClient. Then I simply call:
return this.httpService.post("/login/", creds).pipe(
map((data: any) => {
this.manager = data;
return this.manager;
}));
I created an interceptor to make changes to the url: InterceptService implements HttpInterceptor where I create a new instance of the HttpRequest and customize the request.url using environment.host. I also needed the interceptor to add a Header for the authentication (still not fully implemented)
const httpRequest = new HttpRequest(<any>request.method, environment.host + request.url, request.body);
request = Object.assign(request, httpRequest);
const headers = new HttpHeaders({
'Authorization': 'Bearer token 123',
'Content-Type': 'application/json'
});
Questions:
1) This works, all my requests are changed in the interceptor as I
wanted, but it doesn't look like the best practice in my first look. I
don't like to create a new HeepRequest to be able to do this (i did it
to keep it immutable, I guess that's the correct way). Do you think
this looks good?
2) What about the Authentication being added to the Header in the interceptor? Is it ok? Most of the references I checked did this
Other solutions:
1) I saw some examples where a HttpClientService extends Http and each of the methods such as get and post edit the url and headers before calling super methods. But I believe this is not Angular 6 and is probably not preferrable
2) I could also create a service that receives an angular HttpClient (angular 6 HttpClientModule) instance by injection and I could implement the methods like get or post.
Well, as I didn't get any answers I will add my solution. i believe it's the best solution based on my researches.
I used an interceptor for adding information to the header such as the
token bearer authentication.
import { Injectable } from '#angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpResponse,
HttpHeaders,
HttpErrorResponse
} from '#angular/common/http'
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { environment } from "../../../environments/environment";
import { Router } from "#angular/router";
export class HttpClientInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
// intercept request to add information to the headers such as the token
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
//I decided to remove this logic from the interceptor to add the host url on the HttpClientService I created
//const httpRequest = new HttpRequest(<any>request.method, environment.host + request.url, request.body);
//request = Object.assign(request, httpRequest);
var token = localStorage.getItem("bearerToken");
if (token) {
const newReq = request.clone(
{
headers: request.headers.set('Authorization',
'Bearer ' + token)
});
return next.handle(newReq).pipe(
tap(event => {
if (event instanceof HttpResponse) {
console.log("Interceptor - HttpResponse = " + event.status); // http response status code
}
}, error => {
// http response status code
if (error instanceof HttpErrorResponse) {
console.log("----response----");
console.error("status code:");
console.error(error.status);
console.error(error.message);
console.log("--- end of response---");
if (error.status === 401 || error.status === 403) //check if the token expired and redirect to login
this.router.navigate(['login']);
}
})
)
}
else {
return next.handle(request);
}
};
For changing the url, I created a service on file
http-client.service.ts and got the host url from environment.ts
import { Injectable } from "#angular/core";
import { HttpClient } from '#angular/common/http';
import { Observable } from "rxjs";
import { environment } from "../../../environments/environment";
#Injectable({ providedIn:'root' })
export class HttpClientService {
constructor(private http: HttpClient) { }
get(url: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.get(url, options);
}
post(url: string, body: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.post(url, body, options);
}
put(url: string, body: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.put(url, body, options);
}
delete(url: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.delete(url,options);
}
private updateUrl(req: string) {
return environment.host + req;
}
}
As i said, I believe this is the best approach, but feel free to add information to my question/answer.
Related
I'm using Chopper in my flutter app and what I need to do is, when I get 401 response status code (unauthorized) from my API, I must call another endpoint that will refresh my token and save it into secured storage, when all of this is done, I need to retry the request instantly (so that user cannot notice that his token expired). Is this dooable with Chopper only, or I have to use some other package?
It is possible. You need to use the authenticator field on the Chopper client, e.g.
final ChopperClient client = ChopperClient(
baseUrl: backendUrl,
interceptors: [HeaderInterceptor()],
services: <ChopperService>[
_$UserApiService(),
],
converter: converter,
authenticator: MyAuthenticator(),
);
And your authenticator class, should look something like this:
class MyAuthenticator extends Authenticator {
#override
FutureOr<Request?> authenticate(
Request request, Response<dynamic> response) async {
if (response.statusCode == 401) {
String? newToken = await refreshToken();
final Map<String, String> updatedHeaders =
Map<String, String>.of(request.headers);
if (newToken != null) {
newToken = 'Bearer $newToken';
updatedHeaders.update('Authorization', (String _) => newToken!,
ifAbsent: () => newToken!);
return request.copyWith(headers: updatedHeaders);
}
}
return null;
}
Admittedly, it wasn't that easy to find/understand (though it is the first property of the chopper client mentioned in their docs), but it is precisely what this property is for. I was going to move to dio myself, but I still had the same issue with type conversion on a retry.
EDIT: You will probably want to keep a retry count somewhere so you don't end up in a loop.
I searched couple of days for answer, and I came to conclusion that this is not possible with Chopper... Meanwhile I switched to Dio as my Networking client, but I used Chopper for generation of functions/endpoints.
Here is my Authenticator. FYI I'm storing auth-token and refresh-token in preferences.
class AppAuthenticator extends Authenticator {
#override
FutureOr<Request?> authenticate(Request request, Response response, [Request? originalRequest]) async {
if (response.statusCode == HttpStatus.unauthorized) {
final client = CustomChopperClient.createChopperClient();
AuthorizationApiService authApi = client.getService<AuthorizationApiService>();
String refreshTokenValue = await Prefs.refreshToken;
Map<String, String> refreshToken = {'refresh_token': refreshTokenValue};
var tokens = await authApi.refresh(refreshToken);
final theTokens = tokens.body;
if (theTokens != null) {
Prefs.setAccessToken(theTokens.auth_token);
Prefs.setRefreshToken(theTokens.refresh_token);
request.headers.remove('Authorization');
request.headers.putIfAbsent('Authorization', () => 'Bearer ${theTokens.auth_token}');
return request;
}
}
return null;
}
}
Based on this example: github
And Chopper Client:
class CustomChopperClient {
static ChopperClient createChopperClient() {
final client = ChopperClient(
baseUrl: 'https://example.com/api/',
services: <ChopperService>[
AuthorizationApiService.create(),
ProfileApiService.create(),
AccountingApiService.create(), // and others
],
interceptors: [
HttpLoggingInterceptor(),
(Request request) async => request.copyWith(headers: {
'Accept': "application/json",
'Content-type': "application/json",
'locale': await Prefs.locale,
'Authorization': "Bearer ${await Prefs.accessToken}",
}),
],
converter: BuiltValueConverter(errorType: ErrorDetails),
errorConverter: BuiltValueConverter(errorType: ErrorDetails),
authenticator: AppAuthenticator(),
);
return client;
}
}
I want to add headers to post request from Angular 5 web app.
I've created the following Injectable class, registered in my app module:
#Injectable()
export class headerInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Clone the request to add the new header
const authReq = req.clone({
headers: new HttpHeaders({
'Content-Type': 'application/json; charset=utf-8',
})
});
return next.handle(authReq);
}
}
I have network communication service and Im adding body parameters as below.
#Injectable()
export class NetworkService {
Root:string = 'some valid url';
results:Object[];
loading:boolean;
// inject Http client to our client service.
constructor(private httpClient: HttpClient ) {
}
login(loginParams : URLSearchParams){
let baseURL = `${this.Root}/login`;
this.httpClient.post(baseURL, JSON.stringify({'UserName':'123','Password':'123'} ))
.subscribe(data => {
console.log();
},
err => {
console.log('Error: ' + err.error);
});
when I put breakpoint after the clone method inside headerInterceptor class I can see the request body.
the problem is when I switch to network tab in chrome, I get 405 error and I can't see the body and the new headers. when I return the original request 'req'
the body sent ok but no new headers of course.
App Module,
import { HttpClientModule, HTTP_INTERCEPTORS } from '#angular/common/http';
import { headerInterceptor } from './httpinterceptor'
Add it to providers,
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: headerInterceptor,
multi: true
}
],
Now change the intereptor to, I have changed the way headers are added
#Injectable()
export class headerInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authReq = req.clone({
headers:req.headers.set("Content-Type", "application/json; charset=utf-8")
});
return next.handle(authReq);
}
}
}
You can also use setHeaders:
#Injectable()
export class headerInterceptor implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {y
req = req.clone({
setHeaders: {
"Content-Type": "application/json; charset=utf-8"
}
});
return next.handle(req);
}
}
My problem was on server side I'm using ASP.net and wcf.
it seems that the server must implement CORS support when working with Angular 5 project (works ok with jQuery and cross domain tag).
this post helped me to add support in CORS:
http://blogs.microsoft.co.il/idof/2011/07/02/cross-origin-resource-sharing-cors-and-wcf/
I'm making a web app that uses Axios.
I have a base service class that has an interceptor that will add the access token to each request. However, the interceptor wont add the token on my PATCH request, only on the GET request.
My base api service
import axios from 'axios'
export default class api {
constructor (path) {
this.api = axios.create({
baseURL: 'http://localhost:1337/' + path
})
this.api.interceptors.request.use(config => {
config.headers['x-access-token'] = localStorage.getItem('jwt-token')
return config
})
this.api.interceptors.response.use(undefined, (err) => {
if (err.response.status === 401) {
throw err
} else {
throw err
}
})
}
}
The service that extends the base
import Api from './api'
export default class ProfileService extends Api {
constructor () {
super('profile/')
}
me () {
return this.api.get('/me')
}
updateProfile (uuid, data) {
return this.api.patch('/' + uuid, data)
}
}
Axios does add the token to PATCH requests if I make a global interceptor like this:
axios.defaults.headers.common['x-access-token'] = localStorage.getItem('jwt-token');
I do not want to do this though, as it will send the token to other servers as well.
Any ideas on why my interceptor wont add the token when specified per instance? Is this a bug with Axios?
The OPTIONS-request has the following headers:
Access-Control-Allow-Headers:x-access-token,X-Requested-With,Content-Type,Accept,Authorization,Origin
Access-Control-Allow-Methods:GET,PUT,POST,PATCH,OPTIONS
Thanks in advance,
Axel
I have downloaded the following project: http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api. I run it on VS2015 and IIS express. The project is fine but i want to call the API with Angular 2.
So i have setup my project in Visual Studio Code and made a project in Angular 2 and TypeScript. When I try to post to the API method named Register, the values are null ?
My Visual Studio Code service(Angular2)
import { Injectable } from 'angular2/core';
import { Account } from '../account/Account';
import { RegisterBindingModel } from '../account/RegisterBindingModel';
import {Http, Response, Headers, RequestOptions} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/Rx';
#Injectable()
export class AccountService {
constructor(private _http: Http) {
}
createAccount(account: Account): Observable<string> {
console.log('accountService.createAccount');
let body = JSON.stringify({ account });
console.log('T1' + body);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this._http.post('https://localhost:44305/api/Account/Register', body, options)
.map(this.extractData)
.catch(this.handleError);
Browser error and post values:
Server API errors:
Error2_In_API_Method
I can do a GET operation, but all my POST operations are NULL ?
I found the following, not very effektiv, but working solution, where I map the account class objects to a new object. Then I stringify the new object and post it:
createAccount(account: Account): Observable<string> {
var obj = { Email: account.Email, Password: account.Password, ConfirmPassword: account.ConfirmPassword };
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
let body = JSON.stringify(obj);
return this._http.post('https://localhost:44305/api/Account/Register', body, options)
.map(this.extractData)
.catch(this.handleError);
}
For the first error I had on the API, i solved it by changing the following:
Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
To
HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
And then I could get the GetOwinContext() from the POST
I could use some guidens, sending an object from my angular 2 application to the Web API.
I know how to GET objects from the Web Api, to my angular 2 application, but can't seem to figure out how the post method works or even if I should use the http.post methodd.
My angular 2 application has the following method:
sendUpdatdReservation(updatedReservation: Reservation) {
var result;
var objectToSend = JSON.stringify(updatedReservation);
this.http.post('http://localhost:52262/api/postbookings', objectToSend)
.map((res: Response) => res.json()).subscribe(res => result = res);
console.log(result);
}
The "updatedReservation" is an object, which I convert to JSON.
The Web api can be reached by the following address:
httl://localhost:52262/api/postbookings
Web Api controller:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class PostBookingsController : ApiController
{
[AcceptVerbs()]
public bool ConfirmBooking(Booking booking)
{
return true;
}
}
What I'm trying to do is to send the object, update my database based on the changes values that the object has. Then send back true or false if this is a confirmation or not so I can redirect to confirmation page.
Do any know the unsupported media type error?, is that related to that the object i send is not what the api method expects?
Hope someone can help.
You need to set the Content-Type header when sending the request:
sendUpdatdReservation(updatedReservation: Reservation) {
var result;
var objectToSend = JSON.stringify(updatedReservation);
var headers = new Headers();
headers.append('Content-Type', 'application/json');
this.http.post('http://localhost:52262/api/postbookings', objectToSend, { headers: headers })
.map((res: Response) => res.json()).subscribe(res => {
this.result = res;
console.log(this.result);
});
}
Don't forget to import this class:
import {Http,Headers} from 'angular2/http';