can't access cookies on a rest response in angular - angularjs

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

Related

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

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';
}
});
};

Angular $http read String?

The back end guy wants to send me a string, which will be returned by $http.post. If he sends me something like "success", I will get error like "parsing Json failed". I want him to wrap the string into object like "{"message": "success"}", which works fine. But other back end guys say that front end should comply with back end, so they will just send me the string. Is there any way that I can read the string?
This is the code I use if he sends me "{"message": "success"}", which works perfectly:
AdminService.saveCache(cache)
.then(function(data) {
var data = data.data;
if (data.message == "success") {
scope.successMessage = "Cache hours saved successfully";
} else {
scope.errorMessage = data.message;
}
}, function() {
scope.errorMessage = "Submission failed";
});
By default angular tries to detect if a http response contains JSON. Sometimes this detection fails and you get such an error as you described in your question.
You can avoid this behavior for a single request if you override the response transformation by providing an transformResponse property for on the configuration object passed to the request:
$http({
url: '...',
method: 'POST',
//Just return original response from server without parsing
transformResponse: [function (data, headers) {
return data;
}];
});
Alternatively you can change the default behavior for all your app's http requests by overriding the default response transformation:
myApp.config('$httpProvider', function($httpProvider) {
$httpProvider.defaults.transformResponse = [function (data, headers) {
return data;
}];
}]);
For more information see API Reference $http section "Transforming Requests and Responses"
the API Response Content-Type should be set to text/plain; and use POSTMAN to verify, it would save you a lot of headache

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;
});

Getting 401 error when call $http POST and passing data

I am calling a Web API 2 backend from an angularjs client. The backend is using windows authentication and I have set up the $httpProvider to use credentials with all calls and it works fine for all GETS.
Like this:
$httpProvider.defaults.withCredentials = true
But, when using the POST verb method AND passing a data js object I get a 401 error. If I remove the data object from the $http.post call it reaches the endpoint but I would like to pass up the data I need to save.
Here's an example of the client-side call:
var saveIndicator = function (indicator) {
var req = {
method: 'POST',
url: baseUrl + "/api/indicators",
data: indicator
};
return $http(req).then(function (response) {
return response.data;
});
};

angular $http not returning .error() variables if CORS headers are missing from the response

I'm executing an $http.get over websockets and somehow the .error doesn't populate any of the parameters. I'm using angular 1.3.5 and latest Chrome on OSX and local host is aliased to mywebsite.com.
$http({method: 'GET', 'ws://mywebsite.com/resource'})
.success(function(response){ ... })
.error(function(err, status){
console.log(err); <<< here err and status are null respectively 0 even the XHR Response logs 401 in console.
});
Any clues why this is doing so? No mater what error code it is, it doesn't get passed to error callback. For 2xx response i do get the data and all is fine.
Just to clarify, the .error callback gets called as normal, but err and status re not populated.
Well, well, I discovered what was happening. A combo of things.
First I'm using KOA and the above angular hits the KOA as REST API. I'm also using koa-jwt to auth the users and generate a token.
Now the api runs on a subdomain and even i set the CORS via koa-cors to allow * access.
The issue is that koa-jwt when the token is expired, they simply do a this.throw(401). The way KOA handles that it so immediately terminate subsequent middleware and exit with that error. THAT, didn't allow koa-cors headers to be set, regardless where I put that middleware (before or after koa-jwt).
Hence, the elegant fix was to wrap my top level yield next in a try catch and avoid allowing koa-jwt ctx.throw to propagate.
On Angular side, the browser was refusing to convey the 401 to the .error complaining it didn't find a suitable CORS to allow it to process and hand over the response :).
app.use(cors({origin:"*", methods:'GET,HEAD,PUT,POST,DELETE,PATCH'})); //allow all origins to the API.
app.use ( function *(next){
try {
yield next;
} catch (err) {
this.status = err.status || 500;
this.body = err.message;
this.app.emit('error', err, this);
}
});
... more middleware
// middleware below this line is only reached if jwt token is valid
app.use(jwt({secret: config.app.secret}));
Allowing a this.trow(401) from koa-jwt will ruin your day.
I think you just make a typo, just write like this:
$http({method: 'GET', 'ws://mywebsite.com/resource', headers: headers})
.success(function(response){ ... })
.error(function(err, status){
console.log(err);
});
However as a best practice I would do as
var request = {
method: 'GET',
url: 'ws://mywebsite.com/resource',
headers: {//insert here header you want, put Origin header only for example purposes
Origin: 'ws://mywebsite.com/resource'
}
}
$http(request)
.success(function(response){ ... })
.error(function(err, status){
console.log(err);
});
If the typo is not the error (you may copied the code) just look at this AngularJS $http error function never called

Resources