Django REST API Logout request - javascript

For logging in, I'm doing something like:
function setHeader(xhr) {
// as per HTTP authentication spec [2], credentials must be
// encoded in base64. Lets use window.btoa [3]
xhr.setRequestHeader("Authorization", "Basic " + btoa(username + ':' + password));
}
$.ajax({type: "POST", url: AUTH_URL, beforeSend: setHeader}).
fail(function(resp){
console.log('bad credentials.')
}).
done(function(resp){
});
after which, I'm storing the Session in local storage.
However, for logging out, I'm unable to figure out how to use this session to send with the request header, so that django's : request.logout() logs out the user having that session id

For login you can add view similar to this one:
import json
import requests
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
#csrf_protect
def login(request):
if request.method == "POST":
login = requests.post('http://your_url/api-token-auth/', data={'username': request.POST['username'], 'password': request.POST['password']})
response = json.loads(login.text)
if response.status_code == 200:
token = response['token']
request.session.flush()
request.session['user'] = request.POST['username']
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponseRedirect("/")
else:
error = "Error"
request.session.set_test_cookie()
return render_to_response("login.html", {"error": error}, RequestContext(request))
For logout all you have to do in your view is:
def logout(request):
request.session.flush()
return HttpResponseRedirect('/')
On your API side, you have to define api-token-auth in urls: here is the tutorial for more informations
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
This way you will get your token for communication with the API. Beside TokenAuthentication you can define and SessionAuthentication. More about that you can find in the above tutorial

You are using HTTP Basic Authentication, which does not define a way to log users out. It is not tied to the Django session, so you can't clear that. You could potentially clear out the token from session storage, and send an invalid token, though the browser may opt to send the original credentials (untested).
There are quite a few questions about it on Stack Overflow. Your best bet looks like sending invalid credentials, hoping that the user's browser will invalidate any saved ones.
You may be able to use a form of token-based authentication, such as TokenAuthentication or OAuth, which will not be intercepted by the browser. This way you will not need to worry about logging users out, as the authentication is tied directly to requests made with the token.

Related

FastAPI rejecting POST request from javascript code but not from a 3rd party request application (insomnia)

When I use insomnia to send a post request I get a 200 code and everything works just fine, but when I send a fetch request through javascript, I get a 405 'method not allowed error', even though I've allowed post requests from the server side.
(Server side code uses python).
Server side code
from pydantic import BaseModel
from typing import Optional
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["POST", "GET"],
allow_headers=["*"],
)
class data_format(BaseModel):
comment_id : int
username : str
comment_body : Optional[str] = None
#app.post('/post/submit_post')
async def sumbit_post(somename_3: data_format):
comment_id = somename_3.comment_id
username = somename_3.username
comment_body = somename_3.comment_body
# add_table_data(comment_id, username, comment_body) //Unrelated code
return {
'Response': 'Submission received',
'Data' : somename_3
}
JS code
var payload = {
"comment_id" : 4,
"username" : "user4",
"comment_body": "comment_4"
};
fetch("/post/submit_post",
{
method: "POST",
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json'
}
})
.then(function(res){ return res.json(); })
.then(function(data){ alert( JSON.stringify( data ) ) })
The error
What should I do to get around this error?
Thanks in advance.
To start with, your code seems to be working just fine. The only part that had to be changed during testing it (locally) was the URL in fetch from /post/submit_post to (for instance) http://127.0.0.1:8000/post/submit_post, but I am assuming you already changed that using the domain name pointing to your app.
The 405 Method Not Allowed status code is not related to CORS. If POST was not included in the allow_methods list, the response status code would be 400 Bad Request (you could try removing it from the list to test it). From the reference above:
The HyperText Transfer Protocol (HTTP) 405 Method Not Allowed response
status code indicates that the server knows the request method, but
the target resource doesn't support this method.
The server must generate an Allow header field in a 405 status code
response. The field must contain a list of methods that the target
resource currently supports.
Thus, the 405 status code indicates that the POST request has been received and recognised by the server, but the server has rejected that specific HTTP method for that particular endpoint. Therefore, I would suggest you make sure that the decorator of the endpoint in the version you are running is defined as #app.post, as well as there is no other endpoint with the same path using #app.get. Additionally, make sure there is no any unintentional redirect happening inside the endpoint, as that would be another possible cause of that response status code. For future reference, when redirecting from a POST to GET request, the response status code has to change to 303, as shown here. Also, you could try allowing all HTTP methods with the wildcard * (i.e., allow_methods=['*']) and see how that works (even though it shouldn't be related to that). Lastly, this could also be related to the configurations of the hosting service you are running the application; thus, might be good to have a look into that as well.
It's and old issue, described here. You need Access-Control-Request-Method: POST header in your request.

Flask Session Not Persisting (Postman works, Javascript doesn't)

I'm developing a Flask server to communicate between some backend Python functionality and Javascript clients over the web. I'm attempting to utilize Flask's session variable to store user specific data over the course of their time interacting with the app. I've removed most of the application specific code below but the core problem I'm experiencing remains.
Here is my the code for my (simplified) Flask app:
import json
import os
from flask import Flask, jsonify, request, session
app = Flask(__name__)
app.secret_key = 'my_secret_key'
#app.route('/', methods=['GET'])
def run():
session['hello'] = 'world'
return jsonify(session['hello'])
#app.route('/update', methods=['POST'])
def update():
return jsonify(session['hello'])
if __name__ == '__main__':
app.run(host='0.0.0.0')
Utilizing Postman, I can make a GET request to my server and receive the expected output of "world". I can then make a POST request with an arbitrary body and receive the same expected output of "world" (again using Postman).
When using Chrome, I can visit my server IP and see the expected output "world" on the page. I can also manually make a GET request using Javascript (in Chrome's console) and receive the same response as expected. However, my problem arises when trying to send a POST request to the server using Javascript; the server shows a KeyError: 'hello' when trying to make this request.
Here is the Javascript I'm using to make the POST request:
var url = 'http://my_server_ip/update';
fetch(url, {
method: 'POST',
body: JSON.stringify('arbitrary_string'),
headers: new Headers({
'Content-Type': 'application/json'
})
})
.then(response => response.json())
.then((data) => {
console.log(data);
})
What's going wrong here? Why can I make the GET/POST requests with Postman just fine but run into errors making the same requests with Javascript?
The caveats section of the fetch documentation says:
By default, fetch won't send or receive any cookies from the server, resulting in unauthenticated requests if the site relies on maintaining a user session.
It is recommended to use AJAX to exchange information with Flask views.
Meanwhile, in your code for the Flask app, the session object is a dictionary. Now, if you access a dictionary with its key session['hello'] and if this key does not exist, a Keyerror is raised. To get around this error, you can use the get() method for dictionaries.
What is happening is: the fetch request does not find the hello key(or GET the session value from the Flask view) in the Flask session.
user = session.get('hello')
return jsonify(session_info=user)
But this will still give you a null value for the session { session_info: null }. Why is that so?
When you send GET/POST requests to the Flask server, the session is initialized and queried from within Flask. However, when you send a Javascript fetch POST request, you must first GET the session value from Flask and then send it as a POST request to your Flask view which returns the session information.
In your code, when the POST request is triggered from fetch, when I send the payload data to Flask, it is received correctly and you check this using request.get_json() in the Flask view:
#app.route('/update', methods=['POST'])
def update():
user = session.get('hello')
payload = request.get_json()
return jsonify(session_info=user, payload=payload)
This will return { payload: 'arbitrary_string', session_info: null }. This also shows that fetch does not receive the session information because we did not call GET first to get the session information from Flask.
Remember: The Flask session lives on the Flask server. To send/receive information through Javascript you must make individual calls unless there is a provision to store session cookies.
const fetch = require('node-fetch');
var url_get = 'http://my_server_ip';
var url_post = 'http://my_server_ip/update';
fetch(url_get, {
method:'GET'
}).then((response)=>response.json()).then((data) =>fetch(url_post, {
method: 'POST',
body: JSON.stringify(data),
dataType:'json',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then((postdata) => {
console.log(postdata);
}));
The Flask views will change slightly:
#app.route('/', methods=['GET'])
def set_session():
session['hello'] = 'world'
return jsonify(session['hello'])
#app.route('/update', methods=['POST'])
def update():
payload = request.get_json()
return jsonify(session_info=payload)
When you trigger the Javacript request now, the output will be: { session_info: 'world' }
After a few hours of testing, I managed to figure out the issue. Although I think #amanb's answer highlights the problem, I'm going to answer my own question because what I found is ultimately a simpler solution.
In order to make the POST request return the expected value, I simply needed to add a credentials: 'same-origin' line to the fetch body. This looks like the following:
var url = 'http://my_server_ip/update';
fetch(url, {
method: 'POST',
body: JSON.stringify('arbitrary_string'),
credentials: 'same-origin', // this line has been added
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then((data) => {
console.log(data);
})
According to Mozilla's Fetch usage guide,
By default, fetch won't send or receive any cookies from the server,
resulting in unauthenticated requests if the site relies on
maintaining a user session.
So it seems I looked over this. Changing the credentials to allow communication of the cookie/session between client and server resolved the issue.

Python Requests - Login to Vimeo.com

I have an account at Vimeo.com and am trying to figure out how to log in using the Python Requests package. Here are my initial observations:
Logging in is done via a post request to https://vimeo.com/log_in?ssl=0&iframe=0&popup=0&player=0&product_id=0 and requires an email, password, action, service, and token to be passed as body data
The token for the post request appears to be different from the xsrft token but I'm not able to get either as the token appears to be generated via javascript and only valid for each session (though it isn't clear)
Cookies are necessary
Here is some initial code:
import requests
from bs4 import BeautifulSoup
url = 'http://vimeo.com/log_in?ssl=0&iframe=0&popup=0&player=0&product_id=0'
payload = {'email': 'some#email.com',
'password': 'privatepassword',
'action': 'login',
'service': 'vimeo',
'token': '?????'
}
response = requests.post(url, data=payload, allow_redirects=True)
soup = BeautifulSoup(response.text)
I can't figure out how to get the correct token for the post request and whether the token is associated with the vuid or xsrft token.

Preventing HTTP Basic Auth Dialog using AngularJS Interceptors

I'm building an AngularJS (1.2.16) web app with a RESTful API, and I'd like to send 401 Unauthorized responses for requests where authentication information is invalid or not present. When I do so, even with an HTTP interceptor present, I see the browser-presented basic "Authentication Required" dialog when an AJAX request is made via AngularJS. My interceptor runs after that dialog, which is too late to do something useful.
A concrete example:
My backend API returns 401 for /api/things unless an authorization token is present. Nice and simple.
On the AngularJS app side, I've looked at the docs and set up an interceptor like this in the config block:
$httpProvider.interceptors.push(['$q', function ($q) {
return {
'responseError': function (rejection) {
if (rejection.status === 401) {
console.log('Got a 401')
}
return $q.reject(rejection)
}
}
}])
When I load my app, remove the authentication token, and perform an AJAX call to /api/things (to hopefully trigger the above interceptor), I see this:
If I cancel that dialog, I see the console.log output of "Got a 401" that I was hoping to see instead of that dialog:
Clearly, the interceptor is working, but it's intercepting too late!
I see numerous posts on the web regarding authentication with AngularJS in situations just like this, and they all seem to use HTTP interceptors, but none of them mention the basic auth dialog popping up. Some erroneous thoughts I had for its appearance included:
Missing Content-Type: application/json header on the response? Nope, it's there.
Need to return something other than promise rejection? That code always runs after the dialog, no matter what gets returned.
Am I missing some setup step or using the interceptor incorrectly?
Figured it out!
The trick was to send a WWW-Authenticate response header of some value other than Basic. You can then capture the 401 with a basic $http interceptor, or something even more clever like angular-http-auth.
I had this issue together with Spring Boot Security (HTTP basic), and since Angular 1.3 you have to set $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; for the popup not to appear.
For future reference
I've come up with this solution when trying to handle 401 errors.
I didn't have the option to rewrite Basic to x-Basic or anything similar, so I've decided to handle it on client side with Angular.
When initiating a logout, first try making a bad request with a fake user to throw away the currently cached credentials.
I have this function doing the requests (it's using jquery's $.ajax with disabled asynch calls):
function authenticateUser(username, hash) {
var result = false;
var encoded = btoa(username + ':' + hash);
$.ajax({
type: "POST",
beforeSend: function (request) {
request.setRequestHeader("Authorization", 'Basic ' + encoded);
},
url: "user/current",
statusCode: {
401: function () {
result = false;
},
200: function (response) {
result = response;
}
},
async: false
});
return result;
}
So when I try to log a user out, this happens:
//This will send a request with a non-existant user.
//The purpose is to overwrite the cached data with something else
accountServices.authenticateUser('logout','logout');
//Since setting headers.common.Authorization = '' will still send some
//kind of auth data, I've redefined the headers.common object to get
//rid of the Authorization property
$http.defaults.headers.common = {Accept: "application/json, text/plain, */*"};

API Server giving 401 error after login

I have successfully logged in to the API Server using following code in meteorjs
var request = Npm.require('request');
request('http://api-server-link-here',
{
'auth' : {
'user': 'username',
'pass': 'password',
'sendImmediately': false
}
}
, function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(response);
}
But after this login when i go for using search query using code below. I get Http Status 401 error.
var request = Npm.require('request');
request("http://search-query-link-here",
{
//search query parameters are here
},
function(error,response,body){
console.log(response);
});
Can anybody please give me pointers of why this issue is happening. Or if this is possible that i get a working example here?
One more thing that needs to be told here is that I'm doing login with the help of digest authentication.
Obviously, it's kind of hard to provide you with a working example for an unknown API.
But let's just try to sort it out. Logically, when you send your search query, you are supposed to identify yourself. Most of the public API servers use either tokens (most probably) or session cookies in order to authorize access to a resource. So, whenever you do your second (search) request, you have either provide a token or send a cookie you are getting from the first (login) request.
Long story short, verify which mechanism is being used by the API server.
if it's tokens: check the response body from the login request; most probably there will be an access token you'd send then in the Authorization header of your API calls: headers: {'Authorization': 'Bearer ...'} in your request options;
if it's sessions: make sure you have the cookie jar enabled on login and you it for subsequent requests: https://github.com/mikeal/request#requestjar

Categories

Resources