I'm writing an hybrid web app using flask. By hybrid I mean that there is the conventional web server application built with template engine and there is RESTful API for client side application as well. So here is my confusion:
In my current application, user logs in through the web server so that an HTTP session is created, the user then can do stuff. However, in one of the pages, there is a action that is done via AJAX call to the RESTful part of the same application. Normally in this API, the user will have to authenticate itself again. But here the client side code has no way of knowing the user name and password. What's the correct pattern here?
You can authenticate the user client side in ajax call:
For example:
$.ajax({
url: 'http://example.com/api.ashx/v2/users.xml',
beforeSend: addHeaders,
dataType: "text",
processData: false,
success: function(data, status) {
// do stuff here
},
error: function(xhr, status, error) {
// do stuff here
}
});
var addHeaders = function(xhr) {
var restAuthHeader = readCookie("AuthorizationCookie");
if (restAuthHeader != null) {
xhr.setRequestHeader("Rest-Authorization-Code", restAuthHeader);
}
};
var readCookie = function(input) {
var nameEQ = input + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[ i ];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length, c.length);
}
return null;
};
Let's say you have a form with username and password to authenticate.
<form id="login-form">
<input data-key="username" type="text" placeholder="username" />
<input data-key="password" type="password" placeholder="password" />
<button type="submit">Login</button>
</form>
Your endpoint should return a token and a userid.
var $form = $('#login-form');
// post to your login endpoint with username and password
$.post('/login', {
username: $form.find('input[data-key="username"]').val(),
password: $form.find('input[data-key="password"]').val();
}).done(function (response) {
// put the token and userid in the sessionStorage or localStorage
window.sessionStorage.setItem('token', response.data.token);
window.sessionStorage.setItem('userId', response.data.userId);
}).fail(function (e) {
// handle incorrect credentials here.
alert('authentication failed');
});
You should append these to your headers to request data.
function requestEndpoint(endpoint) {
$.ajax({
// other stuff here you probably know
headers: {
'X-Auth-Token': window.sessionStorage.getItem('token'),
'X-User-Id': window.sessionStorage.getItem('userId'),
'Content-Type': 'application/json'
}
});
}
Just scan for these headers at the endpoints in flask
Related
I'm extremely new to React and Python and just trying to do a simple post from a react form to my Python API that will interface with a mongoDB.
I have a form in react that invokes a handleSubmit function on submit. I want the handleSubmit function to POST to my Python API running on port 5000. My react app is running on port 8080.
The handleSubmit looks like this:
handleSubmit(event) {
const axios = require('axios');
const baseUrl = 'http://localhost:5000'
axios.post('http://localhost:5000/api/create', JSON.stringify(params))
.end((error, response) => {
if (!error && response) {
console.log('got a valid response from the server')
} else {
console.log(`Error fetching data from the server: `, error)
}
});
event.preventDefault();
}
Python endpoint code:
#app.route('/api/create', methods=['POST'])
def create(self):
if request.method == 'POST':
print(request.args.get('exp_title'))
return True
return False
When I click the button, my python API endpoint isn't reached because react is trying to post to a route on port 8080. What am I missing?
I've tried using a regular ajax call and get the same result. At one point, I did something and got a CORS error in the browser, but I can't remember how I did that.
To enable cors, you need to install pip install -U flask-cors,
here is the website: https://flask-cors.readthedocs.io/en/latest/
or you can define cors in proxy in your reactjs package.json like here:
https://facebook.github.io/create-react-app/docs/proxying-api-requests-in-development
Once you install cors in your python app, try this:
Python app:
#app.route('/api/', methods=['POST', 'GET'])
def api_post():
if request.method == 'POST':
print('post app')
req = request.json
print(req)
return jsonify(name='john')
React app:
function App() {
const [todos, setTodos] = useState(null);
const [value, setValue] = useState('');
function handleSubmit(e) {
e.preventDefault();
const data = { name: value };
console.log('submit');
console.log(value);
fetch('http://127.0.0.1:5000/api/', {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
})
.then(res => res.json())
.then(res => console.log(res));
}
function handleValue(e) {
setValue(e.target.value);
}
return (
<section id="app">
<form action="" onSubmit={handleSubmit}>
<input type="text" onChange={handleValue} />
<button> submit </button>
</form>
</section>
);
}
render(<App />, document.querySelector('#root'));
I have installed Chatterbot for Django integration. I followed the easy tutorial with every step and made it so that the endpoint was: http://127.0.0.1:8000/chatterbot/ What I did next was try to communicate with that endpoint to see if I would get back any results. So I made an Ajax request as follows:
var query = {"text": "My input statement"};
$.ajax({
type: 'POST',
url: "http://127.0.0.1:8000/chatterbot/",
data: JSON.stringify(query),
contentType: 'application/json',
success: function (data) {
console.log(data);
}
});
However, what returns in console is: POST http://127.0.0.1:8000/chatterbot/ 403 (Forbidden) and what returns in the cmd prompt when I run my server is:
csrf: WARNING - Forbidden (CSRF token missing or incorrect.):
/chatterbot/ [29/Mar/2018 02:16:43] "POST /chatterbot/ HTTP/1.1" 403
2502
Why am I getting this error? How can I fix it so I receive the call back from the endpoint?
View for this page:
def IndexView(request):
latest_questions = Questions.objects.all().order_by("-date_published")[:5]
popular_questions = Questions.objects.all().order_by("-num_replies")[:5]
return render(request, 'core/index.html',
{'latest_questions': latest_questions, 'popular_questions': popular_questions
})
Try this code
// using jQuery
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
var query = {
"text": "My input statement",
"csrfmiddlewaretoken": csrftoken
};
$.ajax({
type: 'POST',
url: "http://127.0.0.1:8000/chatterbot/",
data: query,
contentType: 'application/json',
success: function (data) {
console.log(data);
}
});
one way is to send the csrfmiddlewaretoken like below
var query = {
"text": "My input statement",
'csrfmiddlewaretoken': "{{csrf_token }}"
};
other way is to use #csrf_exempt decorator
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def IndexView(request):
# .... code.....
other is to add a script
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
Reference: https://docs.djangoproject.com/en/2.0/ref/csrf/
If you dont want to use CSRF tokens just add this above your code.
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def IndexView(request):
# your code
We use AWS Cognito for authentication.
When we create a user, Cognito sends the following email with the following message:
Your username is {username} and temporary password is {####}.
as we know, the user is created with FORCE_NEW_PASSWORD status.
is that possible somehow to add access token to the email body so as to form a link to the page where user may change their password to activate account?
I have used aws-cognito in node js and angular2.
When you attempt first time login then user injects the temporary credentials, after this an OTP will be sent to either phone or email ( decided by user pool ).
The following is function used for login:
var authenticationData = {
Username: username, // req.body.username
Password: password // req.body.password
};
var poolData = {
UserPoolId: upid,
ClientId: cid,
AuthFlow: 'ADMIN_NO_SRP_AUTH'
};
var userPool = new AWS.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var authenticationDetails = new AWS.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: username,
Pool: userPool
};
var cognitoUser = new AWS.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
// not for first time login case and user has permanent credentials
},
onFailure: function (err) {
res.send(err); //login failure
},
newPasswordRequired: function (userAttributes, requiredAttributes) {
response = {"ChallengeName": "NEW_PASSWORD_REQUIRED", "userAttributes": userAttributes};
localStorage.setItem('userAttributes', JSON.stringify(userAttributes)); // I have used localStorage to save data temporarily
res.send(response);
}
}
Now, as you are asking for first time login.
So you need to pass the old password and username, user attributes to next API call to get the permanent credentials.
I have not sent token in email message
and keep that in localStorage so that can be utilised when user come back to the browser this way you will get the ID Token.
so you can use the code for update password as follows:
router.post('/updatepassword', function (req, res) {
var username = req.body.username;
var newPassword = req.body.newpassword;
var userAttributes = req.body.userAttributes;
var oldpassword = req.body.oldpassword;
var userPool = globalConfiguration(); // custom function to get pool data
var userData = {
Username: username, //req.body.username,
Pool: userPool
};
var params = {
UserPoolId: 'us-west-2_XxxxxXX', /* required */
Username: username, //req.body.username,
};
var authenticationData = {
Username: username, //req.body.username,
Password: oldpassword, //req.body.password,
};
var authenticationDetails = new AWS.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
// so only username and previous password are required.
var cognitoUser = new AWS.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) { },
onFailure: function (err) { },
newPasswordRequired: function (userAttributes, requiredAttributes) {
// the api doesn't accept this field back
delete userAttributes.email_verified;
delete userAttributes.phone_number_verified;
cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
var success = {'success': 'success'};
res.send(success);
}
});
});
Hope this will helps you!
I am working on REST API using Django and Django Rest Framework.
In the front-end I have AngularJs app.
I have used Token authentication for APIs. This disables the CSRF checks.
I want keep CSRF validation with REST API. How to achieve this ?
How should I get CSRF Token value so that it can be set into the header of every POST request, using interceptor of my angular application.
I have this configuration in my app.js. Should do the trick!
app.config(function($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
});
I've got the same problem when i started to use Angular 1.x with Django and DRF, and then i found this code snippet in a book i think, and it works fine for me. Include this file in your base.html file or your main html file before any javascript import, and everything will work smoothly and you can start talking to your backend.
// Place at /static/js/csrf.js
// CSRF helper functions taken directly from Django docs
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/ ˆ (GET|HEAD|OPTIONS|TRACE) $ /.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
I have some AJAX that polls the server every 5 seconds:
var date = $('article').first().find('time').text();
console.log(date);
setInterval(function() {
$.post('pollNewEntries', {'date':date}, newEntrySuccess)
}, 5000);
Unfortunately, I'm getting a 403 error every time the AJAX tries to poll the server, stating that I have made an invalid CSRF request. I've used AJAX with forms before and included the CSRF token within the forms, but I"m not sure how I would do it with a formless AJAX request like above.
The solution to this problem is described in the Django documentation: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
Add this code to the top of your js:
$.ajaxSetup({
beforeSend: function(xhr, settings) {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
You need to pass csrf token along with your post data:
var date = $('article').first().find('time').text();
console.log(date);
setInterval(function() {
$.post('pollNewEntries', {'date':date, 'csrfmiddlewaretoken': '{{csrf_token}}'}, newEntrySuccess)
}, 5000);
Simply add these lines in your script. Here is an example in coffeescript :
### CSRF methods ###
csrfSafeMethod = (method) ->
# these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method))
$.ajaxSetup(
crossDomain: false
beforeSend: (xhr, settings) ->
if !csrfSafeMethod(settings.type)
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'))
)
Read the documentation : CSRF
In other hand, as user1427661 suggests to you, it will be better to use HTTP GET method instead of POST, because you only need to read data and don't write anything. See the W3 docs.