I'm using ember-simple-auth and ember-simple-auth-token for allowing users to log into my app. However, when I call Django Rest Framework in the backend with a POST request to authenticate using a username and password, I get a 406 (Not Acceptable) error. This does not happen in the DRF browsable API, so the backend seems to work fine.
I suspect something related to CORS. I use django-cors-headers in Django, and allow all in my dev environment. I also use django-rest-framework-jwt and django-rest-framework-json-api packages, if that matters.
My API shows an OPTIONS and then a POST call being made:
[09/Mar/2016 07:15:54] "OPTIONS /api-token-auth/ HTTP/1.1" 200 0
[09/Mar/2016 07:15:54] "POST /api-token-auth/ HTTP/1.1" 406 114
Response headers:
HTTP/1.0 406 Not Acceptable
Date: Wed, 09 Mar 2016 07:15:54 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *
Content-Type: application/vnd.api+json
Allow: POST, OPTIONS
Vary: Accept
Request headers:
POST /api-token-auth/ HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Content-Length: 2
Accept: application/json, text/javascript
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/48.0.2564.116 Chrome/48.0.2564.116 Safari/537.36
Content-Type: application/json
Referer: http://localhost:4200/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
The request headers do not show application/vnd.api+json but application/json instead. Unfortunately, no matter what I do in Ember is able to resolve that. I've unsuccessfully tried setting the headers to "Accept": "application/vnd.api+json" for my app's JSONAPIAdapter, and in ENV['ember-simple-auth-token'].
Implement your own authenticator which sets up headers used during authentication request:
// your-app/authenticators/your-custom-authenticator.js
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrant.extend({
/**
* your backend authentication endpoint
* #overrides
*/
serverTokenEndpoint: `https://your.authentication.endpoint.sth/login`,
/**
* Makes a request to the authentication server.
* This is what you need to override to customize your headers
* set up for purposes of authentication.
* #overrides
*/
makeRequest(url, data) {
const options = {
url: url,
data: data,
type: 'GET',
dataType: 'json',
accept: 'application/vnd.api+json',
headers: {
"Content-Type": 'application/vnd.api+json'
}
};
return Ember.$.ajax(options);
}
});
Refer to this custom authenticator in your (login) route/controller/wherever you need:
this.get('session').authenticate('authenticator:yourCustomAuthenticator', username, password).then(() => {
// success, redirect, as you like..
})
Take a look at the Authenticators sections of ember-simple-auth docs to choose a parent authenticator as close to your needs as you need: ember-simple-auth - Authenticators
I managed to solve this, more or less. It is an unfortunate combination of packages that led to some problems with having JSON API spec between Ember and DRF.
First, the overwriting of headers I managed to do in my controllers/login.js by simply adding the headers as an argument to .authenticate. Any args get passed to the ember-simple-auth authenticator. (I did not need to implement my own authenticator, as Pavol suggested in his answer.)
// controllers/login.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'authenticator:jwt',
// Set headers to accept JSON API format
headers = {
'Accept': 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json'
};
this.get('session').authenticate(authenticator, credentials, headers);
}
}
});
This introduced the next problem: my content type was not actually JSON API spec, so I did need to implement my own authenticator to translate ember-simple-auth-token's JWT authenticator to produce JSON API spec compatible format. Didn't get it to work, but something like this:
// authenticators/jwt.js
import Base from 'ember-simple-auth-token/authenticators/token';
export default Base.extend({
/**
Returns an object used to be sent for authentication.
#method getAuthenticateData
#return {object} An object with properties for authentication.
*/
// Make sure this is JSON API compatible format.
getAuthenticateData(credentials) {
const authentication = {
// This is apparently not valid JSON API spec, but you get the gist...
'data': [{
[this.identificationField]: credentials.identification,
[this.passwordField]: credentials.password
}]
};
return authentication;
}
});
Now, on the backend, rest_framework_jwt and rest_framework_json_api were still not playing well together.
At this point, I decided that it was just a lot simpler to drop the need for JSON API spec on the auth endpoints: Ember's packages did not produce it, and DRF was having trouble parsing it!
So, I reverted everything on the Ember side, having it produce the request according to my original question. On the DRF side, I subclassed rest_framework_jwt's views and set the parser to DRF's default JSONParser:
"""
Make the JWT Views ignore JSON API package and use standard JSON.
"""
from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken, \
VerifyJSONWebToken
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
class ObtainJSONWebTokenPlainJSON(ObtainJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer, )
class RefreshJSONWebTokenPlainJSON(RefreshJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
class VerifyJSONWebTokenPlainJSON(VerifyJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
Final result: solved by having my API follow JSON API spec everywhere except the token authentication endpoints.
You should be able to explicitly set content-type in your adapter:
export default DS.JSONAPIAdapter.extend({
// set content-type upon every ajax request
ajax: function(url, type, hash){
hash = hash || {} ;
hash.headers = hash.headers || {};
hash.headers['Content-Type'] = 'application/vnd.api+json';
return this._super(url, type, hash);
}
});
Does it solve your problem?
Related
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.
Using:
Django 3.x [ Django-Filters 2.2.0, graphene-django 2.8.0, graphql-relay 2.0.1 ]
Vue 2.x [ Vue-Apollo ]
I am testing single page vue app´s with Django, GraphQL & Vue-Apollo.
If i use csrf_exempt on my view everything works in the frontend.
urlpatterns = [
<...>
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
<...>
Now i wanted to CSRF protect my request.
Within the process of understanding the CSRF protection, i thought all Django GraphQLView needs is to receive the "value" of the X-Csrftoken in the Request Header. So i focused on sending the csrf Value in different ways...via a single view like this
path('csrf/', views.csrf),
path("graphql", GraphQLView.as_view(graphiql=True)),
or by ensure a cookie with ensure_csrf_cookie
Afterwards in my ApolloClient i fetch thes Value and send him back with the request Header .
This i what Django prints when i send a GraphQL request from a Django-Vue page.
Forbidden (CSRF token missing or incorrect.): /graphql
Parallel i always test with thegraphiql IDE and these requests still working. I also print everytime the info.context.headers value of my query resolver.
{'Content-Length': '400', 'Content-Type': 'application/json',
'Host': 'localhost:7000', 'Connection': 'keep-alive',
'Pragma': 'no-cache', 'Cache-Control': 'no-cache',
'Accept': 'application/json', 'Sec-Fetch-Dest': 'empty', 'X-Csrftoken': 'dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36',
'Origin': 'http://localhost:7000',
'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors',
'Referer': 'http://localhost:7000/graphql', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.9,de;q=0.8',
'Cookie': 'sessionid=jqjvjfvg4sjmp7nkeunebqos8c7onhiz; csrftoken=dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz'}
i recognized that the GraphQLView IDE alway puts the X-Csrftoken and the Cookie:..csrftoken. also in the request. if delete the csrftoken-cookie of a GraphQLView IDE before sending the request, i get this
Forbidden (CSRF cookie not set.): /graphql
The IDE shows a long, red report
.... CSRF verification failed. Request aborted.</p>\n\n\n
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms.
This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>\n
The Information of the IDE say´s the request needs a CSRF cookie. But all read until now in Forums, Doc´s, was more related to the value itself. Meaning all you need is to send the csrf value within the Header as X-Csrftoken or so and the View would do the magic.
Question
Therefore my Question is:
Do i have to set the X-Csrftoken and the Cookie:..csrftoken at the same time in my ApolloClient to make a request on my django GraphQLView ?
Or is it also possible to simple send only the X-Csrftoken without a csrf-cookie and vice versa?
After long time and a pause to follow the issue, i tried once more and found a solution.
Setup
django 3.1
vue 2.6
vue-apollo 3.0.4 (supports the new Apollo-Client 3)
#apollo/client 3.1.3
Presumption
I am using Vue as a multi app and not single app.
the Webpack DevServer will Hot-Reload while writing my *vue.js files in the Django STATICFILES_DIRS. Django will take the Files from there. works fine
Problem Recap
After revisiting my problem i noticed i have 2 Issue. One was the Browser denied graphQL request because of CORS. And the Second was the CSRF Token.
Solution
To Fix the CORS Issue i noticed that my uri of the Apollo Client was not the same as my Django Dev Server. Instead of http://127.0.0.1:7000/graphql it was set to http://localhost:7000/graphql. I also set the credentials (see vue-apollo.js)
To Fix the CSRF i did 3 things
make sure to send an {% csrf_token %} with the HTML where your Vue/ GraphQL Client app is hooked. So that we can fetch it later.
Install js-cookie for getting the Cookie
set a header in the Apollo Client Constructor with X-CSRFToken in vue-apollo.js
vue-apollo.js
import Vue from 'vue'
// import path for the new Apollo Client 3 and Vue-Apollo
import { ApolloClient, InMemoryCache } from '#apollo/client/core';
import VueApollo from 'vue-apollo'
import Cookies from 'js-cookie'
// Create the apollo client
const apolloClient = new ApolloClient({
// -------------------
// # Required Fields #
// -------------------
// URI - GraphQL Endpoint
uri: 'http://127.0.0.1:7000/graphql',
// Cache
cache: new InMemoryCache(),
// -------------------
// # Optional Fields #
// -------------------
// DevBrowserConsole
connectToDevTools: true,
// Else
credentials: 'same-origin',
headers: {
'X-CSRFToken': Cookies.get('csrftoken')
}
});
// create Vue-Apollo Instance
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
// Install the vue plugin
Vue.use(VueApollo)
export default apolloProvider
Vue.config.js
const BundleTracker = require("webpack-bundle-tracker");
// hook your apps
const pages = {
'page_1': {
entry: './src/page_1.js',
chunks: ['chunk-vendors']
},
'page_2': {
entry: './src/page_2.js',
chunks: ['chunk-vendors']
},
}
module.exports = {
pages: pages,
filenameHashing: false,
productionSourceMap: false,
// puplicPath:
// Tells Django where do find the bundle.
publicPath: '/static/',
// outputDir:
// The directory where the production build files will be generated - STATICFILES_DIRS
outputDir: '../dev_static/vue_bundle',
chainWebpack: config => {
config.optimization
.splitChunks({
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
chunks: "all",
priority: 1
},
},
});
// Don´t create Templates because we using Django Templates
Object.keys(pages).forEach(page => {
config.plugins.delete(`html-${page}`);
config.plugins.delete(`preload-${page}`);
config.plugins.delete(`prefetch-${page}`);
})
// create webpack-stats.json.
// This file will describe the bundles produced by this build process.
// used eventually by django-webpack-loader
config
.plugin('BundleTracker')
.use(BundleTracker, [{filename: '/webpack-stats.json'}]);
// added to use ApolloQuery Tag (Apollo Components) see vue-apollo documentation
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.transpileOptions = {
transforms: {
dangerousTaggedTemplateString: true,
},
}
return options
})
// This will allows us to reference paths to static
// files within our Vue component as <img src="~__STATIC__/logo.png">
config.resolve.alias
.set('__STATIC__', 'static')
// configure a development server for use in non-production modes,
config.devServer
.public('http://localhost:8080')
.host('localhost')
.port(8080)
.hotOnly(true)
.watchOptions({poll: 1000})
.https(false)
.headers({"Access-Control-Allow-Origin": ["*"]})
// DO have Webpack hash chunk filename
config.output
.chunkFilename("[id].js")
},
devServer: {
writeToDisk: true
}
};
Was running into the same issue.
My application backend is Django with graphene. My frontend is React.
I also had 2 issues:
i didn't use the correct graphql url in my frontend apollo createHttpLink. In my django urls.py, my graphql url had "/" but i didn't put "/" in frontend. Just make sure the urls match exactly.
In addition to setting csrf token to header, you also have to set the csrf in Cookie object otherwise you will get a forbidden error. See [this][https://github.com/graphql-python/graphene-django/issues/786]. When the CSRF_USE_SESSIONS settings variable is set to True post requests can not be made as the request will be rejected. This is because the CSRFTOKEN is not provided, because it will not be stored in a cookie.Django will end up giving this warning: Forbidden (CSRF token missing or incorrect.):
This issue can be prevented by passing the view to csrf_exempt in django's urls.py file.
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True)))
However a better solution is to store csrf in global cookie in the frontend in addition to setting it in the header like this:
const csrftoken = await getCsrfToken();
const cookies = new Cookies();
cookies.set('csrftoken', csrftoken);
(See my index.js code below for full code).
My index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './custom.scss'
import allReducers from './reducer';
import {Provider} from 'react-redux';
import {BrowserRouter, Route, Routes} from "react-router-dom";
import {
ApolloClient,
InMemoryCache,
ApolloProvider, from, createHttpLink
} from "#apollo/client";
import {createStore} from "redux";
import {AuthProvider} from "./utils/auth";
import {setContext} from "#apollo/client/link/context";
import {ACCESS_TOKEN_KEY} from "./constants/Constants";
import {onError} from "#apollo/client/link/error";
import Cookies from "universal-cookie/es6";
const store = createStore(allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
let csrftoken;
async function getCsrfToken() {
if (csrftoken) return csrftoken;
csrftoken = await fetch('http://localhost:8000/csrf/')
.then(response => response.json())
.then(data => data.csrfToken)
return await csrftoken
}
const authMiddleware = setContext(async (req, { headers }) => {
const token = localStorage.getItem(ACCESS_TOKEN_KEY);
const csrftoken = await getCsrfToken();
const cookies = new Cookies();
cookies.set('csrftoken', csrftoken);
return {
headers: {
...headers,
'X-CSRFToken': csrftoken,
Authorization: token ? `Bearer ${token}` : ''
},
};
});
const httpLink = createHttpLink({
uri: 'http://localhost:8000/graphql/',
credentials: 'include'
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
const client2 = new ApolloClient({
uri: 'http://localhost:8000/graphql/',
cache: new InMemoryCache(),
credentials: 'include',
link: from([authMiddleware, errorLink, httpLink])
});
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<ApolloProvider client={client2}>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/*" element={<App/>} />
</Routes>
</AuthProvider>
</BrowserRouter>
</ApolloProvider>,
</React.StrictMode></Provider>,
document.getElementById('root')
);
On the backend, here are some of the related files:
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql/', GraphQLView.as_view(graphiql=True)),
path('csrf/', csrf),
]
settings.py:
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ["http://localhost:3000", ]
CSRF_TRUSTED_ORIGINS = ["http://localhost:3000", ]
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]
views.py (returns generated csrf token to frontend)
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.shortcuts import render
# Create your views here.
def csrf(request):
return JsonResponse({'csrfToken': get_token(request)})
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) => {
});
My response headers look like this
HTTP/1.1 200 OK
Server: nginx/1.9.7
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
X-On-Trial: 1
Cache-Control: no-cache
Access-Control-Allow-Origin: http://localhost:4200
Vary: Origin
Date: Sun, 29 May 2016 00:37:31 GMT
But when I do a console.log(headers) in the RESTAdapter handleResponse function, all that is included is
EmptyObject {Content-Type: "application/json", Cache-Control: "no-cache"}
How can I access the X-On-Trail header or any other custom headers I may need?
I'm not sure if this matters but I am using ember-simple-auth. Does that strip out headers?
I check sources. .handleResponse is called from .ajax
ajax(url, type, options) {
var adapter = this;
var requestData = {
url: url,
method: type
};
return new Ember.RSVP.Promise(function(resolve, reject) {
var hash = adapter.ajaxOptions(url, type, options);
hash.success = function(payload, textStatus, jqXHR) {
let response = adapter.handleResponse(
jqXHR.status,
parseResponseHeaders(jqXHR.getAllResponseHeaders()), // try to check both in debugger
payload,
requestData
);
So just try to stop at parseResponseHeaders(jqXHR.getAllResponseHeaders()) line and check jqXHR.getAllResponseHeaders(). If it's ok - check parseResponseHeaders()
I'll be glad to help with debug, if you have public link for your project or if you can give link for any public project with REstAdapter
About striping - it skips headers which doesn't contains colons
P.S> Thx to #Vlad
xmlHttp.getResponseHeader + Not working for CORS
"Access-Control-Expose-Headers"
Used in response to a preflight request to indicate which HTTP headers can be > used when making the actual request.
Access-Control-Allow-Headers: <field-name>[, <field-name>]*
I created an API /user/auth where I can send using POST a Json object like:
var user = {"username":"alex", "password":"m"}
$http(
{
method: 'POST',
url: '/api/v1/user/auth',
data: user,
}
).
success(function(data, status, headers, config) {
console.log(data)
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
The response from Django is the following:
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Content-Type,*
Access-Control-Allow-Methods:POST,GET,OPTIONS,PUT,DELETE
Access-Control-Allow-Origin:*
Content-Language:fr
Content-Type:application/json
Date:Fri, 30 Aug 2013 15:22:01 GMT
Server:WSGIServer/0.1 Python/2.7.5
Set-Cookie:sessionid=w63m0aoo8m3vfmvv0vk5d6w1708ftsrk; Path=/
Vary:Accept, Accept-Language, Cookie
So Django returns a good cookie but I don't know why, Chrome doesn't set this cookie in Resource.
The request is sent from 127.0.0.1:8000 to 127.0.0.1:8080; I use this middleware to handle CROS requests and I also set:
SESSION_COOKIE_HTTPONLY = False
The problematic line is:
Access-Control-Allow-Origin: *
The credential request doesn't work with a wildcard allow origin. You have to specifically set the name, like :
Access-Control-Allow-Origin: http://127.0.0.1:8080
You can find more information here:
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#Requests_with_credentials
Ok thanks to Bqm link to mozilla I finally found why the cookie was not set.
Indeed you need to set in the header you sent:
Access-Control-Allow-Credentials: true
In Angular this is done with this method:
$http(
{
method: 'POST',
url: '/api/v1/user/auth',
data: user,
withCredentials: true
}
)
Once your backend will answer with a setCookie, the browser will then be able to set the cookie in your browser.