Python: уроки, справочник и задачи

Авторизация в Flask с помощью Flask-Login

Раздел: Flask и Tkinter · Обновлено 31.10.2026

Авторизация в Flask с использованием Flask-Login строится вокруг сессии: расширение запоминает ID вошедшего пользователя и по нему загружает объект при каждом запросе. В статье собрано рабочее приложение с входом, выходом, защищенными страницами и проверкой ролей, а также разобраны настройки и типичные ошибки.

Авторизация в Flask с использованием Flask Login
Фото: Pittigrilli / CC BY-SA 4.0, Wikimedia Commons
Содержание статьи
  1. Как работает Flask-Login
  2. Установка и настройка LoginManager
  3. Модель пользователя и user_loader
  4. Вход и выход пользователя
  5. Защита страниц и проверка ролей
  6. Полезные настройки
  7. Частые вопросы
    1. Почему после входа current_user остается анонимным?
    2. Как получить 401 вместо редиректа для API?
    3. Подходит ли Flask-Login для входа по токену?
    4. Как заблокировать пользователя?

Как работает Flask-Login

Flask-Login не хранит пользователей и не проверяет пароли. Эти задачи остаются за вашим кодом и базой данных. Расширение отвечает за другое:

Аутентификация отвечает на вопрос "кто вы", авторизация на вопрос "что вам можно". Flask-Login решает первую задачу и дает удобную основу для второй. Если вы только начинаете работать с фреймворком, сначала прочитайте статью про основы Flask.

Установка и настройка LoginManager

Пакет ставится через pip. Вместе с Flask устанавливается Werkzeug, в котором есть функции для хеширования паролей.

pip install flask flask-login

Сессия во Flask подписывается секретным ключом, без него вход работать не будет. Ключ должен быть длинной случайной строкой и не попадать в репозиторий, поэтому его читают из переменной окружения.

import os
from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY', 'dev-only-key')

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'  # имя функции страницы входа
login_manager.login_message = 'Войдите, чтобы открыть эту страницу'

Атрибут login_view указывает, куда отправлять неавторизованного посетителя. Если его не задать, защищенная страница вернет ошибку 401. Сгенерировать надежный ключ можно командой python -c "import secrets; print(secrets.token_hex(32))".

Модель пользователя и user_loader

Класс пользователя должен иметь свойства is_authenticated, is_active, is_anonymous и метод get_id(). Писать их вручную не нужно: примесь UserMixin добавляет реализации по умолчанию, достаточно атрибута id.

from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class User(UserMixin):
    def __init__(self, id, username, password_hash, is_admin=False):
        self.id = id
        self.username = username
        self.password_hash = password_hash
        self.is_admin = is_admin

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

# Учебное хранилище, в проекте здесь будет база данных
USERS = {
    '1': User('1', 'anna', generate_password_hash('qwerty123'), is_admin=True),
    '2': User('2', 'boris', generate_password_hash('boris2024')),
}

@login_manager.user_loader
def load_user(user_id):
    return USERS.get(user_id)  # None, если пользователя нет

В user_loader приходит строка, то есть то, что вернул get_id(). Если в базе ID целочисленный, преобразуйте его: db.session.get(User, int(user_id)). Функция должна вернуть None для несуществующего пользователя, а не выбрасывать исключение. Как подключить настоящую таблицу пользователей, рассказано в статье про базу данных во Flask.

Вход и выход пользователя

Представление входа проверяет логин и хеш пароля и вызывает login_user(). Параметр remember=True ставит долговременную cookie, по умолчанию она живет 365 дней. Форма здесь простая, в реальном приложении ее удобнее строить через Flask-WTF с защитой от CSRF, это описано в материале про формы Flask.

from urllib.parse import urlsplit
from flask import request, redirect, url_for, flash, render_template_string
from flask_login import login_user, logout_user, login_required, current_user

LOGIN_FORM = '''
<form method="post">
  <input name="username" placeholder="Логин">
  <input name="password" type="password" placeholder="Пароль">
  <label><input type="checkbox" name="remember"> Запомнить</label>
  <button>Войти</button>
</form>
'''

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('profile'))
    if request.method == 'POST':
        name = request.form.get('username', '')
        user = next((u for u in USERS.values() if u.username == name), None)
        if user and user.check_password(request.form.get('password', '')):
            login_user(user, remember='remember' in request.form)
            next_page = request.args.get('next')
            if not next_page or urlsplit(next_page).netloc:
                next_page = url_for('profile')
            return redirect(next_page)
        flash('Неверный логин или пароль')
    return render_template_string(LOGIN_FORM)

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

Обратите внимание на параметр next. Flask-Login добавляет его к адресу страницы входа, чтобы после авторизации вернуть пользователя туда, куда он шел. Значение приходит из URL, поэтому перед редиректом проверяем, что адрес относительный. Иначе злоумышленник может прислать ссылку, которая после входа уведет на чужой сайт.

Пароли нельзя хранить открытым текстом. Функция generate_password_hash() добавляет соль и возвращает строку с алгоритмом и хешем, а check_password_hash() сравнивает введенный пароль с этой строкой.

Защита страниц и проверка ролей

Декоратор @login_required ставится под @app.route. Гость получит редирект на страницу входа, а вошедший пользователь увидит страницу. Внутри представления и в шаблонах Jinja2 доступен current_user.

from functools import wraps
from flask import abort

@app.route('/profile')
@login_required
def profile():
    return f'Привет, {current_user.username}!'

def admin_required(view):
    @wraps(view)
    @login_required
    def wrapper(*args, **kwargs):
        if not current_user.is_admin:
            abort(403)
        return view(*args, **kwargs)
    return wrapper

@app.route('/admin')
@admin_required
def admin_panel():
    return 'Панель администратора'

Собственный декоратор admin_required объединяет проверку входа и роли, поэтому его не нужно дублировать в каждом представлении. Без @wraps все обернутые функции получили бы одно имя wrapper, и Flask выдал бы ошибку о повторяющемся endpoint.

В шаблоне удобно показывать разные ссылки гостю и пользователю:

{% if current_user.is_authenticated %}
  <a href="{{ url_for('logout') }}">Выйти ({{ current_user.username }})</a>
{% else %}
  <a href="{{ url_for('login') }}">Войти</a>
{% endif %}

Так шаблон меняет меню в зависимости от статуса посетителя. Подробнее о наследовании шаблонов написано в статье про создание шаблона Flask.

Полезные настройки

Основные параметры, которые меняют поведение расширения:

НастройкаГде задаетсяНазначение
login_viewLoginManagerСтраница, куда отправляют гостей
login_messageLoginManagerТекст flash-сообщения при редиректе
session_protectionLoginManagerРежим "basic" или "strong" для защиты от кражи сессии
REMEMBER_COOKIE_DURATIONapp.configСрок жизни cookie "Запомнить меня"
REMEMBER_COOKIE_SECUREapp.configПередавать cookie только по HTTPS

Если нужно, чтобы пользователь заново вводил пароль перед опасными действиями, вроде смены почты, используйте декоратор @fresh_login_required. Сессия, восстановленная из cookie "Запомнить меня", не считается свежей. Все параметры перечислены в документации Flask-Login.

Частые вопросы

Ответы на вопросы, которые возникают при подключении Flask-Login.

Почему после входа current_user остается анонимным?

Чаще всего не зарегистрирован user_loader или он возвращает None из-за несовпадения типов: в сессии лежит строка '1', а в словаре или базе ищется число 1. Проверьте также, что задан secret_key.

Как получить 401 вместо редиректа для API?

Не задавайте login_view или зарегистрируйте свой обработчик через @login_manager.unauthorized_handler и верните из него JSON с кодом 401.

Подходит ли Flask-Login для входа по токену?

Да, для этого есть колбэк @login_manager.request_loader. Он получает объект запроса и может найти пользователя по заголовку Authorization или API-ключу.

Как заблокировать пользователя?

Переопределите свойство is_active, чтобы оно возвращало False для заблокированных. Тогда login_user() откажет во входе и вернет False.