I'm trying to consume an ApiRest, but because they added token validation to it, I can't consume it. Before they added that validation, if I managed to consume it correctly.
Since the token has to be sent, I can't consume it. The error is the following:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'localhost:8100'; is therefore not allowed access. Le agregué los HttpHeaders –
When testing the API in postman, it works correctly.
Currently the rest.ts file is as follows:
import { HttpClient, HttpHeaderResponse, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpHeaders } from '@angular/common/http';
/*
Generated class for the RestProvider provider.
See https://angular.io/guide/dependency-injection for more info on providers
and Angular DI.
*/
@Injectable()
export class RestProvider {
apiUrl = 'http://localhost:49533/api/Login';
constructor(public http: HttpClient) {
console.log('Hello RestProvider Provider');
}
logIn(username: string, password: string, pasword2: string) {
const url = 'http://localhost:49533/api/Login';
const body = JSON.stringify({
Codigo: username,
Usuario: password,
Password: pasword2
});
//let headers = new HttpHeaders();
const headers = new HttpHeaders().set("X-CustomHttpHeader", "CUSTOM_VALUE")
.set('Access-Control-Allow-Origin', '*')
.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT')
.set('Accept', 'application/json')
.set('Content-Type', 'application/json; charset=utf-8');
this.http.post(url, body, { headers: headers }).subscribe(
(data) => {
console.log(data);
},
(err: HttpErrorResponse) => {
if (err.error instanceof Error) {
console.log('Client-side error occured.');
} else {
console.log('Server-side error occured.');
}
}
);
}
/*getUsers() {
return new Promise(resolve => {
this.http.get(this.apiUrl + '/api/Login/123/123/123').subscribe(data => {
resolve(data);
console.log(data);
}, err => {
console.log(err);
});
});
}*/
addUser(data) {
return new Promise((resolve, reject) => {
this.http.post(this.apiUrl + '/users', JSON.stringify(data))
.subscribe(res => {
resolve(res);
}, (err) => {
reject(err);
});
});
}
}
Where I call getUsers():
import { RestProvider } from '../../providers/rest/rest';
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
users: any;
constructor(public navCtrl: NavController, public restProvider: RestProvider) {
this.logIn();
//this.getUsers();
}
logIn(){
this.restProvider.logIn("1234","1234","123123")
}
/*getUsers() {
this.restProvider.getUsers()
.then(data => {
this.users = data;
console.log(this.users);
});
}*/
}
Checking out the Rest API in C#:
public class TokenValidationHandler : DelegatingHandler
{
private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
token = null;
IEnumerable<string> authzHeaders;
if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
{
return false;
}
var bearerToken = authzHeaders.ElementAt(0);
token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
return true;
}
}
I checked that it carries the request.Headers.tryGetValues and it carries the following:
{Method: OPTIONS, RequestUri: 'http://localhost:49533/api/Login', Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent, Headers:
{
Connection: keep-alive
Accept: */*
Accept-Encoding: gzip
Accept-
Encoding: deflate
Accept-Encoding: br
Accept-Language: es-ES
Accept-Language: es; q=0.9
Host: localhost:49533
User-Agent: Mozilla/5.0
User-Agent: (Linux; Android 5.0; SM-G900P Build/LRX21T)
User-Agent: AppleWebKit/537.36
User-Agent: (KHTML, like Gecko)
User-Agent: Chrome/69.0.3497.100
User-Agent: Mobile
User-Agent: Safari/537.36
Access-Control-Request-Method: POST
Origin: http://localhost:8100
Access-Control-Request-Headers: access-control-allow-methods,access-control-allow-origin,content-type
ApplicationInsights-RequestTrackingTelemetryModule-RootRequest-Id: 087a23b9-5473-47db-9b86-bb90f7a1cffa
}}
And from there it no longer happens in the Api rest.
The full token validation handler:
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace Ws
{
public class TokenValidationHandler : DelegatingHandler
{
private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
token = null;
IEnumerable<string> authzHeaders;
if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
{
return false;
}
var bearerToken = authzHeaders.ElementAt(0);
token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
return true;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpStatusCode statusCode;
string token;
//determine whether a jwt exists or not
if (!TryRetrieveToken(request, out token))
{
statusCode = HttpStatusCode.Unauthorized;
//allow requests with no token - whether a action method needs an authentication can be set with the claimsauthorization attribute
return base.SendAsync(request, cancellationToken);
}
try
{
const string sec = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
var now = DateTime.UtcNow;
var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));
SecurityToken securityToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidAudience = "http://localhost:49533",
ValidIssuer = "http://localhost:49533",
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
LifetimeValidator = this.LifetimeValidator,
IssuerSigningKey = securityKey,
ValidateAudience = false,
ValidateIssuer = false
};
//extract and assign the user of the jwt
Thread.CurrentPrincipal = handler.ValidateToken(token, validationParameters, out securityToken);
HttpContext.Current.User = handler.ValidateToken(token, validationParameters, out securityToken);
return base.SendAsync(request, cancellationToken);
}
catch (SecurityTokenValidationException e)
{
statusCode = HttpStatusCode.Unauthorized;
}
catch (Exception ex)
{
statusCode = HttpStatusCode.InternalServerError;
}
return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode) { });
}
public bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
if (expires != null)
{
if (DateTime.UtcNow < expires) return true;
}
return false;
}
}
}
Implementing @sioesi's help:
The ionic project runs at: http://localhost:8100/ and the rest api at http://localhost:49533/
In this case the ionic.config.json :
{
"name": "ionic3-angular43-rest",
"app_id": "",
"type": "ionic-angular",
"integrations": {},
"proxies": [
{
"path": "/api",
"proxyUrl": "http://localhost:49533"
}
]
}
and the login method:
return this.http.post('/api/api/Login', body, { headers: headers }).subscribe(
(data) => {
console.log(data);
},
(err: HttpErrorResponse) => {
if (err.error instanceof Error) {
console.log('Client-side error occured.');
} else {
console.log('Server-side error occured.');
}
}
With this last change, it worked correctly. Thank you very much for your help.
ionic.config.json
Try to implement the fix in the fileproxies
And when you make the call you just concatenate:
And the operation remains as you have it, when you use the word that you define in the
path
, it will automatically understand that it must access the url that you define and the call will be executed as if it were made from the same location.The problem is clearly the cors and not your backend, this answer works only when you test on the web, with a
ionic serve
, from the mobile application you will not have problems.