I have a web application that uses GitHub's OAuth API in order to allow the app's users to interact with GitHub. However, I'm seeing some very odd behaviour with regards to the session cookie.
As a bit of background, I am using peewee to interface with Heroku's Postgres server, and have a User model like so:
class User(peewee.Model):
login = peewee.TextField(unique=False)
token = peewee.TextField()
I am using the web application flow described in the GitHub OAuth documentation, and am successfully getting called back with an access token, which I store in the database, and also in the session [1]:
#app.route('/callback')
def finishlogin():
# I've verified that `token` and `login` are both valid at this point
user = User.create(login=login, token=token)
session['token'] = token
return redirect(url_for('home'))
My route for home is as follows:
#app.route('/')
def home():
if 'token' in session:
user = User.get(token=session.get('token'))
return 'Your login is {}'.format(user.login)
else:
# ...
So far, so good, and this works correctly. However, I am experiencing instances of users logging in, refreshing the page and finding that they are suddenly logged in as someone else. Logging the requests to the app shows that on the second request the session cookie itself has sent the wrong value (i.e. session.get('token') in home() returns a valid, but incorrect value. Clearly the user's browser can't know any other session value, so it seems that there is some "leakage" in setting the session between different clients and requests.
I'm not sure what the problem might be. My database is stored on the Flask g object as described in the peewee docs and has before_request and teardown_request hooks set up to open and close the database connection, and from all the documentation and example code I have read (and I've read a lot!), I seem to be using the session object correctly. I have set up a working secret_key for the session store.
I'm wondering if this could be something going on with Heroku and their routing mesh? But then, how would one user suddenly send another user's session?
Any hints or advice would be appreciated—I've been staring at this for a long time and am at my wits' end.
[1] I'm aware that storing the token directly is a bad design choice. The application is non-public and this will be fixed, but for now I want to describe the problem as it exists, even though it's not ideal.
Answering my own question for future reference.
It seems that this was being caused by Flask's default session cookie behaviour, which is to send a Set-Cookie header with every single request, even for static assets. Our local Squid proxy was therefore gladly caching those requests and re-issuing Set-Cookie headers for every user.
Setting Cache-Control headers for the whole app seems to fix the issue.
Related
I am writing a Django app that uses a 3rd-Party Authentication Service implemented with FusionAuth.
I can successfully register and login (using the authorization code flow).
However, logging out is not working. I can logout from the local Django app, but when I try to login after that FusionAuth recognizes that my access_token is still valid and just goes directly to the redirect page (the page you usually goto after successfully logging into FusionAuth).
Clearly I don't understand something about FusionAuth. I have tried both the python client and the restful API and I don't really understand what it does. According to the documentation it "...is intended to be used to remove the refresh token and access token cookies if they exist on the client and revoke the refresh token". Why then is the access_token still valid?
There seem like a couple of issues here.
First, you need to redirect the browser to https://your.fusionauth.instance/oauth2/logout and that will delete your FusionAuth SSO session. That will stop the behavior of FusionAuth redirecting you. This is because you have an SSO session cookie, and going to that URL will delete it.
Second, if you want to revoke the access token, that takes a bit more work. It is stateless. It's not the same as a session, and is distinct from the SSO session mentioned above. The access token is something you present to other APIs, not to FusionAuth. Here's some more info: https://fusionauth.io/learn/expert-advice/tokens/revoking-jwts
Hope this helps.
I am a bit confused as to how these callbacks work.
I have seen some REST APIs ask for callback URLs mainly for authentication(Oauth) and if I understand correctly, the API calls an endpoint implemented at our backend once they're done. But if this is a website and the API calls a backend how will it call the particular session that an user triggered that API call from?
Do we generate some unique ID and use that to identify each session?
Also is it possible to implement something like this for uses other than authentication?
Excuse me if this seems trivial but I've been trying to figure it out for hours now.
Thanks in advance! :)
P.S: I'm trying it out in python using FastAPI.
Yes, you usually include a key that identifies the "transaction". Either one that is pre-shared (for webhooks where an API makes callbacks at specific times when an event happens and the request isn't a response to something initiated by you), or one that you generate to identify the session itself after redirecting a user outside of your own application.
remote server makes a request to /webhooks?key=foo
.. or what usually happens, includes a signature as an HTTP header that signs the message based on an already exchanged key.
See for example Stripe's webhook documentation for an example of how the request is signed.
However, in the case where the user is redirected back to your application (which is the flow for many authentication schemes), the redirect back is given to the user's client - and their session will still be active. In that case the redirect will include a session identifier from the authentication system, which you can then use in your backend to verify that the user actually was authenticated and with what autorization.
/foo -> redirect -> https://remote/auth
(enters authentication information)
https://remote/auth -> redirect -> /authenticated?secret_key=foobar
I have found a number of articles online explaining that the Flask session is secured and not encrypted.
This means that users can Base64 decode the cookie and read the values inside.
Is it an issue if my application uses the User ID value from this session cookie to authenticate the user after he successfully logs in?
If I understand correctly, the user will not be able to change his cookie value to an ID of another user, as it signed by the application private key on the server side. Is that correct?
And if so, are there any issues with using the secured contents of the Flask cookie-based session to allow users access to protected views of the application?
To summarise answers given in the comments:
Cookie can be tampered with but if Flask session security is enabled, such tampered session will be thrown away, forcing the client to re-login
The session data Base64-encoded within the cookie can be quite easily viewed. Therefore anything that your clients are not supposed to see should not be included there
I am building an api for interfacing with mobile applications that requires users to login. Just using session auth gives me a csrf error. I was able to fix it by providing credentials in basic auth, but I don't think this is ideal. This will be my first time developing for mobile devices. I was planning on using cordova, and I don't know if there is a way to store credentials on the user's device, or if the session data will be automatically stored on the devices. If the session data will be stored on the mobile devices automatically, that would be the ideal route to go. Has anyone else had similar issues with DRF session auth, or advice on if this is the best route to go or not?
Update:
I was able to get the csrf error to go away by using this from another post:
from rest_framework.authentication import SessionAuthentication
class NoCsrfSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return
But this seemed to cause an error with the request.data parameter. It kept returning an empty query dict.
If you want to use session auth, but are confident that you can give up CSRF protection for a given view, the
#csrf_exempt
decorator will do just that. (If you are using class-based views, check out this: https://stackoverflow.com/a/14379073/1375015)
Since you are using session based authentication, your mobile applications must be storing some kind of session cookie. Therefore, you should also be able to store the csrftoken cookie and send it along with your http requests. However, even then I had some troubles with the django CSRF protection framework in the past.
Maybe switching to token authentication is an option?
The app I'm deving uses a lot of ajax calls. Unfortunately I hit a snag when researching on how to restrict access to the api. For example:
i have table that does an ajax call to http://site/api/tasks/bob
i need to make sure that only bob, logged in, can read that table
(otherwise somebody who knows the pattern might request to see bob's
tasks by simply entering the url in the browser).
on a different page,the same table needs to be able to call http://site/api/tasks/all and show the tasks of all users (only an admin should be able to do that)
Thank you for your time reading this and maybe answering it.
The thousand-foot view is you need to authenticate the user either with:
A) HTTP-Auth (either basic or digest) on each request.
B) Server-side sessions. (The user authenticates and receives a session key - their user information is stored in the session backend on the server, attached to that key Once they have a session they can make requests passing their session key back to you (either in the URL or in a cookie) and the information they have access to is returned to them.)
Flask has a pair of useful extensions that deal with a large part of this sort of thing - check out Flask-Login and Flask-Principal to see examples of how authorization can be added to a Flask application.