I am building an app with AngularJS and using external API made with Django.
For API calls I'm using Restangular (but it's not the case because even with $http I get the same).
My default content-type for post calls is:
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
This is done just so I could send credentials when signing in win OAuth2:
var data = {
grant_type: 'password',
client_id: clientID,
client_secret: clientSecret,
username: username,
password: password
};
$http.post('http://server.com/api/oauth2/access_token/', data);
GET is done using default AngularJS configuration (application/json I guess).
So far so good, everything was working until... I was making POST requests and sending large JSON objects.
The configuration from above didn't work, so I changed those POST requests to application/json, and getting error response:
400: JSON parse error - No JSON object could be decoded
Tried the same with just jQuery - and it worked although I had to do JSON.strigify and in error debugging on Chrome Request Payload with jQuery looks like a proper object, and with AngularJS looks like this:
type=Feature&geometry=%5Bobject%20Object%5D&properties=%5Bobject%20Object%5D
(yes, by the way, I'm sending a valid GeoJSON object)
So I thought maybe JSON.stringify has something to do with this but after applying this to my object, the Request Payload from AngularJS is a mess:
0=%7B&1=%22&2=t&3=y&4=p&5=e&6=%22&7=%3A&8=%22&9=F&10=e&11=a&12=t&13=u&14=r&15=e&16=%22&17=%2C&18=%22&19=g&20=e&21=o&22=m&23=e&24=t&25=r&26=y&27=%22&28=%3A&29=%7B&30=%22&31=t&32=y&33=p&34=e&35=%22&36=%3A&37=%22&38=P&39=o&40=i&41=n&42=t&43=%22&44=%2C&45=%22&46=c&47=o&48=o&49=r&50=d&51=i&52=n&53=a&54=t&55=e&56=s&57=%22&58=%3A&59=%5B&60=1&61=9&62=.&63=0&64=8&65=3&66=2&67=4&68=1&69=2&70=2&71=4&72=2&73=8&74=8&75=9&76=%2C&77=5&78=0&79=.&80=2&81=4&82=2&83=4&84=0&85=2&86=0&87=7&88=2&89=3&90=6&91=8&92=9&93=%5D&94=%7D&95=%2C&96=%22&97=p&98=r&99=o&100=p&101=e&102=r&103=t&104=i&105=e&106=s&107=%22&108=%3A&109=%7B&110=%22&111=c&112=o&113=n&114=t&115=r&116=i&117=b&118=u&119=t&120=i&121=o&122=n&123=t&124=y&125=p&126=e&127=%22&128=%3A&129=1&130=2&131=1&132=4&133=5&134=%2C&135=%22&136=l&137=o&138=c&139=a&140=t&141=i&142=o&143=n&144=%22&145=%3A&146=%7B&147=%22&148=n&149=a&150=m&151=e&152=%22&153=%3A&154=%22&155=D&156=a&157=n&158='&159=s&160=%20&161=H&162=o&163=u&164=s&165=e&166=%22&167=%2C&168=%22&169=d&170=e&171=s&172=c&173=r&174=i&175=p&176=t&177=i&178=o&179=n&180=%22&181=%3A&182=n&183=u&184=l&185=l&186=%2C&187=%22&188=s&189=t&190=a&191=t&192=u&193=s&194=%22&195=%3A&196=n&197=u&198=l&199=l&200=%2C&201=%22&202=c&203=r&204=e&205=a&206=t&207=e&208=d&209=_&210=a&211=t&212=%22&213=%3A&214=%22&215=2&216=0&217=1&218=4&219=-&220=0&221=4&222=-&223=3&224=0&225=T&226=1&227=0&228=%3A&229=2&230=4&231=%3A&232=5&233=8&234=.&235=9&236=3&237=7&238=Z&239=%22&240=%2C&241=%22&242=i&243=d&244=%22&245=%3A&246=4&247=5&248=%7D&249=%7D&250=%7D
Any help? What I'm doing wrong? Or it has something to do with Django API? Should we change something in it?
SOLVED
The problem was caused by serialiser (my interpretation of jQuery's $.param). Apparently I needed this for sending credentials for the OAuth2 but not for any other POST request.
Although I still don't know why it is like that.
try this
var data = {
grant_type: 'password',
client_id: clientID,
client_secret: clientSecret,
username: username,
password: password
};
$http.post('http://server.com/api/oauth2/access_token/', angular.toJson(data));
Include jquery and try this
$http({
method: 'POST',
url: 'http://server.com/api/oauth2/access_token/',
data: $.param(data)
});
Related
I am trying to test the /login API via POSTMAN (via FE it works fine) but it doesn't
show anything in the body part even though I am sending body data.
but when printing the request from the BE, the body is empty...
....
body: {},
....
unlike when using FE:
....
body: {data: { username: 'admin', password: 'admin' }},
....
Any idea what's going on? If anything else is needed to be provided - pls let me know
I know it's going through because the server responds with 500 and the message
TypeError: Cannot read property 'username' of undefined
The weird part is, that the data I am sending, are nowhere to be found in the request object at all :(
EDIT:
This is how I call it from the FE:
return axios.post('login', { data: user })
and the user is:
user: {
username: 'admin',
password: 'admin'
}
So the format should be right
data: {
username: 'admin',
password: 'admin'
}
Because that's how I access it on the BE side
req.body.data.username
EDIT2:
The ultra-super-rare-weird part is, that JEST is working fine :)
const creds = {
data: {
username: 'admin',
password: 'admin'
}
}
return request(app)
.post("/api/v1/login")
.send(creds)
.expect(200)
.then(res => {
expect(res.body).toMatchSnapshot()
})
and this test passes .... f**k me guys.. what's going on?
if you are working with an express server try to parse your body in the server as soon as you initialize express app before routing
const app = express();
app.use(express.json());
The syntax of your body looks like JSON, yet you've specified the type of the body as "raw text". This will set the Content-type header of your request to "text/plain", which is probably causing your backend to be unable to actually read the body (because it expects a JSON object).
Simply switch from "Text" to "JSON", wrap your current body in curly braces (so that you're actually sending a single JSON object with a data property set) and try sending the request again. The content-type header will be correctly set to "application/json" this time and your backend will read the data successfully.
Add the following parameters in the request headers configuration in Postman:
Content-Type: application/json
Content-Length
The value of Content-Length is automatically calculated by Postman when a request is sent. Both values are used to identify the media type of the request body and to parse it accurately. When missing, the body may be ignored completely (depending on the server).
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've been trying to make a POST to my Django application via XHTTP Request and jQuery with no correct result at all.
The fact is that the Django app is on one of my subdomains, and the web app is in another, not being part of the Django app, just only a separated app.
I've been reading the docs, but none of this works.
All of my subdomains are in the ALLOWED HOSTS list and in the CORS ORIGIN WHITELIST, and the petition works correctly with GET, but POST returns me a Forbidden 403 error telling CSRF Cookie not found.
This is the code of my petition.
var petition = new XMLHttpRequest();
petition.open('POST','http://localhost:8000/login/',true);
petition.setRequestHeader("X-CSRFToken",sessionStorage["csrf_token"]);
petition.send()
I already have stored the CSRF cookie in the session storage with this function.
function storeCSRFToken(){
var token;
$.ajax({
url : 'http://localhost:8000/token',
type: "GET",
dataType : "json",
success: function( data ){
token = data.csrf_token;
sessionStorage['csrf_token'] = token;
}
});
}
Any clue of what is going wrong?
I am using ajax request to send POST but it got response 403 because of csrf_token. I divide the frontend just using Vuejs and backend using Django to just reponse API only so I can't use Django template to render {% csrf_token %} or having csrftoken in session to use getcookie('csrftoken') like in Django's doc recommend. Is there anybody face this problem like me and got some solutions ? So thank you if you can help me this.
You can set the CSRF token in the header of your AJAX request. E.g., if you use jquery and jquery.cookie library, you can easily retrieve the Django-set csrftoken cookie like so:
$.ajax({
url : 'YOUR_URL_HERE',
headers: {'X-CSRFToken': $.cookie('csrftoken')},
type: 'POST',
dataType: 'json',
data: {},
success: function() {
},
error: function(xhr, errMsg, err) {
},
});
Django documentation also includes a section on this: https://docs.djangoproject.com/en/1.11/ref/csrf/#ajax
Please note that this solution may depend on your specific Django settings. The Django documentation link above details everything pretty clearly.
EDIT:
Given that even your initial page request is not served by Django, here is how you can accomplish what you're looking for...
1.) Create a view in your Django app that manually generates and returns a CSRF token (using django.middleware.csrf.get_token):
def get_csrf_token(request):
token = django.middleware.csrf.get_token(request)
return JsonResponse({'token': token})
2.) You would also need to add an appropriate entry in your Django URLs file:
url(r'^get-token/$', get_csrf_token)
3.) Then your Vue.js app can fetch the CSRF token using this endpoint. This doesn't need to be a user-initiated event; for example, you can configure your front-end app to fetch it on the $(document).ready() event. Then, using your preferred AJAX library (I am using jQuery in my example):
$.ajax({
url: '/get-token/',
type: 'GET',
dataType: 'json',
success: function(data) {
$.cookie('csrftoken', data.token); // set the csrftoken cookie
}
});
4.) Now your csrftoken cookie is set and should be usable for subsequent POST requests.
$.ajax({
url : 'YOUR_URL_HERE',
headers: {'X-CSRFToken': $.cookie('csrftoken')},
type: 'POST',
dataType: 'json',
data: {},
success: function() {
},
error: function(xhr, errMsg, err) {
},
});
I have used jQuery for AJAX functionality and the jQuery.cookie library for getting and setting cookies, but of course you can use whichever library you would prefer for these functions.
According to the Django documentation you can simply use the ensure_csrf_cookie decorator on a view and that will send the cookie with the token with the response.
This is going to be wildly unpopular, but I've found it to be a relatively simple, secure and unobtrusive way to separate the front/backend.
In your VueJS app, you've probably got a login redirect when the user tries to access a page and are unauthenticated.
So instead of sending it to a vue router page, redirect it to /account/login/ (or some django app route - put an exception in cloudfront, or nginx proxy for /account/login/ to proxy pass to django) - then in the login.html template, just use a javascript window.location.href to your vueJS login page /login
The csrf_token will be set as a HttpOnly, secure cookie (which is what you want), and the disruption to the user is so minimal as to not even justify worrying about.
I followed the doc here TokenAuthentication and already create token for every user.but I don't how to get token in javascript? For example, I wannt post a comment, using ajax like
$.ajax({
url: "/api/comment/",
type: "POST",
datatype: "json",
data: {"reply_id": "1", csrfmiddlewaretoken: "{{ csrf_token }}", and token here?},
});
But how can I get current user's token? cookies has set http-only and I can't access it. Maybe I should sent another ajax to get the token by using username(which I display on my website) to search the database first?
Any help is welcome, Thanks.
EDIT: Should I use localStorage instead?
You first need to retrieve the auth token by logging in using the users credentials. You then have an auth token which you can store in your localStorage for example.
The JS code for logging in looks roughly like this;
var credentials = {
username: "admin",
password: "abc123"
};
$.post("http://localhost:8000/api-token-auth/", credentials, function(auth) {
console.log("Logged in, auth token: ", auth.token);
}