I'm trying to send a POST request to a Django view from an ordinary Python script using Python-Request. The django view is not #login_required, so the only thing i need to send, other than my JSON data, is a CSRF token, here is what i tried:
token = session.get('http://127.0.0.1:8000/myview/view')
data = json.dumps({'test': 'value'})
session.post('http://127.0.0.1:8000/myview/myview',
data={
'csrfmiddlewaretoken': token,
'data': data})
The django view should just receive the Request and print it to my console:
def myview(request):
if request.method == 'POST':
data = request.POST.get('data')
print(json.loads(data))
print('received.')
response = HttpResponse(get_token(request))
return response
The problem with my current code is that my console will throw a log: WARNING - Forbidden (CSRF token missing or incorrect.). I cannot use #csrf_exempt, since i need this to be as safe as possible. Any advice? Thanks in advance!
Why might a user encounter a CSRF validation failure after logging in?
For security reasons, CSRF tokens are rotated each time a user logs in. Any page
with a form generated before a login will have an old, invalid CSRF token and need to be reloaded. This might happen if a user uses the back button after a login or if they log in a different browser tab.
This also goes for cookies. After you log in, django will send a new csrf cookie to the client. This will be stored in client.cookies and replaces the old one. The django server does not keep any record of the old token, so that's why you get the "CSRF token missing or incorrect." response.
You can access the new token from request.cookies['csrftoken'] as before.
import requests
LOGIN_URL = 'http://127.0.0.1:8000/myview/view'
request = requests.session()
request.get(LOGIN_URL)
# Retrieve the CSRF token first
csrftoken = request.cookies['csrftoken']
r1 = request.post(LOGIN_URL, headers={'X-CSRFToken': csrftoken},
allow_redirects=False))
new_csrftoken = r1.cookies['csrftoken']
data = json.dumps({'test': 'value'})
payload = {'csrfmiddlewaretoken': new_csrftoken,'data':data }
In fact, you can just use the client cookie directly. This would have avoided this bug in the first place. Requests keeps track of cookies for you when you use requests.session().
try :
r2 = request.post('http://127.0.0.1:8000/myview/myview', data=payload, headers={'X-CSRFToken': r1.cookies['crsftoken']})
except :
print('error expected')
Related
I'm currently developing a Django-React web app and using django-rest-framework-simplejwt and dj-rest-auth for authentication.
At first I was storing JWT in frontend cookies (js-cookie) and sending tokens in the headers to get access for restricted endpoints. Since local client cookies are not HttpOnly and after some research I found out that it was not a safe method to store it on the frontend. So I decided not to store them in the client cookies.
It seems like best solution to use HttpOnly cookies, in django settings I declared cookie name as JWT_AUTH_COOKIE = 'myHttpOnlyCookie', so when I make a request from client with username and password to log-in server responses with the cookie that has the access_token.
For the login part, I didn't write any code since dj-rest-auth handles it well so I use their standard loginserializer and view.(https://github.com/jazzband/dj-rest-auth/blob/master/dj_rest_auth/serializers.py). Well maybe I should modify it.
However the problem is I can't add the token in the header of client requests since I'm not storing the token on the client and it is HttpOnly. Well I really don't know how to authenticate the user if I can't send the token in requests.
Once you make a login request to the server, tokens are added to httponly cookie by default. On consecutive requests cookies are sent by default.
Axios request for login.
axios.post('http://localhost:8080/api/auth/login/',
{'email':'test_email', 'password':'test_password'},
{withCredentials:true},
{headers:{
'Content-Type': 'application/json',
'Accept': 'application/json'
}}
)
"withCredentials" must be always set to "true", this will ensure cookies are added to the every request. Once you login, tokens are stored in httponly coolie. For next requests , refer below pseudo code.
const axiosGetReqConfig = {baseURL: '', withCredentials:true, headers:{'Content-Type':'application/json, 'Accept':'application/json'}}
axiosGetReqConfig.get('test/').then(resp => {console.log(resp)}).catch(err => {console.log(err)})
axiosGetReqConfig.interceptors.response.use(
// If request is succesfull, return response.
(response) => {return response},
// If error occured, refresh token and try again.
(error) => {
const originalRequest = error.config;
console.log(originalRequest)
// Make a call to refresh token and get new access token.
axios('http://localhost:8080/api/auth/token/refresh/', {
method:'post',
withCredentials: true
}).then(resp => {
console.log(resp);
}).catch(err => {
// push user to login page.
console.log(err)
})
// Return original request.
return axios.request(originalRequest)
return Promise.reject(error)
}
)
In the above code, I am creating config object using some basic details and implementing interceptors to refresh token if, access token is expired. If refresh token is expired, user will be re-directed to login page.
Main part with including httponly cookie is the variant that we use in making axios request and "withCredentials". There is an open issue with JWT. Since dj-rest-auth uses JWT, if you need to refresh token, you have to implement middleware in django. Refer below link to implement middleware and add that middleware to settings.
https://github.com/iMerica/dj-rest-auth/issues/97#issuecomment-739942573
In this way, I want to set my cookie. But it fails to set.
#app.route('/')
def index():
res = flask.make_response()
res.set_cookie("name", value="I am cookie")
When I print res it shows <Response 0 bytes [200 OK] But not set cookie
You have to return the response after setting the cookie.
#app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('somecookiename', 'I am cookie')
return resp
This way a cookie will be generated in your browser, but you can get this cookie in the next request.
#app.route('/get-cookie/')
def get_cookie():
username = request.cookies.get('somecookiename')
The cookie you set will be visible if you either use other tools to see it (ex: Press F12 for Developer Tools in Firefox or Chrome) or use a piece of code of JavaScript inside the rendered response.
The cookies are set on the browser by either browser itself (JavaScript) or as a response from a server.
The difference is of great importance as even if the cookie is set by the server the cookie might not be set on the browser (ex: cases where cookie are completely disabled or discarded).
So even if the server might tell "I set up the cookie" - the cookie might not be present on the browser.
For the server to be sure that the cookie was set a subsequent request from the browser is needed (with the cookie present in the request's headers).
So even if the Flask's response (res variable) will mention that the cookie is set we can only be sure that it was set by the server but it will have no confirmation about it from the browser.
Advanced
Another aspect is about how Flask or any other API is creating the responses. As the payload (html/template code) and headers (cookie) are set at same time - the "code" inside the payload (html/template code) might not have access to the cookie.
So you might not be able to set a cookie and display it in the response.
An idea might be to (be able to) first set the cookies and THEN to render the context and the order of setup to be important - so that the html/template to be aware of already setup values. But even in this case is only the server's confirmation that it set up the cookie.
A solution
#app.route('/')
def index():
res = flask.make_response()
res.set_cookie("name", value="I am cookie")
# redirect to a page that display the cookie
resp.headers['location'] = url_for('showcookies')
return resp, 302
This response will set cookie in you browser
def func():
response = make_response( render_template() )
response.set_cookie( "name", "value" )
return response
I have a Flask-Admin app running with Flask-Security and use a token based authentication for API calls. Everything was working correctly. The following method is what returned the token.
def get_token(self):
url = 'http://nginx/login'
headers = {'Content-Type':'application/json'}
data = {'email':os.environ['SCRAPER_USERNAME'],
'password':os.environ['SCRAPER_PASSWORD']}
response = requests.post(url, headers=headers, data=json.dumps(data))
jsonresponse = json.loads(response.text)
self.token = jsonresponse['response']['user']['authentication_token']
I have now added templates to the login and logout pages of Flask-Admin and changed the urls to /admin/login. So I changed the one line from url = 'http://nginx/login' to url = 'http://nginx/admin/login' I thought this was all that was necessary but unfortunately I am now experiencing issues. This method now returns a 400 error when trying to load nginx/admin/login.
Nginx is the server running in a docker container. So it appears creating custom templates has caused the issue. Would anyone know why and how to resolve it?
I am trying to login to a website in order to get some data. I have noticed that there is not form-data in the 'post' method but there is a 'request payload'. Furthermore, when I login in I cannot see anymore the login post method. Here is a screenshot of the network post login method:
When I login the next page showed is I use the following code in order to login:
import requests
urlData = 'https://b*********.dk/Account/Market'
urlLogin = 'https://b**********an.dk/
with requests.Session() as c:
urlLogin = 'https://b*************n.dk/Authorization/
c.get(urlLogin)
NetSession = c.cookies['ASP.NET_SessionId']
login_data = {
'ASP.NET_SessionId': NetSession,
'username':"A******",
'Password':"q******",
'remmemberMe': True
}
lol = c.post(urlLogin, data=login_data)
print(lol.text)
Running this code the following is outputed:
{"Processed":true,"Message":"The user name or password provided is incorrect.","NeedResetPassword":false}
When i input a wrong password the Processed value is false, while with correct credentials is true. But it deosnt login. Any idea why this could happen?
As you've already correctly noticed, the original credentials are not sent using form encoding (meaning &user=alice&password=secret), but are JSON encoded (so rather {"user":"alice", "password": "secret"}). You can also see this in the request's Content-Type header, which is application/json where (as opposed to application/x-www-form-urlencoded otherwhise).
For your custom request to work, you propably also need to send JSON-encoded data. This is documented in length in the official Documentation, so I'll just give the short version:
import json
# Build session and request body just like you already did in your question
# ...
headers = {"Content-Type": "application/json"}
lol = c.post(urlLogin, data=json.dumps(login_data), headers=headers)
print(lol.json())
I am trying to authenticate user using FIWARE.
It returns a 404. Thus fails at Step 1 itself. What is the access token url ? Any other pointers to check
I have tried variations with 'oauth/access_token', 'oauth/token' 'oauth2/token' 'oauth2/access_token' . All of them dont seem to work.
My Code is Below:
import oauth2 as oauth
# OAuth secret into your project's settings.
consumer = oauth2.Consumer(settings.FIWARE_CLIENT_ID,settings.FIWARE_CLIENT_SECRET)
client = oauth2.Client(consumer)
access_token_url = 'https://account.lab.fiware.org/oauth2/access_token'
# This is the slightly different URL used to authenticate/authorize.
authenticate_url = 'https://account.lab.fiware.org/oauth2/authorize'
def fiware_login(request):
# Step 1. Get a request token from FIWARE.
resp, content = client.request(access_token_url, "GET")
print resp
if resp['status'] != '200':
print content
raise Exception("Invalid response from FIWARE.")
# Step 2. Redirect the user to the authentication URL.
url = "%s?access_token=%s" % (authenticate_url,
resp['access_token'])
return HttpResponseRedirect(url)
Correct endpoint is "/oauth2/token".
Maybe you should use POST method instead of GET.
For more information see https://github.com/ging/fi-ware-idm/wiki/Using-the-FI-LAB-instance