Not persistent connection for flutter/dart http.Client() - python

I have a running django-server that works with sessions. A simple example from my views.py that should be enough to reproduce my problem is given here:
def test(request):
print("Session objects(")
for k,v in request.session.items():
print(k,v)
print(")")
request.session["a"] = "b"
So this does just print everything in the current session and after that saving some dummy-data in the session. If I do access this via my browser the first time the output is
Session objects(
)
so the session is empty just like expected. Then after refreshing the site the output is:
Session objects(
a b
)
also as expected, so everything seems to work just fine.
But now I want to use the site with my flutter app. For that I used the flutter packacke import 'package:http/http.dart' as http like this:
var client = http.Client();
String host = ...; // just the ip:port to my host
void my_request() async {
var response = await client.get(host + "/path/to/test/");
response = await client.get(host + "/path/to/test/");
}
So everything this should do is requesting my site twice just like i did before in the browser manually. But now my server just logges twice:
Session objects(
)
So obviously the client has a not persistent connection where the session is not preserved. But according to the doc https://pub.dev/packages/http this should work
If you're making multiple requests to the same server, you can keep open a persistent connection by using a Client rather than making one-off requests
is this a problem with my flutter/dart app or is the problem on my server? Is it maybe a big in the flutter package?
note: I first thought this could be a problem with csrf-authentication so deactivated it on my server, but this doesn't change anything ...

You don't need a 3rd-party library. It's a fairly small amount of code. So, after a first authorized request, server will respond with a cookie in the response headers, under the key set-cookie. The useful information is in the value of set-cookie key, from the beginning, to the 1st occurrence of the ; character (more on this later). For example, the value of set-cookie might look like this:
sessionid=asfweggv7bet4zw11wfpb4u415yx; expires=Fri, 06 Nov 2020 11:14:40 GMT;
You need to save it and use every time in your next authorized requests.
To save it, I created a method, which you should call after the first authorized response. You can call it after every response (if you have generic response handling), since it won't mess with the existing cookie if the server didn't send a new one.***
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
const kCookie = 'my_fancy_cookie';
// ...
void _storeCookie(http.Response response) async {
String rawCookie = response.headers['set-cookie'];
if (rawCookie != null) {
int index = rawCookie.indexOf(';');
String cookie = (index == -1) ? rawCookie : rawCookie.substring(0, index);
await FlutterSecureStorage().write(key: kCookie, value: cookie);
}
}
And then before I send my request, I add the cookie to headers:
// endpoint, payload and accessToken are defined earlier
cookie = await FlutterSecureStorage().read(key: kCookie);
http.Response response = await http.post(
endpoint,
body: json.encode(payload),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer ' + accessToken,
'cookie': cookie,
}
);
Remember to clear the cookie from secure storage after logout. :)
*** - Servers might change the session id (to reduce things like clickjacking that we don't need to consider yet), so it's good to keep extracting the cookie from every response.

The browser is saving/sending the Django session cookie. The flutter app http requests are not.
"Persistent" connection does not mean it will handle cookies for you. You will need to do that yourself or find a third-party library that does it.

Related

Accomplishing Oauth2.0 authorization with refresh token through Python (Google API service creation)

I'm trying to access Google API services through a headless Linux server using Oauth2. I read through all the answers on this post: How do I authorise an app (web or installed) without user intervention? but none of them showed how to use the refresh token to generate an access token in python. pinnoyyid had a javascript example (https://stackoverflow.com/a/19766913/15713034) that went something like this:
function get_access_token_using_saved_refresh_token() {
// from the oauth playgroundfunction get_access_token_using_saved_refresh_token() {
// from the oauth playground
const refresh_token = "1/0PvMAoF9GaJFqbNsLZQg-f9NXEljQclmRP4Gwfdo_0";
// from the API console
const client_id = "559798723558-amtjh114mvtpiqis80lkl3kdo4gfm5k.apps.googleusercontent.com";
// from the API console
const client_secret = "WnGC6KJ91H40mg6H9r1eF9L";
// from https://developers.google.com/identity/protocols/OAuth2WebServer#offline
const refresh_url = "https://www.googleapis.com/oauth2/v4/token";
let refresh_request = {
body:`grant_type=refresh_token&client_id=${encodeURIComponent(client_id)}&client_secret=${encodeURIComponent(client_secret)}& refresh_token=${encodeURIComponent(refresh_token)}`;,
method: "POST",
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
}
JavaScript isn't really my best language, but I could decipher they were sending a POST request to the google server. So I tried to recreate the request in Python with the requests package:
import requests
result = requests.post("https://www.googleapis.com/oauth2/v4/token", body={'grant_type':'refresh-token', 'client_id':client_id, 'client_secret':client_secret, 'refresh_token': refresh_token}, headers={'Content-Type': 'application/x-www-form-urlencoded'})
And when I look at result it shows it has a 200 status code (success) but when I try to examine the response, there's nothing easy to read and I can't parse the result in JSON to get the access token. The other approach I tried was to spin up a Flask server using Google's suggested code: https://developers.google.com/identity/protocols/oauth2/web-server#python_5 but that doesn't work either because when I try to return the credentials from one of the functions (object that contains the access code) that won't return JSON no matter what. I'd prefer the post request method since it is cleaner and uses less code. Thanks!
In Python, one approach is to use requests-oauthlib to perform the Backend Application Flow. This is useful when you don't have a front-end to redirect someone to, in order to approve fetching a token.
This website (https://community.atlassian.com/t5/Bitbucket-questions/Refresh-Tokens-using-Python-requests/qaq-p/1213162) says solution could be something like this:
import requests
auth = ("<consumer_id>", "<consumer_secret>")
params = {
"grant_type":"refresh_token",
"refresh_token":"<your_refresh_token_here>"
}
url = "https://www.googleapis.com/oauth2/v4/token"
ret = requests.post(url, auth=auth, data=params) #note data=params, not params=params
Since none of the solutions above worked, I had to finally just give up and use a service account.

How to authenticate user with JWT and HttpOnly cookies

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

Python Requests HTTP POST response cookie missing / hiding fields

new to using Python requests lib, and looking for a little help around accessing cookies...
I am unable to get access to all of the fields within a cookie that I get from the following code using the Requests library - similar code from GoLang or Postman all work fine, but for some reason i am missing a few key fields that I need from Python-land. Sample code is as follows:
import requests
# Host base URL
host = 'sampleurl.link/endpoint'
# Username and password for login to API endpoint
credentials = "username=sausage123%40spaniel.org&password=Passw0rd"
ses = requests.Session()
authString = ''
def auth(host):
payload = credentials
headers = {
'Content-Type': "application/x-www-form-urlencoded",
}
ses.post("https://" + host + "/auth", data=payload, headers=headers)
cookies = ses.cookies.get_dict()
print(cookies.items())
authString = "; ".join([str(x)+"="+str(y) for x,y in cookies.items()])
print(authString)
auth(host)
The output is as follows:
[('systemid3', 'eSgRCbaH2EWyBVbRyfBS7xfftYCAqE-BRaon1Uc350pi14qTVgsmDXLrK9TDJvPsKmAzgw==')]
However, from the same API call in GoLang or Postman equiv. i get all of the required fields including path and expires:
systemid3=eSgRCbaH2EWyBVbRyfBS7xfftYCAqE-BRaon1Uc350pi14qTVgsmDXLrK9TDJvPsKmAzgw==; Path=/; Expires=Sat, 21 Sep 2019 09:37:46 GMT
Note that Postman also gives me the same 'Path' and 'Expires' fields etc.
How do i get Requests lib to give me access to all of the fields in the cookie, not just the first 2? Why is it hiding / removing the othe cookie fields?
Thanks in advance!
It's because you're using .items(), it only gets cookie name and value
You can access other parts like this:
for cookie in s.cookies:
print(cookie.name)
print(cookie.value)
print(cookie.expires)
print(cookie.path)

Stormpath failed to find resource

In order to authenticate in Flask with Stormpath, the user sends an access token in the request's header.
headers = {
'Authorization': 'Bearer <token_id>'
}
I validate it with:
authenticator = ApiRequestAuthenticator(application)
uri = 'bla_dont_care.com'
result = authenticator.authenticate(headers=request.headers, http_method='GET', uri=uri, body={}, scopes=[])
is_valid = result.account is not None
The authenticator is not being initiated in every request but saved in memory.
My response time raised from 40ms to 450ms. I read that the Stormpath SDK caches the API calls specifically for this.
Trying to debug this, I did see that the cache get 2 misses per 1 request here. The key it failes to find is https://api.stormpath.com/v1/applications/VJiXDWTWnYdNgrFe9BoI3/accounts/6TGIMWF47DO0XUK96M07XEMG1. It fails to find the resource here.
What account is it looking for? 6TGIMWF47DO0XUK96M07XEMG1 is not the account sent. How can I debug this?

Push notifications on a Python server with App engnine

I'm implementing a push notifications mechanism for my android app.
Right now I'm trying to run a small test just to see that I manage to send push notifications from a python server via http to GCM, and to recieve it successfuly in an android client.
Client code is exactly as in google's tutorial:
http://developer.android.com/google/gcm/client.html
There's a main activity called DemoActivity which is responsible for registering or retrieving an already existed a registration id, and two classes GcmIntentService and GcmBroadcastReceiver responsible for handling the messags from the GCM server to the app.
Of course, I've set the SENDER_ID correctly, and I do manage to get a registration ID for my client app.
Now for the server:
In the server I always recieve the following error:
HTTP Error 401: Unauthorized
This is my server code:
url = "https://android.googleapis.com/gcm/send"
headers = { 'Content-Type' : 'application/json', 'Authorization': 'key=' + SERVER_API_KEY }
values = { 'registration_ids': [CLIENT_REGID]
, 'data': {'test': 'test} }
data = urllib.urlencode(values)
req = urllib2.Request(url, json.loads(values), headers)
response = urllib2.urlopen(req)
the_page = response.read()
self.response.out.write(the_page)
For security reasons I omitted the server api key and the client registration id (they're hard-coded), but I double and triple checked them and they're correct. Also, I made sure the server API key was formed correctly (Credentials -> Create new key -> Server key) and also made sure "Any IP allowed".
All solutions I found on the internet was related to a mistake in the server api or something like that but I already checked that.
Thanks for helpers!
edit:
added 'key=' in the header, but now I recieve Bad request error (HTTP code 400)
another edit:
changes the values object abit and sent it using json.loads, but not I have this error in the client (means it finally recieves a notification from server!!):
Unable to instantiate receiver GcmBroadcastReceiver
Any ideas? I copied the sample project from google as is, so I don't have any idea what's wrong here.
The auth header should be (note the key= part):
Authorization:key=<your_key_here>
so you should set headers like this:
headers = { 'Content-Type' : 'application/json', 'Authorization': 'key='+SERVER_API_KEY }
I believe the issue is that you're sending urlencoded payload, when telling the server to expect json. Try changing data to a json object, as a string: data ="{ 'registration_ids':...}"

Categories