Is there a way in Ember.js (and Ember-data) to send credentials to an api that requires Basic HTTP Authentication? I can see how it's done in JQuery here, but don't see any straightforward way to do it in Ember. I thought maybe adding something to the header would work (see below in coffeescript), but no success:
App.AuthAdapter = DS.RESTAdapter.extend(
host: 'https://my-api.example.com'
namespace: 'v1'
headers:
"Authorization Basic fooUsername:barPassword"
...
You can extend the default Rest adapter and add a headers hash which will be included in the ajax that's sent.
App.ApplicationAdapter = DS.RESTAdapter.extend(
headers:
withCredentials: true
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
)
Or you could take it a step farther and override the ajax method
App.ApplicationAdapter = DS.RESTAdapter.extend(
ajax: (url, type, hash) ->
adapter = this
new Ember.RSVP.Promise((resolve, reject) ->
hash = hash or {}
hash.url = url
hash.type = type
hash.dataType = "json"
hash.context = adapter
if hash.data and type isnt "GET"
hash.contentType = "application/json; charset=utf-8"
hash.data = JSON.stringify(hash.data)
if adapter.headers isnt `undefined`
headers = adapter.headers
hash.beforeSend = (xhr) ->
forEach.call Ember.keys(headers), (key) ->
xhr.setRequestHeader key, headers[key]
hash.success = (json) ->
Ember.run null, resolve, json
hash.error = (jqXHR, textStatus, errorThrown) ->
Ember.run null, reject, adapter.ajaxError(jqXHR)
Ember.$.ajax hash
)
)
Can you use $.ajaxPrefilter? e.g.
Ember.$.ajaxPrefilter (options) ->
options.xhrFields = { withCredentials: true }
options.username = 'fooUsername'
options.password = 'barPassword'
true # need to return non-falsy here
As #gerry3 stated $.ajaxPrefilter is a valid solution.
But if you want to solve a problem of dynamically changing your Headers AFTER an event, for instance, a successful LOGIN attempt, then you need to put more wires. In my case I need to send back a 'Token' Header that is provided by the server after a successful AJAX-login. But, of course, when the user initiates the App he's not logged-in already.
The problem is that once you reopen or extend the RESTAdapter, or define an ajaxPrefilter, even if you're binding it to a value (or localStorage as in my case) the class won't be following the current variable value. It's like a snapshot taken at some moment. So it's useless in my scenario.
I'm following Embercast Client Authentication which is a good start (code available), but instead of jQuery data-fetching I'm using Ember-Data.
So the trick is to observe the token and re-define the ajaxPrefilter as many times as you need it.
tokenChanged: function() {
this.get('token')=='' ?
localStorage.removeItem('token') :
localStorage.token = this.get('token');
$.ajaxPrefilter(function(options, originalOptions, xhr) {
return xhr.setRequestHeader('Token', localStorage.token);
});
}.observes('token')
Therefore, when the user logs-in he'll have a valid token and send it in every request to the server via the RESTAdapter.
Hope this helps someone.
Related
I'm building an api with api platform and a front with react (using the react template of apiplatform). I configured authentification and a return to client with httponly cookie which contains the jwt. But when my front does a request, it does not send this cookie... And I absolutly don't know why, I thought it was automaticaly done by browser till it's on same domain.
Here is an example of the network history from my client :
my app is running on https://localhost:3000/
Do you see something wrong in theses request ? Or does anyone has an idea of what it could come from ?
My app and api are using https and have a valid certificate...
If you need any additional info, feel free to ask, and thanks all !!!
I assume you work with either xhr or fetch.
Cookies ignore ports, but cross origin policy does not.
You work with two urls (http://localhost:8443 and http://localhost:3000). So your app is making cross origin request because ports differ.
xhr requires to set its withCredentials property to true in order to send cookies with cross-origin request.
fetch requires its credentials parameter to be set to include.
Server side, set the Access-Control-Allow-Credentials to true.
Also note that your cookie is samesite=strict. In production, if you use two domains for your app and your api, it will never be sent.
The real question here is why using a cookie instead of Authorization header ?
Ok, I didn't know... I've found nothing on it when I was trying to solve my prob.
I'm using cookie httponly because :
I want to try it :D
Lot of security articles says that it's more secure because client api can't access theses cookies, browser manages it. It seems to counter xss and stealth of cookies, but if my cookie is stored with localforage, I think I do not have this problem, but with localStorage I do, no ?
It's cool no ! I've done too many project with classic bearer auth, I can improve it now
A big thanks for your nice answer rugolinifr !
Okay, I'm still having my issue finally... My browser is not sending the cookie...
My auth request returning bearer cookie (valid, tested with postman)
My cookie received from auth request
My GET request without that auth cookie
I'm missing something but I don't find it...
I've set credentials, Access-Control-Allow-Credentials, samesite is 'none' for sending it everywhere. Is there something else to do ? Or maybe I'm doing a stupid little thing that is wrong ?
I can't answer in comment because there's code...
So, It's managed by the react admin base of api-platform (https://api-platform.com/docs/admin/), but my config is like this :
const fetchHeaders = {
credentials: 'include',
};
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
const apiDocumentationParser = (entrypoint) =>
parseHydraDocumentation(entrypoint, { headers: new Headers(fetchHeaders) }).then(
({ api }) => ({ api }),
(result) => {
...
},
);
const dataProvider = baseHydraDataProvider(entrypoint, fetchHydra, apiDocumentationParser, true);
So, all get, post etc request for datas are based on this conf
But my first call for authentication is done like that :
login: ({ username, password }) => {
const request = new Request(`${entrypoint}/authentication_token`, {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
return fetch(request).then((response) => {
if (response.status < 200 || response.status >= 300) {
localStorage.removeItem('isAuthenticated');
throw new Error(response.statusText);
}
localStorage.setItem('isAuthenticated', 'true');
});
},
ok, I've found solution :
add credentials to the auth request, if header is not added, cookie won't be stored by browser.
And second point :
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
credentials: 'include',
});
credentials: 'include' is not in headers option... Nice !
Faced the same problem.Tried out many solutions but didn't work.At last found out it was the cors configuration of node backend that was causing the problem. Configured cors like the following way to solve the problem.
const corsConfig = {
origin: true,
credentials: true,
};
app.use(cors(corsConfig));
app.options('*', cors(corsConfig));
I'm trying to send a POST request from a Vue.js template to my API created with Django.
When sending I get a 403 CSRF token missing or incorrect error. Since I separated the front and the back, I don't have a view with {csrf_token} on the Django side.
How do I send my form?
I tried some exemples on the web using cookies but i'm beginners and need more explaination about the POST subject and CSRF
I have a Djano View (and urls associated) juste like this :
def get_csrf_token(request):
token = get_token(request)
return JsonResponse({'token': token})
Whe i'm requesting the url, obtained the JSON with the token.
And on the Front side i'm using this method to get the Token :
getToken: function() {
this.loading = true;
this.$http.get('/qualite/get-token/')
.then((response) => {
this.token =response.data;
this.loading = false;
})
.catch((err) => {
this.loading = false;
console.log(err);
})
},
addNc: function() {
let headers = {
'Content-Type': 'application/json;charset=utf-8'
};
if(this.token !== '') {
headers['HTTP_X-XSRF-TOKEN'] = this.token
}
this.loading = true;
this.$http.post('/qualite/api/nc/',this.newNc, {headers: headers})
.then((response) => {
this.loading = false;
})
.catch((err) => {
this.loading = false;
console.log(err)
})
},
For the CSRF you get by default after user login aside with the session, if you're using SessionAuthentication (It's the default authentication used in DRF).
You have to send it with each request in the header, you can refer the this link to know more about the header sent, as it's name is changed and can be configured.
Note also that in the settings you have to make sure that CSRF_COOKIE_HTTPONLY is set to False (which is the default), to be able to read it from the client side JS.
Another path would be removing CSRF enforcement per requests (But it's highly not recommended for security concerns), you can find more about this in the answer here.
Use a Token-based authentification.
Same issue i was encountered with,
the problem was, i had used Class based view and at the time of registered the url i forget to mention as_view() with class Name.
ex:- class PostData(APIView)
before :- path('post_data', PostData)
after correction:- path('post_data', PostData.as_view())
React Client Code - Using request promises to send username and password in Header
var password = values.password;
var email = values.email;
request
.head(
"https://us-central1-simplineet-754e8.cloudfunctions.net/CreateUserAuth"
)
.set('Content-Type', 'application/x-www-form-urlencoded')
.auth(email, password, false)
.query(dataobj)
.then(res => {
console.log(res);
if (res.statusCode === 200) {
console.log("statusText",res.body);
} else {
console.log("statusText",res.statusText);
}
})
.catch(err => {});
Backend - Google Cloud Function to Handle Basic Auth Requests from Client
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors({origin: true}));
exports.CreateUserAuth = functions.https.onRequest((request, response) => {
var corsFn = cors();
corsFn(request, response, function () {
// Request Header
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
response.setHeader('Access-Control-Allow-Credentials', true);
response.setHeader('Access-Control-Allow-Origin', '*');
var auth = require('basic-auth') // basic-auth NPM package to extract username and password from header
var user = auth(request)
var email = user.name; // Getting username from Auth
var password = user.pass; // Getting password from Auth
var username = request.query.username;
response.send('Hello from Firebase!'); // Not getting this response in Client
});
});
Response Getting in Client :
Response {req: Request, xhr: XMLHttpRequest, text: null, statusText: "", statusCode: 200, …}
As per MDN docs, HEAD responses should not have a body:
The HTTP HEAD method requests the headers that are returned if the specified resource would be requested with an HTTP GET method. Such a request can be done before deciding to download a large resource to save bandwidth, for example.
A response to a HEAD method should not have a body. If so, it must be ignored. Even so, entity headers describing the content of the body, like Content-Length may be included in the response. They don't relate to the body of the HEAD response, which should be empty, but to the body of similar request using the GET method would have returned as a response.
My guess is that GCP is handling it as a GET and stripping out the body before returning a response.
However, keep in mind that Google Cloud Functions HTTP trigger docs don't explicitly say that HEAD is a supported method:
You can invoke Cloud Functions with an HTTP request using the POST, PUT, GET, DELETE, and OPTIONS HTTP methods.
It looks like you are making a HEAD request instead of a POST request. Change to request.post() and it should work
Heres my setup:
Ember: 1.1.0-beta.1
Ember-data: 1.0.0-beta.2
# ON STARTUP
APP.ApplicationAdapter = DS.RESTAdapter.extend(
headers: {
'X-API-TOKEN': localStorage.token
}
)
This works fine if they were already logged in when they refresh...
However...
#ON LOGIN FORM SUBMIT ACTION
$.post('/sessions', data).then( (response) =>
if response.token
localStorage.token = response.token
APP.ApplicationAdapter = DS.RESTAdapter.reopen(
headers: {
'X-API-TOKEN': localStorage.token
}
)
else
#set('error_message', response.error)
The calls are still unauthorized, and the adapter keep trying to pass the old token.
So basically the 'headers' property on the adapter is not updated by the 'reopen' method. Whats the proper way to change the adapter in ember-data?
Thanks guys
Once instantiated you should get your adapter from the container.
But since it's not very clear from where you are making the request, try something like this:
#ON LOGIN FORM SUBMIT ACTION
$.post('/sessions', data).then( (response) =>
if response.token
localStorage.token = response.token
adapter = APP.__container__.lookup 'adapter:Rest'
adapter.set('headers', { 'X-API-TOKEN': localStorage.token })
else
#set('error_message', response.error)
Note: using App.__container__ is not recomended for production code depending from where you are making the request it would be more appropriate to get the container in a more clean way.
Update in response to your last comment
In the case you need access to the container in a controller then:
APP.LoginController = Ember.ObjectController.extend
...
adapter = #get('container').lookup 'adapter:application'
adapter.set('headers', { 'X-API-TOKEN': localStorage.token })
...
should get you the adapter.
Hope it helps.
Headers can be used as a computed property to support dynamic headers.
You can use the volatile function to set the property into a non-cached mode causing the headers to be recomputed with every request.
APP.ApplicationAdapter = DS.RESTAdapter.extend(
headers: function() {
return {
'X-API-TOKEN': localStorage.token
};
}.property().volatile()
)
URL's:
toc_headers-customization
method_volatile
I've made a login system with Ember.js and Rails . So I tried to make redirection for non-authenticated users. I don't know where I must do the redirection (the application controller or in the application route ? ).
Here's my auth system :
window.AuthApp = Ember.Object.extend(Ember.Evented).create
authToken: null
currentUserId: null
signIn: (data) ->
if data == null
data = {}
if data['remember'] == true
cookie = true
$.ajax 'api/login',
type: 'POST'
dataType: 'JSON',
contentType: 'application/json; charset=utf-8'
data: JSON.stringify(data)
error: ->
alert('Error signIn')
success: (data) ->
console.log(data)
AuthApp.set 'authToken', data['auth_token']
if cookie == true
$.cookie 'remember_token', data['auth_token'],
expires: 7
AuthApp.Module.RememberMe = Ember.Object.create
recall: ->
if ($.cookie('remember_token'))
data = {}
data['auth_token'] = $.cookie('remember_token')
AuthApp.signIn(data)
return true
As you can see, I've just to call AuthApp.Module.Remember.recall() for check if an user is connected or not.
Thanks for your help
You should take a look at the router-facelift. One of the design goals was to make authentication-based apps less difficult to implement.
https://gist.github.com/machty/5723945