Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to limit one session from any browser for a username in flask?

I am using a gunicorn server in which I am trying to figure out a way to limit only one session per username i.e. if user A is logged in to the app from Chrome he should not be able to login through Firefox unless he logs out of chrome, or shouldn`t be able to open another TAB in chrome itself.

How can I generate a unique id for the browser and store it in a DB so that until the user logs out or session expires, the user can`t login through any other browser.

like image 527
RamPrasadBismil Avatar asked Jul 08 '15 00:07

RamPrasadBismil


People also ask

How are sessions managed in Flask?

The data that is required to be saved in the Session is stored in a temporary directory on the server. The data in the Session is stored on the top of cookies and signed by the server cryptographically. Each client will have their own session where their own data will be stored in their session.

Does Flask support client side sessions?

Flask uses the client-side approach. Pros: Validating and creating sessions is fast (no data storage) Easy to scale (no need to replicate session data across web servers)

Does Flask login use session?

Once the user has passed the password check, you will know that they have the correct credentials and you can log them in using Flask-Login. By calling login_user , Flask-Login will create a session for that user that will persist as the user stays logged in, which will allow the user to view protected pages.


2 Answers

A possible method of limiting sessions to a single tab involves creating a random token on page load and embedding this token into the page. This most recently generated token gets stored in the user's session as well. This will be similar to how various frameworks add validation tokens to prevent CSFR attacks.

Brief example:

  • User loads page in tab 1 in Firefox. Token1 is generated, embedded and stored in session
  • User loads page in tab 2 in Firefox. Token2 is generated, embedded and stored in session. This overwrites previous value.
  • User loads page in tab 1 in Chrome. Token3 is generated, embedded and stored in session. this overwrites previous value.

At this point, the user has the page open in 3 tabs. The user's session, though, only has Token3 stored. This method prevents the user from being locked out (different IP addresses, different user agent strings, incogneto mode, etc) because each new session simply generates a new token. The newest load becomes the active window, immediately invalidating all previous sessions.

Next, any time the page interacts with the server (clicks a link, submits data, etc.), the token embedded in the page is sent as well. The server validates that the passed token matches the token in the session. If they match, the action succeeds. If they do not match, the server returns a failure message.


You can generate random numbers in multiple ways, but you probably want something secure. We'll use the example from another question:

import string
import random
...
N = 20  # Length of the string we want, adjust as appropriate
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))

This uses random.SystemRandom, which is more secure than simply using random.choice


On page load, you need to check if the existing token is valid, generate the random token and store this in the user's session. Since we want this everywhere, let's make a decorator first, to reduce duplicate code later. The decorator checks if the session is valid and if not you get to select what to do (insert your own logic). It also sets a session token. This is needed (or you need logic to exclude your main page) otherwise you'll hit an infinite loop where the user attempts to load the main page, doesn't have a token, fails and the process repeats. I have the token regenerating on each page load via the else clause. If you do not implement the if section, this decorator is pointless as both paths perform the same action and simply reset the token on page load. The logic in the if is what will prevent the user from having multiple sessions.

from flask import session
from functools import wraps

def random_string(length):
    return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length))

def validate_token(f):
    @wraps(f)
    def wrapper(existing_token, *args, **kwargs):
        if session['token'] != existing_token:
            # Logic on failure. Do you present a 404, do you bounce them back to your main page, do you do something else?
            # It is IMPORTANT that you determine and implement this logic
            # otherwise the decorator simply changes the token (and behaves the same way as the else block).
            session['token'] = random_string(20)
        else:
            session['token'] = random_string(20)
        return f(*args, **kwargs)
    return wrapper

Now in our routes, we can apply this decorator to each, so that the user session gets updated on each page load:

from flask import render_template

@app.route('/path')
@validate_token
def path(token=None):
    return render_template('path.html', token=session['token'])

In your template, you want to utilize this token value anywhere you need to prevent the session from continuing. For example, put it on links, in forms (though Flask has a method of CSRF protection already), etc. The server itself can check if the passed token is valid. The template could look as simple as this:

<a href="{{ url_for('path', token=token) }}">Click here to continue</a> 
like image 132
Andy Avatar answered Oct 18 '22 07:10

Andy


I don't know how to implement it exactly, but it seems that you need websockets (See here)

This way, on every page load, you could have an Ajax request to the server, which would use the websocket functionality to query the previously opened browser+tab, if any. If it has an answer, it means that there is another browser+tab open. The server should then should return this information.

Your script which called the server through Ajax, depending on the return, should then decide to redirect to an error page, or to continue loading it.

I never used Websocket, but I'm pretty confident this would work (as is is well implemented in most browsers now).

like image 2
BriceP Avatar answered Oct 18 '22 07:10

BriceP