Fixing 3rd party API 'Access-Control-Allow-Origin' error - angularjs

I have an Angular application, currently an HTML file I open but it will soon be converted to a server/accessed through localhost. I use $http to access a 3rd party API (I have no control over its responses; most of the API calls work, but some don't and throw the error:
XMLHttpRequest cannot load http://api.nal.usda.gov/ndb/search/?format=json&api_key=RJEnADgGbCjfJYi0z8vuVnelYXn2Smud2Dfi2u2F&q=susage. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 404.
The API calls that throw that error are the searches that return 0 results (in the example, the database returns no results for "susage"). In the Network tab, the response can't be loaded and no response headers are listed; for other working API calls, under Response Headers, the necessary "Access-Control-Allow-Origin:*" is present.
The API definitely forms a response and tries to send it back, but fails for whatever reason. Visiting the posted url shows that response.
Why do only the empty searches throw the error when API understands and has a response for both calls, and how do I fix it? I would prefer that my frontend communicates with the API directly, as opposed to communicating with my backend which in turn communicates with the API.
For comparison, a search with results (spelling 'sausage' correctly) vs a search without results: http://imgur.com/a/ihhI1
The $http code:
return $http.get('http://api.nal.usda.gov/ndb/search/?format=json', {
params: {
api_key: usdaKey,
q: query
}
})

In order to expose the "-1" status (timeout) to the UI, I changed the service method to simply return the promise, like this:
var search = function(query) {
return $http.get('http://api.nal.usda.gov/ndb/search/?format=json', {
params: {
api_key: usdaKey,
q: query
}
});
};
Then you can handle the error in your controller, like this:
$scope.search = function(query) {
$scope.items = [];
$scope.err = null;
foodInfo.search(query).then(function(response) {
console.log(response);
$scope.items = response.data.list.item;
}, function(e) {
console.log('Error status: ' + e.status);
if (e.status === -1) {
$scope.err = 'No data found';
}
});
};

Related

How to send an HTTP GET request using Firebase and Angular?

My app uses IBM Watson Speech-to-Text, which requires an access token. From the command line I can get the access token with curl:
curl -X GET --user my-user-account:password \
--output token \
"https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api"
When I make an HTTP request using Angular's $http service I get a CORS error:
var data = {
user: 'my-user-account:password',
output: 'token'
};
$http({
method: 'GET',
url: 'https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api',
data: data,
}).then(function successCallback(response) {
console.log("HTTP GET successful");
}, function errorCallback(response) {
console.log("HTTP GET failed");
});
The error message says:
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://127.0.0.1:8080' is therefore not allowed
access. The response had HTTP status code 401.
As I understand, it's not possible to do CORS from Angular; CORS has to be done from the server. I know how to do CORS with Node but I'm using Firebase as the server.
Firebase has documentation about making HTTP requests with CORS. The documentation says to write this:
$scope.getIBMToken = functions.https.onRequest((req, res) => {
cors(req, res, () => {
});
});
First, that doesn't work. The error message is functions is not defined. Apparently functions isn't in the Firebase library? I call Firebase from index.html:
<script src="https://www.gstatic.com/firebasejs/4.3.0/firebase.js"></script>
My controller injects dependencies for $firebaseArray, $firebaseAuth, and $firebaseStorage. Do I need to inject a dependency for $firebaseHttp or something like that?
Second, how do I specify the method ('GET'), the URL, and the data (my account and password)?
if you want to send credentials with angular, just set withCredentials=true. I am also using CORS with Angular v4, for your HTTP header error, you are right. Header Access-Control-Allow-Origin must be added on server side, check if you have settings in your api to allow certain domains, urls, pages, because google api's has this function, so check where you get token there should be some settings.
Here is example, how I am calling API with CORS, using typescript:
broadcastPresense(clientId: string) {
const headers = new Headers({'Content-Type':'application/json','withCredentials':'true'});
return this.http.post('http://localhost/api.php',
{
'jsonrpc': '2.0',
'method': 'somemethod',
'params': {'client_id': clientId},
'id': CommonClass.generateRandomString(16)
},{headers: headers, withCredentials:true}).map(
(res: Response) => {
console.log(res);
const data = res.json();
console.log(data);
if (data.error == null) {
return data.result;
} else if (data.error != null) {
throw data.error;
}
throw data.error;
}
).catch(
(error) => {
this.router.navigate(['/error',3],{queryParams: {desc:'Server error'}});
return Observable.throw(error);
}
);
}
Hope it helps :)
The answer is to use Cloud Functions for Firebase, which enable running Node functions from the server. Then you use the Node module request to send the HTTP request from Node.

$http.get pass encodeURIComponent to Web API

I want to pass some special characters( _ + - ) to my web api, now I know I have to use encodeURIComponent() in javascript which does encode them. But when I pass the encoded url to http.get it is throwing an exception:
The get request code:
find: function (id) {
var scope = this;
var urlApi = webapi_base_url + model_url;
id = encodeURIComponent(id);
return $http.get(urlApi + id)
.then(function (response) {
if (response.data.success && response.data.exists) {
//logger.info('Successfully found model', '', 'Success');
}
else {
if (showLogger)
logger.error(response.data.error, '', 'Error');
}
return response.data;
})
.catch(function (err) {
scope.WebFailed('get', err);
})
}
For example I want to pass the values a+ to my web api, the url is:
https://localhost:44310/api/v1/model/a%26
The following error is thrown:
XMLHttpRequest cannot load https://localhost:44310/api/v1/model/a%26. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:59242' is therefore not allowed access. The response had HTTP status code 400.
When I send the value of 'a' to the web API it is fine and no problems:
The web api code
[Route("{description}")]
public async Task<IHttpActionResult> Get([FromUri] string description)
{
Model model = await _repository.Model().Get().Where(x => x.Description == description).FirstOrDefaultAsync();
if (model == null)
return Ok(new { success = false, exists = false, error = "Could not find model" });
return Ok(new { success = true, exists = true });
}
I think when I have encoded the uri the web api cannot find the path and is throwing an exception.
Any ideas how I can fix the web api to allow encoded characters.
Thanks

can't access cookies on a rest response in angular

I have an angular front end with a webapi back end. I have implemented OAuth v2 security using OWIN/Identity and JWT tokens (thanks to Taiseer Joudeh's blogs). My burden is that we still have legacy pages that require a specific cookie. I have augmented the Http Response from WebApi to include that cookie when the JWT token is returned from a login request. I have verified the cookie is in the response header.
My problem is that I am unable to see the cookie inside my angular response handler where I will push it to the browser. I have tried each of the following based on suggestions I found elsewhere within StackOverflow but so far visibility of the cookie within the .js code has eluded me (alternate attempts have been commented out but left in for completeness). I have also made sure I set the appropriate "allow" fields on the server by adding "Access-Control-Allow-Headers" to "set-cookie" and "Access-Control-Allow-Credentials" to "true" at the end of my ValidateClientAuthenticationContext(..) method.
What do I need to do to see the attached cookie on my webapi response? Is this a problem on the server or client? both?
in my authService.js file:
var _login = function (loginData) {
// this makes the data "form data"
var data = "grant_type=password&client_id=ngAuthApp&username=" + loginData.userName + "&password=" + loginData.password;
var deferred = $q.defer();
$http.post(serviceBase + 'oauth/token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
.success(function (response) {
localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName });
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
console.log($cookies);
//var xxx = $http.defaults.headers;
//var headers = $http.response.headers;
var ddc = $http.response.cookies;
$cookies.DDC = ddc;
deferred.resolve(response);
})
//.success(function (data, status, headers, config) {
// // any required additional processing here
// var results = [];
// results.data = data;
// results.headers = headers();
// results.status = status;
// results.config = config;
// deferred.resolve(results);
//})
.error(function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;
};
in my custom OAuthProvider .cs file
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// skipping over lots of code here
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "authorization", "content-type", "set-cookie" });
context.Validated();
return Task.FromResult<object>(null);
}
According to the docs - see here
$http.post() method returns an HttpPromise future object. Your call to .post() returns a promise. Which according to the Deprecation Notice on the above referenced page :
The $http legacy promise methods success and error have been
deprecated. Use the standard then method instead. If
$httpProvider.useLegacyPromiseExtensions is set to false then these
methods will throw $http/legacy error.
So instead of .success() / error(), use this: (Copied from docs)
$http.post()
.then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
Also, if you haven't already tried this (according to the .post() call it doesn't appear to) set the responseType property of your $http configuration object. This sets the datatype of the response object returned. Otherwise the default of a DOM string is returned. It may not fix it but it could be a start.
This could also need the help of withCredentials property set. Test them out and see how it goes. Idea comes from the suggestion of bastijn.
Your $http call should also set the withCredentials flag to true to explicitly allow cookie sharing.
$http.post(url, {withCredentials: true, ...})
The withCredentials flag allows javascript to access the authenticated session of the user.
//edit
Now that I read your question again this is probably not your issue. The withCredentials is to,communicate your session to the server on the next request you make that requires the authenticated session. From your question it seems you want to validate in the js code that the cookie you verified is there is also reachable by code.
It turns out the error was in my assumptions. I expected that a cookie sent via a web service directly from embedded js code would be ignored by the browser. However, the response header has the "Set-Cookie" value in the header and the browser IS already pushing it to be with the rest of the cookies. I really didn't expect that.
I must add this has been a very useful question for me because it taught me a lot about web programming and how the browser works with http header values. I appreciate everyone's time!
Marcus

API-key header is not sent (or recognized). Angularjs

I'm trying to access an API with AngularJS but I get the following error:
XMLHttpRequest cannot load http://www.football-data.org/alpha/soccerseasons/398/leagueTable?callback=JSON_CALLBACK. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://purepremier.com' is therefore not allowed access.
This is my code for the service:
angular.module('PremierLeagueApp.services', []).
factory('footballdataAPIservice', function($http) {
var footballdataAPI = {};
footballdataAPI.getTeams = function() {
$http.defaults.headers.common['Auth-Token'] = 'token';
return $http.get('http://www.football-data.org/alpha/soccerseasons/398/leagueTable?callback=JSON_CALLBACK');
};
return footballdataAPI;
});
I use an authentication token (api key) to access the api, but according the API owner this API key header is not sent or recognized. Do you have any idea how I can adapt the code to make this work? thanks!
You should hide that API key before posting on a public site such as this. I would advise you regenerate your key (if possible) just in case - better safe than sorry.
Assuming your site url is 'http://purepremier.com' from the error message, the API should add a 'Access-Control-Allow-Origin' header with your site URL to allow you access. Have a look here for more information.
This is not directly related to your problem, but I notice you are setting $http defaults every time getTeams() is called. You should either set this outside of the actual function call (preferably in a run block), or just send the GET request with that header specifically applied. As the API key is specific (I assume) to that call, you may not want to be sending it to anyone and everyone, every time you make a HTTP request.
Change your factory code like this:
factory('footballdataAPIservice', function($http) {
return {
getTeams: function(){
return $http({
url:'http://www.football-data.org/alpha/soccerseasons/398/leagueTable',
headers: { 'X-Auth-Token': 'your_token' },
method: 'GET'
}).success(function(data){
return data;
});
}
}
});
Inject factory in your controller and retreive the data:
.controller('someController',function(footballdataAPIservice,$scope){
footballdataAPIservice.getTeams().then(function(data){
$scope.teams=data;
console.log($scope.teams)
});
});
Here is the working plunker
You change the Auth-Token To Authorization
$http.defaults.headers.common['Authorization'] = 'token';
Because token is send via headers using Authorization
try jsonp
angular.module('PremierLeagueApp.services', []).
factory('footballdataAPIservice', function($http) {
var footballdataAPI = {};
footballdataAPI.getTeams = function() {
$http.defaults.headers.common['Auth-Token'] = 'token';
return $http.jsonp('http://www.football-data.org/alpha/soccerseasons/398/leagueTable?callback=JSON_CALLBACK');
};
return footballdataAPI;
});

angular $resource receive extra information

I am using ng-resource to do ajax request. I want to send extra info besides the data.
For example, I have an article entity on my server
exports.fetchArticle = function(req, res, next) {
var article = req.article
return res.json({data: article, message: 'success fetch article'})
}
The reason I wrap it is that, in the case of deletion, it makes no sense to send data, I can just return res.json({data: null, message: 'deleted successfully'})
on my client side, I have:
$scope.fetchArticle = function() {
Article.get({articleId: $routeParams.articleId}, function(response) {
$scope.article = response.data
$scope.ajaxSuccess = response.message
}, function(err) {
$scope.ajaxError = err.data.message
})
}
$scope.article is not an instance of ng-resource anymore, thus I can't do further request with $scope.article, i.e. this will cause error, since $scope.article is a plain json object:
$scope.article.$update(function(response) {...})
If I simply return res.json(article) from server, it works, but I can't send along the message.
The reason I dont generate the message from client but fetch from server is that, the error message is from server, I want to keep success message consistent with the error message.
Is there any other elegant way to send the message?
Assuming that all your servers responses follow this format:
{
data: {/*...*/},
message: 'some message'
}
You could use $http's transformResponse for that, so that you get an ngResource instance that is your returned object while still processing your message. For that, you need a transform-function:
function processMessage(data, message) {
//Do whatever you want with your message here, like displaying it
}
function transform(response) {
processMessage(response.data,response.message);
var data = response.data;
delete response.data;
delete response.message;
for(var attributeName in data) {
response[attributeName] = data[attributeName];
}
return response;
}
Then you can add this function to $http's default transfroms in the config of your app:
angular.module("yourApp",[/* ... */])
.config(function($httpProvider){
//....all your other config
$httpProvider.defaults.transformResponse.unshift(transform);
});
Now all repsonses from $http get transformed by this function, triggering processMessage and leaving you with a ngResource instance of the returned object.

Resources