Flask user session management.

Overview

Flask-Login

Tests coverage Software License

Flask-Login provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering your users' sessions over extended periods of time.

Flask-Login is not bound to any particular database system or permissions model. The only requirement is that your user objects implement a few methods, and that you provide a callback to the extension capable of loading users from their ID.

Installation

Install the extension with pip:

$ pip install flask-login

Usage

Once installed, the Flask-Login is easy to use. Let's walk through setting up a basic application. Also please note that this is a very basic guide: we will be taking shortcuts here that you should never take in a real application.

To begin we'll set up a Flask app:

import flask

app = flask.Flask(__name__)
app.secret_key = 'super secret string'  # Change this!

Flask-Login works via a login manager. To kick things off, we'll set up the login manager by instantiating it and telling it about our Flask app:

import flask_login

login_manager = flask_login.LoginManager()

login_manager.init_app(app)

To keep things simple we're going to use a dictionary to represent a database of users. In a real application, this would be an actual persistence layer. However it's important to point out this is a feature of Flask-Login: it doesn't care how your data is stored so long as you tell it how to retrieve it!

# Our mock database.
users = {'[email protected]': {'password': 'secret'}}

We also need to tell Flask-Login how to load a user from a Flask request and from its session. To do this we need to define our user object, a user_loader callback, and a request_loader callback.

class User(flask_login.UserMixin):
    pass


@login_manager.user_loader
def user_loader(email):
    if email not in users:
        return

    user = User()
    user.id = email
    return user


@login_manager.request_loader
def request_loader(request):
    email = request.form.get('email')
    if email not in users:
        return

    user = User()
    user.id = email

    # DO NOT ever store passwords in plaintext and always compare password
    # hashes using constant-time comparison!
    user.is_authenticated = request.form['password'] == users[email]['password']

    return user

Now we're ready to define our views. We can start with a login view, which will populate the session with authentication bits. After that we can define a view that requires authentication.

@app.route('/login', methods=['GET', 'POST'])
def login():
    if flask.request.method == 'GET':
        return '''
               <form action='login' method='POST'>
                <input type='text' name='email' id='email' placeholder='email'/>
                <input type='password' name='password' id='password' placeholder='password'/>
                <input type='submit' name='submit'/>
               </form>
               '''

    email = flask.request.form['email']
    if flask.request.form['password'] == users[email]['password']:
        user = User()
        user.id = email
        flask_login.login_user(user)
        return flask.redirect(flask.url_for('protected'))

    return 'Bad login'


@app.route('/protected')
@flask_login.login_required
def protected():
    return 'Logged in as: ' + flask_login.current_user.id

Finally we can define a view to clear the session and log users out:

@app.route('/logout')
def logout():
    flask_login.logout_user()
    return 'Logged out'

We now have a basic working application that makes use of session-based authentication. To round things off, we should provide a callback for login failures:

@login_manager.unauthorized_handler
def unauthorized_handler():
    return 'Unauthorized'

Complete documentation for Flask-Login is available on ReadTheDocs.

Contributing

We welcome contributions! If you would like to hack on Flask-Login, please follow these steps:

  1. Fork this repository
  2. Make your changes
  3. Install the requirements in dev-requirements.txt
  4. Submit a pull request after running make check (ensure it does not error!)

Please give us adequate time to review your submission. Thanks!

Comments
  • current_user instance is of LocalProxy type

    current_user instance is of LocalProxy type

    Hi, when using current_user instance I was having a weird bug with MongoEngine, then I decided to debug and when checking:

    ipdb> type(current_user)
    <class 'werkzeug.local.LocalProxy'>
    

    That's not desired if I will use it directly. MongoEngine complains when using that instance during query/update, etc... Is there any way to unwrap the proxy? Right now I have to query to the DB to get the real object.

    Thanks

    opened by eka 35
  • Broken compatibility with Werkzeug 0.9 and Flask 0.10

    Broken compatibility with Werkzeug 0.9 and Flask 0.10

    The current release version does not work well with Flask 0.10 and the current code in Flask-Login in master does not work with Werkzeug 0.9.

    The two changes that break it:

    • sessions in flask can no longer contain binary data
    • headers are now unicode.
    Bug 
    opened by mitsuhiko 20
  • Drop Python 2.7 support

    Drop Python 2.7 support

    Fixes #508.

    This removes all support for and references to Python 2.7, including in the test matrix. Most changes were performed through running 2to3 but some work was performed manually, such as imports and removing the _compat module. Running make clean reports all is well.

    opened by le717 19
  • Backwards compatible imports (_create_identifier)

    Backwards compatible imports (_create_identifier)

    Can we keep the global import namespace compatible with 0.3.x version for utils introduced in 088ac3cf5e0597d59224fcfb0536bea031a9ae17 ?

    __init__.py

    
    from utils import _create_identifier, ...
    
    __all__ = ('_create_identifier', ...)
    

    Thanks!

    -- Make sure these boxes are checked before submitting your issue--thank you!

    • [x] Ensure you are using the latest PyPI release.
    • [x] Read the CHANGES document thoroughly.
    • [x] Provide a clear and simple set of steps to reproduce your issue for others.
    opened by jirikuncar 19
  • Fix handling of X-Forwarded-For header

    Fix handling of X-Forwarded-For header

    The code that calculates a user's identifier uses the remote address or the contents of the X-Forwarded-For header doesn't account for the fact that the X-Forwarded-For header can contain a comma separated list of remote addresses, which is the case for scenarios in which 2+ proxy servers are involved in the request forwarding. This caveat causes Flask-Login to unexpectedly log users out when the combination of proxy servers that routed a previous request change.

    See http://tools.ietf.org/html/rfc7239 for more information about X-Forwarded-For standard.

    opened by sholsapp 19
  • Avoid touching the session unless something changed

    Avoid touching the session unless something changed

    Some session backends will try to detect changes made to the session dictionary and only save if the session was modified. The pop() call will set modified = True in such a backend. By checking if the "remember" key exists before we pop it we avoid this behaviour.

    opened by simonklee 15
  • Bump jinja2 from 2.10.3 to 2.11.3 in /requirements

    Bump jinja2 from 2.10.3 to 2.11.3 in /requirements

    Bumps jinja2 from 2.10.3 to 2.11.3.

    Release notes

    Sourced from jinja2's releases.

    2.11.3

    This contains a fix for a speed issue with the urlize filter. urlize is likely to be called on untrusted user input. For certain inputs some of the regular expressions used to parse the text could take a very long time due to backtracking. As part of the fix, the email matching became slightly stricter. The various speedups apply to urlize in general, not just the specific input cases.

    2.11.2

    2.11.1

    This fixes an issue in async environment when indexing the result of an attribute lookup, like {{ data.items[1:] }}.

    2.11.0

    This is the last version to support Python 2.7 and 3.5. The next version will be Jinja 3.0 and will support Python 3.6 and newer.

    Changelog

    Sourced from jinja2's changelog.

    Version 2.11.3

    Released 2021-01-31

    • Improve the speed of the urlize filter by reducing regex backtracking. Email matching requires a word character at the start of the domain part, and only word characters in the TLD. :pr:1343

    Version 2.11.2

    Released 2020-04-13

    • Fix a bug that caused callable objects with __getattr__, like :class:~unittest.mock.Mock to be treated as a :func:contextfunction. :issue:1145
    • Update wordcount filter to trigger :class:Undefined methods by wrapping the input in :func:soft_str. :pr:1160
    • Fix a hang when displaying tracebacks on Python 32-bit. :issue:1162
    • Showing an undefined error for an object that raises AttributeError on access doesn't cause a recursion error. :issue:1177
    • Revert changes to :class:~loaders.PackageLoader from 2.10 which removed the dependency on setuptools and pkg_resources, and added limited support for namespace packages. The changes caused issues when using Pytest. Due to the difficulty in supporting Python 2 and :pep:451 simultaneously, the changes are reverted until 3.0. :pr:1182
    • Fix line numbers in error messages when newlines are stripped. :pr:1178
    • The special namespace() assignment object in templates works in async environments. :issue:1180
    • Fix whitespace being removed before tags in the middle of lines when lstrip_blocks is enabled. :issue:1138
    • :class:~nativetypes.NativeEnvironment doesn't evaluate intermediate strings during rendering. This prevents early evaluation which could change the value of an expression. :issue:1186

    Version 2.11.1

    Released 2020-01-30

    • Fix a bug that prevented looking up a key after an attribute ({{ data.items[1:] }}) in an async template. :issue:1141

    ... (truncated)

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the Security Alerts page.
    dependencies python 
    opened by dependabot[bot] 13
  • Exception: No user_loader has been installed for this LoginManager

    Exception: No user_loader has been installed for this LoginManager

    For apps with multiple User models, is there any way to alter the user_loader / load_user() to be able to distinguish between the models? The standard is

    @login_manager.user_loader
    def load_user(user_id):
        return User.get(user_id)
    

    I need to somehow discern which model the user_id passed belongs to and insert a condition to replace User.get() with the appropriate model.

    Bug 
    opened by NikolaJankovic 13
  • Flask WTF and session_protection = 'strong'

    Flask WTF and session_protection = 'strong'

    This https://github.com/maxcountryman/flask-login/commit/9c8f3ca7441fa0abb506093c7161b5aa4cad88df has created me a lot of problems using forms with csrf token enabled and session protection set to strong this is the issue that I was getting as well https://github.com/lepture/flask-wtf/issues/197 and unsetting the session_protection made it work.

    I can't figure out how I can make it work again using 'strong', could you please provide me some useful links or examples?

    opened by shipperizer 13
  • Support force-logout for active sessions

    Support force-logout for active sessions

    Right now you can only force-logout a remember-me token by using a custom token. It would be nice if there was a clean way to do this for active sessions, too.

    From a quick look at the code this could probably be done by making get_id return e.g. a tuple of userid, token and the user_loader handling this token accordingly (retrieving the user from the DB and checking if the tokens match). However, the documentation is quite clear about the user id having to be unicode and a second look at the code shows that the user id actually converted to a string at some point: data = encode_cookie(str(session['user_id'])) (actually, shouldn't this be unicode and not str?).

    Sure, I could use a custom string representation such as id:token but it feels dirty.

    Contributor Friendly Feature Request 
    opened by ThiefMaster 13
  • Re-enable localisation of flash messages after migration to Flask 0.10+

    Re-enable localisation of flash messages after migration to Flask 0.10+

    Flask 0.10+ makes it impossible to use login_manager.login_message = lazy_gettext('Please log in') (no more pickle), thus buggering localisation of flask-login's flash messages.

    Add a localize_callback attribute to LoginManager that, if present, will be called with login_message and needs_refresh_message and whose return value will be sent flash.

    opened by deanishe 13
  • Getting mixed results with flask-login, when using authenticated and anonymous clients

    Getting mixed results with flask-login, when using authenticated and anonymous clients

    Describe the bug I have a test that makes use of two fixtures. One provides an anonymous client, and another provides an authenticated client. Should I run the following code, the test fails and the user info returned is that of the authenticated user, although I am using the anonymous client.

    #Note: APIAwareFlaskLoginClient extends FlaskLoginClient to request a csrf prior to each post
    
    @pytest.fixture
    def app():
        app = create_app()
        app.test_client_class = APIAwareFlaskLoginClient
    
        with app.app_context():
            # reinit the database at each test
            db.drop_all()
            db.create_all()
    
            with Transaction(db) as session:
                session.add(Owner(email='a', name='b', password='c'))
    
        yield app
    
    @pytest.fixture
    def client(app):
        return app.test_client()
    
    @pytest.fixture
    def authenticated_client(app):
        client = app.test_client()
        client.post("/api/auth/login", json=dict(email='a', password='c', remember='1'))
        return client
    
    def test_anonymous_after_auth(client, authenticated_client):
        response = client.get('/api/auth/user')
        assert response.status_code == 401
    

    Should I remove the authenticated_client from the method signature, everything works as expected. Is this a bug, or I am doing something wrong?

    Environment (please complete the following information):

    • OS: Linux
    • Python 3.10.8
    • flask-login 0.6.2
    opened by flixman 0
  • regenerate & push latest ReadTheDocs documentation

    regenerate & push latest ReadTheDocs documentation

    Describe the bug The documentation on the ReadTheDocs website is out of date.

    There was a broken link that was fixed in 2019 (https://github.com/maxcountryman/flask-login/commit/dc6103ac), but the ReadTheDocs website still has the old link (https://flask-login.readthedocs.io/en/latest/#login-example).

    The latest docs should be regenerated/pushed to ReadTheDocs to resolve the bug.

    To Reproduce Steps to reproduce the behavior:

    1. Go to https://flask-login.readthedocs.io/en/latest/#login-example
    2. Click on 'this Flask Snippet' link
    3. See error

    Expected behavior Link should go to https://web.archive.org/web/20120517003641/http://flask.pocoo.org/snippets/62/

    Screenshots

    Desktop (please complete the following information):

    • OS: macOS
    • Browser: chrome
    • Version
    opened by pwillis-eiq 2
  • Potential regression in expected behavior with UserMixin.is_authenticated defaulting to is_active that was changed in 0.6.0

    Potential regression in expected behavior with UserMixin.is_authenticated defaulting to is_active that was changed in 0.6.0

    Version 0.6.0 has:

    • UserMixin.is_authenticated will return whatever is_active returns by default. This prevents inactive users from logging in. #486, #530

    Based on the comments in the above issues, the case for doing this was to prevent inactive users from logging in but an inactive user is not the same as a user who can't authenticate due to invalid credentials. These are much different things.

    In 0.5.0 you could do:

        @login_manager.user_loader
        def load_user(uid):
            user = user_model.query.get(uid)
    
            if not user.is_active():
                login_manager.login_message = 'This account has been disabled.'
                return None
    
            return user
    

    And then if an inactive user tried to login they would receive a custom flash message. This lets them know they have an account but they've been disabled.

    With 0.6.0 this code path doesn't seem to execute because the user gets blocked before they would be loaded so you end up with whatever message you would send to the user when their authentication failed. This is a regression in behavior.

    I thought a potential workaround in 0.6.0 would have been to add this to my user model (the default in 0.5.0):

        def is_authenticated(self):
            return True
    

    But this had no effect. I'm still not able to execute the user loader that would have presented a custom flash message.

    How can we get the old behavior back where end users of this library can handle inactive users after they've been authenticated?

    opened by nickjj 0
  • deprecate `__about__` module

    deprecate `__about__` module

    The only thing that might be relevant to runtime inspection is __version__, which is already exported. The rest is package metadata that should be in setup.cfg, which can be inspected with importlib.metadata if needed.

    opened by davidism 0
  • don't look at the `X-Forwarded-For` header

    don't look at the `X-Forwarded-For` header

    Flask-Login has no way to guarantee that this header is correct because it doesn't know how many proxies the app is or isn't deployed behind. Instead, it should always read request.remote_addr, and assume that the user has configured ProxyFix if necessary.

    opened by davidism 1
Releases(0.6.2)
  • 0.6.2(Jul 26, 2022)

  • 0.6.1(May 2, 2022)

  • 0.6.0(Mar 30, 2022)

    • Changes: https://github.com/maxcountryman/flask-login/blob/main/CHANGES.md#version-060

    This release sets new minimum versions of Python, Flask, and Werkzeug, and fixes compatibility with the latest versions of those.

    • Python >= 3.7
    • Flask >= 1.0.4, this will be bumped to reflect the latest supported release (2.1) in the future
    • Werkzeug >= 1.0.1, this will be bumped to reflect the latest supported release (2.1) in the future
    Source code(tar.gz)
    Source code(zip)
Owner
Max Countryman
Distributed systems, functional programming, cloud computing.
Max Countryman
Get inside your stronghold and make all your Django views default login_required

Stronghold Get inside your stronghold and make all your Django views default login_required Stronghold is a very small and easy to use django app that

Mike Grouchy 384 Nov 23, 2022
Connect-4-AI - AI that plays Connect-4 using the minimax algorithm

Connect-4-AI Brief overview I coded up the Connect-4 (or four-in-a-row) game in

Favour Okeke 1 Feb 15, 2022
Simplifying third-party authentication for web applications.

Velruse is a set of authentication routines that provide a unified way to have a website user authenticate to a variety of different identity provider

Ben Bangert 253 Nov 14, 2022
A simple model based API maker written in Python and based on Django and Django REST Framework

Fast DRF Fast DRF is a small library for making API faster with Django and Django REST Framework. It's easy and configurable. Full Documentation here

Mohammad Ashraful Islam 18 Oct 05, 2022
Spotify User Token Generator Template

Spotify User Token Generator Template Quick Start $ pip3 install -r requirements

Arda Soyer 1 Feb 01, 2022
Authentication Module for django rest auth

django-rest-knox Authentication Module for django rest auth Knox provides easy to use authentication for Django REST Framework The aim is to allow for

James McMahon 878 Jan 04, 2023
Graphical Password Authentication System.

Graphical Password Authentication System. This is used to increase the protection/security of a website. Our system is divided into further 4 layers of protection. Each layer is totally different and

Hassan Shahzad 12 Dec 16, 2022
Django-react-firebase-auth - A web app showcasing OAuth2.0 + OpenID Connect using Firebase, Django-Rest-Framework and React

Demo app to show Django Rest Framework working with Firebase for authentication

Teshank Raut 6 Oct 13, 2022
Auth for use with FastAPI

FastAPI Auth Pluggable auth for use with FastAPI Supports OAuth2 Password Flow Uses JWT access and refresh tokens 100% mypy and test coverage Supports

David Montague 95 Jan 02, 2023
A simple Boilerplate to Setup Authentication using Django-allauth 🚀

A simple Boilerplate to Setup Authentication using Django-allauth, with a custom template for login and registration using django-crispy-forms.

Yasser Tahiri 13 May 13, 2022
Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Intility 220 Jan 05, 2023
Out-of-the-box support register, sign in, email verification and password recovery workflows for websites based on Django and MongoDB

Using djmongoauth What is it? djmongoauth provides out-of-the-box support for basic user management and additional operations including user registrat

hao 3 Oct 21, 2021
python-social-auth and oauth2 support for django-rest-framework

Django REST Framework Social OAuth2 This module provides OAuth2 social authentication support for applications in Django REST Framework. The aim of th

1k Dec 22, 2022
Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Intility 220 Jan 05, 2023
Ready to use and customizable Authentications and Authorisation management for FastAPI ⚡

AuthenticationX 💫 Ready-to-use and customizable Authentications and Oauth2 management for FastAPI ⚡

Yasser Tahiri 408 Jan 05, 2023
RSA Cryptography Authentication Proof-of-Concept

RSA Cryptography Authentication Proof-of-Concept This project was a request by Structured Programming lectures in Computer Science college. It runs wi

Dennys Marcos 1 Jan 22, 2022
OpenConnect auth creditials collector.

OCSERV AUTH CREDS COLLECTOR V1.0 Зачем Изначально было написано чтобы мониторить какие данные вводятся в интерфейс ханипота в виде OpenConnect server.

0 Sep 23, 2022
Flask JWT Router is a Python library that adds authorised routes to a Flask app.

Read the docs: Flask-JWT-Router Flask JWT Router Flask JWT Router is a Python library that adds authorised routes to a Flask app. Both basic & Google'

Joe Gasewicz 52 Jan 03, 2023
Python One-Time Password Library

PyOTP - The Python One-Time Password Library PyOTP is a Python library for generating and verifying one-time passwords. It can be used to implement tw

PyAuth 2.2k Dec 26, 2022
Automatizando a criação de DAGs usando Jinja e YAML

Automatizando a criação de DAGs no Airflow usando Jinja e YAML Arquitetura do Repo: Pastas por contexto de negócio (ex: Marketing, Analytics, HR, etc)

Arthur Henrique Dell' Antonia 5 Oct 19, 2021