Flask JWT Extended set cookie error - javascript

I have created an API that is supposed to answer to both mobile devices and web browsers. For example, /web/toys for web and /API/toys for JSON responses. I am using JW Tokens as a means of authentication.
I am displaying forms in HTML and in the background, I call jQuery Ajax's methods to POST to my APIs. I am keeping the access_tokens in the session cookie. To prevent CSRF attacks, I am using Flask-JWT-Extended.
When I decorate a view with #jwt_required and CSRF is set to True, I get missing JWT in headers and cookies, even when the cookies were being set and transferred. I checked the source code and found that it is important to set X-CSRF-TOKEN in the request header. However, since the endpoint answers to both GET and POST calls, how can I set the headers in the GET call without resorting to loading the complete page using jQuery.? Basically, I want to show the form on the webpage, and when the user clicks submit, the form be transferred using jQuery to the existing API. If there is a better way to handle things, I would love to know it.
Thanks!

Author of Flask-JWT-Extended here. As you have discovered, with this extension we are currently doing CSRF protection for every type of request. However, CSRF protection is only really needed on state changing requests: See: https://security.stackexchange.com/questions/115794/should-i-use-csrf-protection-for-get-requests/115800
The benefit of protecting all requests types is that if you have an endpoint that incorrectly changes state in a GET request (there is no technical reason this couldn't happen), it becomes vulnerable to CSRF attacks. Now if the backend is designed more 'up to spec', this is no longer a problem. It sounds like I need to update Flask-JWT-Extended to allow for ignoring CSRF protection on certain types of requests, just like how Flask-WTF operates. I'll try to get this updated today.
Alternately, if your backend is serving JSON instead of html directly (such as a REST api backend and javascript frontend), you can use Ajax to do GET requests with CSRF tokens. In this use case, we could use an Ajax call along these lines.
get (options) {
let self = this
$.ajax({
method: 'GET',
dataType: 'json',
headers: {
'X-CSRF-TOKEN': Cookies.get('csrf_access_token')
},
url: "some_url",
success (result, statusText) {
// Handle success
},
error (jqXHR, textStatus, errorThrown) {
//handle error
}
})
}
EDIT: I also want to preserve the CSRF error messages if the CSRF token isn't present and you are using both headers and cookies for JWTs. Progress on both of these can be tracked here:
https://github.com/vimalloc/flask-jwt-extended/issues/28
https://github.com/vimalloc/flask-jwt-extended/issues/29

Related

JWT token with AJAX, non-AJAX, JQuery

I'm a bit frustrated with managing my JWT token during login, submits and redirects. Before I get started here's my technology stack just in case:
JQuery/Html -> Node.Js -> Java Restful Services -> MySQL.
My java Restful services manages creating the JWT Token returning it to the Node.js layer which decides what to do with it and pass it on the the client. This all works wonderfully.
To get the JWT token I'm making an ajax based authentication request to the Node middle tier, which authenticates and returns the token which is summarily crammed into localstorage on the client.
Now I have no desire what so ever to make the entire site load off a single page through ajax, it's a complex site and doing that is just dumb! I need to forward and navigate to sub pages while carrying along the JWT token.
Here's the question (finally)... How do send along the JWT token to the middle tier (node.js) without attaching it as a request or post parameter because that's a big no no? I can't seem to find a way to stuff it in the header associated with Bearer.
You need to store the token at client side using for example a cookie or localStorage
Ajax requests
Cookies: A cookie is sent automatically when making a request to the server, so you do not need to add a specific header
LocalStorage:It is needed to provide the token in each request using an HTTP header.
For example
POST /authenticatedService
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
This is an example code to show how to execute an ajax POST request using jquery
$.ajax({
type: "POST", //GET, POST, PUT
url: '/authenticatedService' //the url to call
data: yourData, //Data sent to server
contentType: contentType,
beforeSend: function (xhr) { //Include the bearer token in header
xhr.setRequestHeader("Authorization", 'Bearer '+ jwt);
}
}).done(function (response) {
//Response ok. process reuslt
}).fail(function (err) {
//Error during request
});
Form submit
With a form submission you can not control the headers set by browser, so it is not possible to set the Authorization header with a Bearer token. In this case you can
Cookie: store the JWT in a cookie that will be sent with the form data. You will need to add extra security to avoid CSRF attachs
Form param: The JWT is stored in a hidden field of the form.
Use always POST (not GET) to avoid cache of JWT
Link
A link executes a GET request. You could build the link adding the JWT as a query param url?jwt=...
But, consider in this case the security risks. Browser can cache the url and it will be present in logs. An attacker could potentially obtain them if he has access. Also the user could copy the link and use it outside your web application (e.g send it by email...)
If you use cookies, the token will be automatically sent to the server by clicking on the link, but this will only work if the user is authenticated. In this case be aware of CSRF vulnerabilities
Your only option is to store the token in a cookie if you don't want to do anything suggested above. You can't set http headers in links.

Do I have to make a script to make an authorization header for jwt?

I am working on a simple website using jwt. (node.js, koa.js)
Most example codes including expressjs, I cannot find the client-side example
about how to deal with jwt sent from a server.
Only one example (https://github.com/auth0-blog/cookie-jwt-auth) showed me that
[index.html]
... script src="app.js...
[app.js]
$.ajax({
type: 'POST',
url: 'http://localhost:3001/secured/authorize-cookie',
data: {
token: token
},
headers: {
'Authorization' : 'Bearer ' + token
}
After I read this example, I felt that I should have some scripts for users to send an authorization header with jwt. Is it right?
Or are there some front-end frameworks that deal with authorization header?
Thank you for reading newbie'q question.
Yes, you will need to define a mechanism for sending the user's JWT back to the server. It's up to you to decide where the JWT will live in the request -- the most common places are in the Authorization header, or by setting a cookie on the browser (which will be sent along with every HTTP request). You should also consider whether you want the JWT to persist across sessions / page reloads (using for example document.cookie or localStorage).
If you choose not to use the cookie approach, you can configure all $.ajax requests to set your Authorization header "pre-flight" using $.ajaxSetup({...}) (but this is a bit of a sledge-hammer approach). Manually setting the Authorization header on each individual $.ajax request, as you've demonstrated above, is a good option too.
If you want to skip headers all together, you can send the JWT inside the body of your request (as JSON, for example).

Basic Authentication with jQuery.ajax request and jsonp

I have some local html/js files with which I'd like to invoke some remote servers via https and eventually use Basic Authentication for the request.
I am encountering two problems. First is that if I don't specify 'jsonp' for the dataType, jQuery.ajax() request returns the error:
Access to restricted URI denied code:
1012
Are my requests considered cross-domain because my main work file is stored locally, but retrieving data from a server elsewhere?
So fine, I update the call so it now looks like:
$.ajax({
url: myServerUrl,
type: "GET",
dataType: "jsonp", // considered a cross domain Ajax request if not specified
username: myUsername,
password: myPassword,
success: function(result)
{
// success handling
},
error: function(req, status, errThrown){
// error handling
}
})
Because I need to use Basic Authentication, I'm passing in the username/password but if I monitor the request, I don't see it being set and additionally, the server sends an error response since it doesn't have the expected info.
Additionally, because I have jsonp set, beforeSend won't get invoked.
How do I pass along the credentials using Basic Authentication for this request?
The short version is you can't do this. Your suspicions are correct, because you're local and these files are remote, you can't access them, you're being blocked by the same-origin policy. The work-around for that is JSONP, but that really doesn't seem to apply to your situation...
JSONP works differently, it's a GET request via a <script> tag include to get the file, so you're not sending special headers or anything.
You'll need to proxy the request through the server you're on (the domain of where this script is running) or another proxy option, but going from the client to another domain is blocked, mainly for security reasons.
Try doing http://user:password#restservice. This mimics a basic-auth request.
I think you'll have to add a server proxy of some sort. JSONP is just a particular way to use a script tag. Thus, it doesn't allow setting arbitrary headers. And of course, you can't do a cross-origin XHR.

Is it Possible to Make Cross-Domain Requests in Javascript AND Set Custom Headers?

Since you can't apply custom headers on JSONP calls, how do I make cross domain requests AND apply custom headers using jQuery?
I'm basically trying to access google docs with jQuery and need to pass an authentication token:
var token = "my-auth-token";
$.ajax({
url: "http://docs.google.com/feeds/documents/private/full?max-results=1&alt=json",
dataType: 'json',
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "GoogleLogin auth=" + token);
},
success: function(data, textStatus, XMLHttpRequest) {
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
}
});
Note: The goal of this is to completely bypass the application layer. It's simple to use ruby to connect to the Google Data API, but it takes up a lot of resources parsing feeds all the time server-side.
You can use Google's JavaScript client library to query the Docs API. Although it doesn't come with helpers for Docs specifically, it can still be used with most APIs, including Docs. See this blog post by a Google employee that shows a working example.
If you end up in an infinite loop of authorizations, see this related question from Google groups. Basically, the cookies aren't getting set fast enough, so when the JavaScript client library checks, it finds nothing and redirects to the OAuth authorization page. A solution is to either add a small delay before the check is done, or use a login button that initiates the authorization instead of doing it on page load.
You would also need to add any image to your page that resides on the same domain. It can be hidden with CSS, as long as in the DOM.
Using the example in the above blog post, I was able to retrieve my documents list with JavaScript alone. Here's the modified initialize function I used to get rid of the infinite authorization loop:
function initialize() {
var scope = 'http://docs.google.com/feeds/';
if (google.accounts.user.checkLogin(scope)) {
var service = new google.gdata.client.GoogleService('writely', 'DocList-App-v1.0');
service.getFeed(scope + 'documents/private/full/', handleFeed, handleError);
} else {
var loginButton = $("<button>Click here to login</button>");
loginButton.click(function() {
var token = google.accounts.user.login(scope); // can ignore returned token
});
$("body").append(loginButton);
}
};
​
Consider to write some code at the server side which plays for a proxy and let jQuery call it.
If you're using PHP, use curl.
If you're using Java, use URLConnection or the more convenienced Apache HttpClient.
If you're using C#/.NET, use WebClient.
If you're using Ruby, use Net::HTTP.
You can, as long as the external domain allows it by sending an appropriate Access-Control-Allow-Origin header. Then just use the XMLHttpRequest API in browsers that support the standard cross-domain XHR API and XDomainRequest in IE.

How to POST data to an HTTP page from an HTTPS page

I know this is a long shot, but I figured I'd ask the question anyway.
I have an HTTPS page and am dynamically creating a form. I want to POST the form to an HTTP page. Is this possible without the browser popping up a warning? When I do this on IE8, I get the following message:
Do you want to view only the webpage content that was delivered securely?
Essentially, I'm asking about the inverse of question 1554237.
Sadly, I know of absolutely no way to not get warned when posting from HTTPS to HTTP. If you serve the form securely, the browser expects to submit the data securely as well. It would surprise the user if anything else was possible.
Nope, can't be done. Our good friend IE will always pop up that warning.
There is a way to do this if you write a back-end service of your own. So lets say you want to post an HTTP request to s1 using your front-end service fs1.
If you use Spring, you can use an ajax call from fs1 to a 'uri' that is recognized by your spring back-end, say bs1. Now, the service bs1 can make the call to the s1.
Pictorial representation here: http://i.stack.imgur.com/2lTxL.png
code:
$.ajax
({
type: "POST",
uri:/json/<methodName>
data: $('#Form').serialize(),
success: function(response)
{
//handle success here
},
error: function (errorResponse)
{
//handle failure here
}
})
You can solve this by either acting as a proxy for the form destination yourself (i.e. let the form submit to your server which in turn fires a normal HTTP request and returns the response), or to let access the page with the form by HTTP only.
If you don't need to actually redirect to the insecure page, you can provide a web service (authenticated) that fires off the request for you and returns the data.
For example:
From the authenticated page, you call doInsecure.action which you create as a web service over https. doInsecure.action then makes a manual POST request to the insecure page and outputs the response data.
You should be able to do this with the opensource project Forge, but it sounds like overkill. The Forge project provides a JavaScript interface (and XmlHttpRequest wrapper) that can do cross-domain requests. The underlying implementation uses Flash to enable cross-domain (including http <=> https) communication.
http://github.com/digitalbazaar/forge/blob/master/README
So you would load the Forge JavaScript and swf from your server over https and then do a Forge-based XmlHttpRequest over http to do the POST. This would save you from having to do any proxy work on the server, but again, it may be more work than just supporting the POST over https. Also, the assumption here is that there's nothing confidential in the form that is being posted.

Categories

Resources