How to setup OpenID to work with load balancer? - flask

The stack I'm currently using is:
Keycloak
flask
flask-oidc
nginx as load balancer
The set up I'm having is I have two instances of a service running (instance1, instance2). The issue I'm facing is:
A user uses web browser to authenticate by going to https://mycompany.com/auth/login
instance1 handles this request and redirects the user to Keycloak for authentication
Keycloak redirects the user back to the app using redirect url (https://mycompany.com/auth/auth_callback)
This time the load balancer routes the request to the redirect url to instance2. Here instance2 errors out with a response from Keycloak saying "{'error': 'invalid_grant', 'description': 'Incorrect redirect uri'}", which is very confusing because the redirect uri is correct.
I am not entirely sure why this set up is not working. But after reading through how openID works, I kind of suspect it has to do with the state parameter (https://auth0.com/docs/protocols/oauth2/oauth-state). Again, I am not entirely sure. But it has to be something that's only local to instance1, which instance2 doesn't have.
How do people tackle this issue ? Is this set up even possible?

From the documentation
Note that you should probably provide the library with a place to store the credentials it has retrieved for the user. These need to be stored in a place where the user themselves or an attacker can not get to them. To provide this, give an object that has setitem and getitem dict APIs implemented as second argument to the init() call. Without this, the library will only work on a single thread, and only retain sessions until the server is restarted.
It is referring to credentials_store option in OpenIDConnect instantiation. To support persisted login via multiple application instances, you will need a persisted shared datastore for this use case. You could use a share redis or dynamodb instance.
Implementation of this credentials_store is fairly simple, you can try something like,
class RedisOpenIdCredStore:
def __init__(self):
# Handle Redis instance initialisation here
pass
def __setitem__(self, key, value):
# Set item to redis
pass
def __getitem__(self, key):
# Fetch and return item from redis if present
pass
credential_store = RedisOpenIdCredStore()
oid_connect = OpenIDConnect(app, credential_store=credential_store, ...)

Related

How often is load_user called in Flask Login?

I know the function load_user is used to load a user entry from a database of the developer's choosing.
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id) # DB agnostic
For me, it seems that it tries to load the user on every http request. This would make sense since HTTP is stateless. However, I thought Flask-Login allows for a session to hold onto the user until logout or login and the session cookie changes.
So basically, how often is load_user called, and what triggers it? Specifically, from the browser's perspective?
Some on my team are observing that the user is staying signed in for too long. I suspect either the session/token is not being cleared or there are multiple signed instances of the same user account as the db attempts to called User.get, or we are not protecting routes correctly with login_required.

Is it possible to send data from a client to a server without the API being public?

I'm currently trying to make an account signup page for a small project I'm working on and I don't know how to send data back to the server (I'm using the Flask framework) without also allowing everyone to send data. Let's say that I've set up an API endpoint on /createAccount. I can then send POST requests to that endpoint: {"username": "test", "password": "test"}. The web server will then handle that request by inserting that data into a database and responding with 201. The problem is, anybody would be able to send these requests, and I only want users to be able to register through the login page, and not by making an API call. Is there any way of doing this?
Edit: I've given this problem a bit more thought and I think that the only API that is difficult to secure is the signup API. When a user has created an account, I can just assign them an API key, which they will send to the API every time they want to make a request, which means that an account is required to make API calls. If a certain key is making too many requests, they can be rate limited or temporarily banned from making further requests. The problem with the signup API however, is that there is no information by witch a request sender could be identified. I could use the IP address, but that can be changed and wouldn't really help if multiple IPs are spamming the API at the same time. Is there a way I can identify non-registered users?
Short answer: no.
You have to check data to make sure the account being created is something legit and not trash data to fill your database or any other malicious intents.
This is the reason you usually have to confirm an account clicking on a confirmation link sent to your mail: this way the app is sure that your account is legit.
You could also check info on the front end, but that is never as secure as back end checking, because of your concern in the question: in the end, anyone who gets to know your endpoints could potentially send direct requests to your server with whatever data they wanted.
Assuming you have a trusted source of registrations, an if that source can make an ssh connection to the server where your Flask app is running, an alternative to trying to lock down a registration API is to provide a command line script to do the registration.
The trusted source does something like
ssh someuser#youripaddress /path/to/register.py "username" "password" "other info"
If you use a Flask custom command you can share model definitions db configuration.

Django memcached session get deleted

Two Django applications(DRF) are running on two separate ports 8001 and 8000 with same host localhost. I am using memcached to store the sessions. Both the application share the sessions from the memcache. when i try to access pages using second application after logining in using first I am getting error :
"The request's session was deleted before the request completed. The user may have logged out in a concurrent request, for example."
I want to build a distributed apps where one app can be used for auth running on a separate docker, so other apps can share the session using memcached
Sessionmiddleware is able to populate the session object in the Request object, but after execution of below line of code in AuthenticationMiddleware
request.user = SimpleLazyObject(lambda: get_user(request))
The session._session dictionary elements got deleted.
In the above case you are running application on two different ports with the same database. If user1 logged in with port 8000 then a session will be created for him. And again user1 logged in with port 8001 then the existing session will be replaced/destroyed by the new session. Because browser treats localhost:8000 and localhost:8001 as two different domains.
To avoid it you can use nginx as a reverse proxy server with same ip 127.0.0.1 or domain localhost. Now, route the api requests to port 8001 and web requests to port 8000.
In above case the domain localhost remains same so django will not replace existing session. So, It will work.
Reference: https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
To solve this issue we need to add the same secret key in the settings in both Django applications.
Django adds the user in the request body in authmiddleware. The user can be anonymous(unauthenticated) or authenticated user. The session is verified before adding the user in the request. This verification is done as follows.
session_hash_verified = session_hash and constant_time_compare(
session_hash,
user.get_session_auth_hash()
)
This user.get_session_auth_hash() function uses salted_hmac(key_salt, self.password).hexdigest() function for verification. salted_hmac function uses settings.SECRET_KEY for calculating the hash. If both applications secret keys are different then session hasj will not be varified.

Django: Protect view with HTTP Basic Auth and separate user model?

I have a little shop-like app powered by Django (1.7), that needs to process credit cards using external gateway.
I have my own User table for customers. They login via form and auth data is kept within sessions.
Now, payment gateway needs to access my app's predefined urls via SSL & Basic Auth.
How I can protect this (and only this) url with HTTP Basic Auth? Also I don't want to add the gateway user in my User table. Also, I don't want my custommers access that secret URL with their credentials (of course, the URL is not displayed to users, but you should never trust the users, right?)
I could to that with HTTP-server (nginx/apache) settings, but then, I can't test this behavior via django test framework.
Is there a solution for this?
You can manually check the request.META['REMOTE_USER'] and request.META['HTTP_AUTHORIZATION'] against your custom credentials, wherever those credentials reside. If the request isn't properly authorized, you need to send back a HTTP 401 Unauthorized response:
def some_view(request, **kwargs):
if not remote_user_is_authenticated(...):
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
If you're using Apache with mod_wsgi, the REMOTE_USER Meta header won't be passed on to your application by default; you'll need to put WSGIPassAuthorization On in your Apache config file. Nginx probably has a similar setting (or not, I really don't know :P).

Django Login/Session Not Sticking

After my site has been up and running for a while in production, I suddenly have a problem with my users loging into it.
I have protected certain views/pages with the login_required decorator and I am also using the django admin.
When an anonymous user hits any of these pages, he is redirected to the login page.
When this anonymous user adds its credentials, the POST request is successful and he is redirected to the inital page. At the same time, the user gets a new sessionid (as expected)
However, now the results get very unreliable. When pressing reload or when navigating to other pages (that require a login), either of the 2 outcomes might happen:
a) The user is identified and the page is displayed correctly
b) The user is redirect to the login page.
I have checked the content of the session via the shell, and nothing is changing there.
The production site is served via a load balancer and 8 application servers. Even stranger: if I test the same code (with the same settings) on a test server, that is not load balanced and basically has not traffic, everything is working fine.
I am running Django 1.6 on Ubuntu with Apache and mod_wsgi in daemon mode behind SSL and I am using the Session database backend. I am using django-allauth.account for account management/login. My Session settings are like this:
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 60*60*24
SESSION_COOKIE_SECURE = True
UPDATE
To get some more debug information, I have created this Middleware:
from django.conf import settings
class SessionDebugMiddleware(object):
def process_response(self, request, response):
session = request.session
user = getattr(request, 'user', None)
if user:
user=user.id
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
response['X-Meta-Requ'] = '{0},{1},{2},{3}'.format(session_key, session.get('_auth_user_id'), session.get('_auth_user_backend','---'), user)
return response
If I hit the refresh button 10 times,
8 times I will get this header:
igv0xshezhdxh50kks9x00r0l67mx0sk,None,---,None
2 times I will get this header: X-Meta-Requ:igv0xshezhdxh50kks9x00r0l67mx0sk,330619,django.contrib.auth.backends.ModelBackend,330619
It seems be be random and does not follow any logic.
So I have the following questions/ideas?
Can this be related to loadbalancing in anyway? My understanding was that Django does not need sticky sessions, when the DB session backend is used.
Can this be related to a threading issue?
Can this be related to high load?
Can this be related to a decoding issue: https://github.com/django/django/blob/master/django/contrib/sessions/backends/base.py#L83. But why should that decoding issue not be consistent. And I have not found any log entries that refer to "Session data corrupted".
Any other hints are welcome.
Just in case someone is having this issue, it can also be caused by SECRET_KEY definitions not being consistent.
I had mine generated in the settings file (a bad decision).
It was fine in development mode, but once the application was served by WSGI, multiple instances were created, each with different keys.
This causes the mixup in the hash, and the corrupt session to be reported.
I think you have problem because of highload or error in your code (maybe you just rebuild session in your application anywhere and forget about it :)
Please try to read info from database with your session key to be sure session data exists.
If data exists but not loaded to session - this mean you have troubles with session processing. If data doesn't exists, but it present in past because you got 2 times right session data, this mean you have problems with session storage (maybe because of highload your database lose sessions data). Do you have database replication?
Also which session backend you use? Just a DB or Cached_Db?