Angular2 httponly cookie not saved when adding custom http headers - ionic2

I've came to dead end with implementing and handling CORS issues between Angular2 application and Java backend API.
I am aware and know what are CORS requests, have implemented already in angular1 apps, but I can't make it work in Angular2 (Ionic2) app.
The ionic 2 app should consume an existing API web service, which already have enabled everything for making CORS requests and the authentication is done with Http only cookie... already implemented in Angular 1.
For some reason when we are setting headers in the request, Angular2 set "withCredentials" flag to false or not send at all, and the request is made to the api but the cookie is not returned... and we can't make authenticated API calls.
I've tried many, many things to send together http headers and "withCredentials" flag together, but nothing seems to work in Angular2, while in Angular1 works fine. All headers that we are sending are allowed by the server and they are returned in "Access-Control-Allow-Headers" in the response.
Funny thing is that if I try to send requests with "withCredentials" only flag, the requests are send ok and the cookie is set correctly by the browser.
I've tried following things:
Create custom http class, like described here:
http://www.adonespitogo.com/articles/angular-2-extending-http-provider/
Extending the like BrowserXhr class and setting "withCredentials" to true like described here: Angular 2 - http get withCredentials
Setting directly in the RequestOptions like here:
angular 2 http withCredentials
Extend BaseRequestOptions (RequestOptions) like here:
Angular2 - set headers for every request
Example Code:
import { Injectable } from '#angular/core';
import { Headers, RequestOptions } from '#angular/http';
#Injectable()
export class DefaultRequestOptions extends RequestOptions {
withCredentials:boolean = true;
// headers:Headers = new Headers({
// 'Accept': 'application/json, text/plain, */*',
// 'Authorization': 'sometoken',
// });
constructor() {
super();
if(!this.headers){
this.headers = new Headers();
}
this.headers = new Headers();
this.headers.append('Accept','application/json');
this.headers.append('CustomHeader',"someTokenGoesHere");
this.withCredentials = true;
}
}
Later in module:
#NgModule({
declarations: [
MyApp
],
imports: [
IonicModule.forRoot(MyApp,{tabsHideOnSubPages:true})
],
bootstrap: [IonicApp],
entryComponents: [
MyApp
],
providers: [
{ provide: RequestOptions, useClass: DefaultRequestOptions }
]
})
Also tried like this:
let headers = new Headers();
headers.append("Accept",'application/json');
headers.append("CustomHeader",'someTokenGoesHere');
let options = new RequestOptions({ headers: headers, withCredentials: true});
this.http.get('apiurl',options).subscribe((res)=>{ console.log(res)})
If anyone has successfully combined http headers and withCredentials flag, please share your experience.
UPDATE:
I didn't found solution for this problem, so we decided to remove some security headers and make ip address filtering for requests. In this way we don't need to send any extra http headers and continue using httponly cookies.

You can add headers in options object of the http request
let headers: Headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
});
let options = {
method: RequestMethod.Post,
headers: headers,
responseType: ResponseContentType.Blob,
withCredentials: true
};
return this.http
.post(API + `/user/${id}`, {}, options)
.map((r: Response) => {
});

Related

How to call a server from localhost VueJS with Axios?

I'm trying to call an AWS hosted API from my VueJS app, which is running on my localhost:8080. I have used this blog post to setup the vue.config.js with this block:
module.exports = {
devServer: {
proxy: 'https://0123456789.execute-api.eu-west-1.amazonaws.com/'
},
...
}
With this in place, I can use this code to make a GET request to an endpoint at that host:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/mock/api/endpoint',
{
headers: {
'Content-Type': 'application/json'
}})
This is because I have configured the AWS API Gateway mock endpoint to return these headers for the OPTIONS method:
Access-Control-Allow-Headers: 'Cache-Control,Expires,Pragma,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
Access-Control-Allow-Methods: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'
Access-Control-Allow-Origin: '*'
However, I cannot make this call:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
This endpoint is a Lambda integration and also has an OPTIONS method with the same headers as above.
Why should both endpoints, configured the same way, have different responses for axios?
UPDATE
As advised by #deniz, I have updated the .env.development file to contain:
VUE_APP_API_URI=https://0123456789.execute-api.eu-west-1.amazonaws.com/
I have also updated the axios requests to:
let url = 'mock/api/endpoint'
let headers = {
headers: {
'Content-Type': 'application/json',
},
}
this.$axios
.get(url, headers)
...and...
let url = 'lambda/api/function'
let headers = {
headers: {
'Content-Type': 'application/json',
},
}
this.$axios
.get(url, headers)
The result I get for the first GET request is:
200 OK
However the second request's response is:
Access to XMLHttpRequest at 'https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Your config for your dev env. as a proxy setup is doing nothing else then pretend to be someone else.
Thats why you dont get any CORS issues when you work with a proxy. its a kinda bottleneck which acts like "i am someone else, not localhost"
module.exports = {
devServer: {
proxy: 'https://0123456789.execute-api.eu-west-1.amazonaws.com/'
},
...
}
from now on all your requests came from this very proxy based URL
https://0123456789.execute-api.eu-west-1.amazonaws.com/
if you try to access the api like this:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
you should keep in mind that you are already pretend that your proxy is doing his desguise stuff and still acts like its from a other source.
your URL when you call the API looks like this now, if i am not completely wrong:
https://0123456789.execute-api.eu-west-1.amazonaws.com/https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function
all you have to do is change the axios url in your request to:
this.$axios
.get('lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
and try again.
UPDATE
VUE_APP_API_URI=https://0123456789.execute-api.eu-west-1.amazonaws.com/
wrap your URL string into quotes, like this and remove the last slash.
VUE_APP_API_URI='https://0123456789.execute-api.eu-west-1.amazonaws.com'
thats a common practice to handle .env vars.
2.
the CORS error you get is a result of not using proxy anymore.
your requesting data from a other source now and this is no allowed on modern browsers like FireFox or Chrome etc.
here you have to handle the server side configs in your API:
https://0123456789.execute-api.eu-west-1.amazonaws.com
because if you go like that you need to give your localhost and your backend the permission to handle requests if the requests are made from different sources, like in your case:
i am localhost and i request data from https://0123456789.execute-api.eu-west-1.amazonaws.com
normally this is forbidden and is a highly risk on security
But the solution is...
As you did before in your AWS API
Access-Control-Allow-Origin: '*' is the important part which handles your "CORS" issues.
make sure it is setup correct and works as intended. maybe play around with that and set localhost instead of * (allow for all)
3.
i highly recommend you to use the proxy way on development and use the non proxy way only for production, and just allow CORS for your frontend only.

Fetch Data from REST API sends two requests and can't authenticate

I'm trying to fetch data in my React app from an Django server with Django Rest Framework, I'm using the built in token authentication.
componentDidMount() {
let headers = {
"content-type": "application/json",
"authorization": "Token <token is here>"
};
fetch('http://localhost:8000/api/stats/', {
headers: headers,
})
.then(res => res.json())
.then((data) => {
this.setState({ games: data })
})
.catch(console.log)
}
Inspecting the page in Chrome reveals this
This looks like two separate requests but I'm not sure.
The requests that has status (failed) has the headers I provided in React with in it. This request seems to have failed completely, it did not even reach the server.
The other request that has the status 401 doesn't have the headers I provided. The request did however get a response from the server.
Anyone have an idea what's wrong?
Solved by David Nováks comment:
Installed django-cors-headers in my django project.
Added my React servers hosting address to CORS_ORIGIN_WHITELIST.

get sharepoint list items via http request in typescript app

I have a sharepoint list(User). I am able to get these data with simple http request in browser.here is the out put of request:
all items retrieving as you see below and I am trying to achieve this in typescript service here is my code piece;
getUsers() : Observable<Users[]> {
var headers = new Headers();
headers.append('accept', 'application/json;odata=verbose');
return this.http_.get("https://yeneryilmaz1-3200ef41d84877.sharepoint.com/Sharepoint-TS-Angular2/_api/web/lists/getByTitle('User')/items",{headers})
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));
}
but it always throws exception.. how can I get list items via http request and also I know i need to add access token of from sharepoint account how can I add them to headers ?
EDITED:
response of the server for this request in browser console: "GET https://yeneryilmaz1-***.sharepoint.com/Sharepoint-TS-Angular2/_api/web/lists/getByTitle('User')/items 403 (Forbidden)"
so it seems authentication issue i encounter, so.. how can I embed access tokens to this request
You are very close. You need to add the authorization in the header too.
Make sure you are importing 'Headers' from http.
import { Http, Response, RequestOptionsArgs, Headers } from '#angular/http';
The call:
getUserData(): Observable<IUsers[]> {
var _headers = new Headers();
_headers.append('Content-Type', 'application/json');
_headers.append('Authorization', 'Bearer ' + access_token_here);
return this._http.get(this._protocolUrl, {
headers: _headers
})
.map((res: Response) => <IUser[]> res.json())
.do(data => console.log('All: ' + JSON.stringify(data)))
}

Google Cloud Functions enable CORS?

I just finished the Hello World Google Cloud Functions tutorial and received the following response headers:
Connection → keep-alive
Content-Length → 14
Content-Type → text/plain; charset=utf-8
Date → Mon, 29 Feb 2016 07:02:37 GMT
Execution-Id → XbT-WC9lXKL-0
Server → nginx
How can I add the CORS headers to be able to call my function from my website?
here we go:
exports.helloWorld = function helloWorld(req, res) {
res.set('Access-Control-Allow-Origin', "*")
res.set('Access-Control-Allow-Methods', 'GET, POST');
if (req.method === "OPTIONS") {
// stop preflight requests here
res.status(204).send('');
return;
}
// handle full requests
res.status(200).send('weeee!);
};
then you can jquery/whatever it as usual:
$.get(myUrl, (r) => console.log(r))
I'm the product manager for Google Cloud Functions. Thanks for your question, this has been a popular request.
We don't have anything to announce just yet, but we're aware of several enhancements that need to be made to the HTTP invocation capabilities of Cloud Functions and we'll be rolling out improvements to this and many other areas in future iterations.
UPDATE:
We've improved the way you deal with HTTP in Cloud Functions. You now have full access to the HTTP Request/Response objects so you can set the appropriate CORS headers and respond to pre-flight OPTIONS requests (https://cloud.google.com/functions/docs/writing/http)
UPDATE (2022):
Just noticed there was a question about docs, and our docs have moved. Updated docs for CORS are here:
https://cloud.google.com/functions/docs/samples/functions-http-cors
You can use the CORS express middleware.
package.json
npm install express --save
npm install cors --save
index.js
'use strict';
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors')({origin: true});
const app = express();
app.use(cors);
app.get('*', (req, res) => {
res.send(`Hello, world`);
});
exports.hello = functions.https.onRequest(app);
I've just created webfunc. It's a lightweight HTTP server that supports CORS as well as routing for Google Cloud Functions. Example:
const { serveHttp, app } = require('webfunc')
exports.yourapp = serveHttp([
app.get('/', (req, res) => res.status(200).send('Hello World')),
app.get('/users/{userId}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}`)),
app.get('/users/{userId}/document/{docName}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}. I like your document ${params.docName}`)),
])
In your project's root, simply add a appconfig.json that looks like this:
{
"headers": {
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, POST",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
"Access-Control-Allow-Origin": "*",
"Access-Control-Max-Age": "1296000"
}
}
Hope this helps.
In the python environment, you can use the flask request object to manage CORS requests.
def cors_enabled_function(request):
if request.method == 'OPTIONS':
# Allows GET requests from any origin with the Content-Type
# header and caches preflight response for an 3600s
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '3600'
}
return ('', 204, headers)
# Set CORS headers for the main request
headers = {
'Access-Control-Allow-Origin': '*'
}
return ('Hello World!', 200, headers)
See the gcloud docs for more.
You need to send an 'OPTIONS' response by setting its header as follows:
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', '*');
res.set('Access-Control-Allow-Headers', '*');
res.status(204).send('');
}
Runtime: NodeJS 10
If you tried the accepted answer but encountered a preflight error, the docs offer examples of handling it in multiple languages, with the caveat that it only works on public functions, i.e. deployed with --allow-unauthenticated:
exports.corsEnabledFunction = (req, res) => {
res.set("Access-Control-Allow-Origin", "*");
if (req.method === "OPTIONS") {
/* handle preflight OPTIONS request */
res.set("Access-Control-Allow-Methods", "GET, POST");
res.set("Access-Control-Allow-Headers", "Content-Type");
// cache preflight response for 3600 sec
res.set("Access-Control-Max-Age", "3600");
return res.sendStatus(204);
}
// handle the main request
res.send("main response");
};
Another option is to use Express as shown in this post, complete with cross-origin enabled.
You must enable CORS within all your functions, for example hello function:
index.js
const cors = require('cors')();
// My Hello Function
function hello(req, res) {
res.status(200)
.send('Hello, Functions');
};
// CORS and Cloud Functions export
exports.hello = (req, res) => {
cors(req, res, () => {
hello(req, res);
});
}
Don't forget about package.json
package.json
{
"name": "function-hello",
"version": "0.1.0",
"private": true,
"dependencies": {
"cors": "^2.8.5"
}
}
After applying your favourite answer from here, if you're still getting this error, check for uncaught errors in your cloud function. This can result in the browser receiving a CORS error, even when your error has nothing to do with CORS
After CORS enabled if you send POST request to your function also check for your request Content-Type header, mine was set it to "text/plain" and my browser was constantly triggering CORS errors, after setting the header to "application/json" everything worked properly.

Fetch API with Cookie

I am trying out the new Fetch API but is having trouble with Cookies. Specifically, after a successful login, there is a Cookie header in future requests, but Fetch seems to ignore that headers, and all my requests made with Fetch is unauthorized.
Is it because Fetch is still not ready or Fetch does not work with Cookies?
I build my app with Webpack. I also use Fetch in React Native, which does not have the same issue.
Fetch does not use cookie by default. To enable cookie, do this:
fetch(url, {
credentials: "same-origin"
}).then(...).catch(...);
In addition to #Khanetor's answer, for those who are working with cross-origin requests: credentials: 'include'
Sample JSON fetch request:
fetch(url, {
method: 'GET',
credentials: 'include'
})
.then((response) => response.json())
.then((json) => {
console.log('Gotcha');
}).catch((err) => {
console.log(err);
});
https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
Have just solved. Just two f. days of brutforce
For me the secret was in following:
I called POST /api/auth and see that cookies were successfully received.
Then calling GET /api/users/ with credentials: 'include' and got 401 unauth, because of no cookies were sent with the request.
The KEY is to set credentials: 'include' for the first /api/auth call too.
If you are reading this in 2019, credentials: "same-origin" is the default value.
fetch(url).then
Programmatically overwriting Cookie header in browser side won't work.
In fetch documentation, Note that some names are forbidden. is mentioned. And Cookie happens to be one of the forbidden header names, which cannot be modified programmatically. Take the following code for example:
Executed in the Chrome DevTools console of page https://httpbin.org/, Cookie: 'xxx=yyy' will be ignored, and the browser will always send the value of document.cookie as the cookie if there is one.
If executed on a different origin, no cookie is sent.
fetch('https://httpbin.org/cookies', {
headers: {
Cookie: 'xxx=yyy'
}
}).then(response => response.json())
.then(data => console.log(JSON.stringify(data, null, 2)));
P.S. You can create a sample cookie foo=bar by opening https://httpbin.org/cookies/set/foo/bar in the chrome browser.
See Forbidden header name for details.
Just adding to the correct answers here for .net webapi2 users.
If you are using cors because your client site is served from a different address as your webapi then you need to also include SupportsCredentials=true on the server side configuration.
// Access-Control-Allow-Origin
// https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api
var cors = new EnableCorsAttribute(Settings.CORSSites,"*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
This works for me:
import Cookies from 'universal-cookie';
const cookies = new Cookies();
function headers(set_cookie=false) {
let headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
};
if (set_cookie) {
headers['Authorization'] = "Bearer " + cookies.get('remember_user_token');
}
return headers;
}
Then build your call:
export function fetchTests(user_id) {
return function (dispatch) {
let data = {
method: 'POST',
credentials: 'same-origin',
mode: 'same-origin',
body: JSON.stringify({
user_id: user_id
}),
headers: headers(true)
};
return fetch('/api/v1/tests/listing/', data)
.then(response => response.json())
.then(json => dispatch(receiveTests(json)));
};
}
My issue was my cookie was set on a specific URL path (e.g., /auth), but I was fetching to a different path. I needed to set my cookie's path to /.
If it still doesn't work for you after fixing the credentials.
I also was using the :
credentials: "same-origin"
and it used to work, then it didn't anymore suddenly, after digging much I realized that I had change my website url to http://192.168.1.100 to test it in LAN, and that was the url which was being used to send the request, even though I was on http://localhost:3000.
So in conclusion, be sure that the domain of the page matches the domain of the fetch url.