Социальная аутентификация в приложении на Flask

В этом руководстве мы рассмотрим, как добавить социальную аутентификацию с помощью GitHub и Google в приложение на Flask.

От редакции Pythonist: реализацию социальной аутентификации в Django мы рассматривали в статье «Аутентификация в Django-REST с помощью Auth.js».

Социальная аутентификация — это процесс аутентификации пользователя через сторонний сервис, а не через собственный. Например, кнопка «Войти с помощью Google», которую вы видите на многих сайтах, является лучшим примером социальной аутентификации. Google проверяет подлинность пользователя и предоставляет приложению токен для управления пользовательской сессией.

Использование социальной аутентификации имеет свои преимущества. Вам не нужно будет настраивать аутентификацию для веб-приложения, поскольку этим занимается сторонний провайдер OAuth. Кроме того, поскольку такие провайдеры, как Google, Facebook и GitHub, проводят тщательные проверки для предотвращения несанкционированного доступа к своим сервисам, использование социальной аутентификации вместо создания собственного механизма может повысить безопасность вашего приложения.

Наряду с Flask мы будем использовать Flask-Dance для включения социальной аутентификации, Flask-Login для регистрации пользователей и управления сессиями, а также Flask-SQLAlchemy для взаимодействия с базой данных для хранения пользовательских данных.

Содержание

Зачем использовать социальную аутентификацию?

Почему стоит использовать социальную аутентификацию, а не создавать свою собственную?

Плюсы

  • Не нужно создавать собственный рабочий процесс аутентификации.
  • Повышенная безопасность. Сторонние провайдеры аутентификации, такие как Google, Facebook и т. д., уделяют большое внимание безопасности. Использование таких сервисов может повысить безопасность вашего приложения.
  • Можно автоматически получать имя пользователя, электронную почту и другие данные от провайдера аутентификации. Это улучшает процесс регистрации, поскольку не нужно просить пользователя вводить все это вручную.

Минусы

  • Ваше приложение теперь зависит от другого приложения, которое вы не контролируете. Если стороннее приложение выйдет из строя, пользователи не смогут зарегистрироваться или войти в систему.
  • Люди склонны игнорировать разрешения, запрашиваемые провайдером аутентификации. Некоторые приложения могут получить доступ к данным, которые им не требуются.
  • Пользователи, не имеющие учетной записи хотя бы у одного из предложенных вами провайдеров, не смогут получить доступ к вашему приложению. Лучше всего реализовать как социальную аутентификацию, так и аутентификацию через логин и пароль, чтобы предоставить пользователю право выбора.

OAuth

Социальная аутентификация чаще всего реализуется с помощью OAuth — открытого стандартного протокола авторизации, в котором сторонний провайдер проверяет личность пользователя.

Наиболее распространенным потоком (или грантом) является код авторизации:

  1. Пользователь пытается войти в ваше приложение, используя свою учетную запись от стороннего провайдера авторизации.
  2. Пользователь перенаправляется к провайдеру аутентификации для проверки.
  3. После проверки они снова попадают в ваше приложение с кодом авторизации.
  4. Вы делаете запрос с кодом авторизации к провайдеру аутентификации для получения маркера доступа.
  5. После того как провайдер проверит код авторизации, он отправит обратно маркер доступа.
  6. После этого пользователь входит в систему и получает доступ к защищенным ресурсам.
  7. Токен доступа можно использовать для получения данных от провайдера.

Давайте рассмотрим быстрый пример этого потока на примере GitHub:

"""
Импортировать необходимые модули.
  - `os` для чтения переменной env
  - `requests` для запросов GET/POST 
  - `parse_qs` для парсинга ответа
"""
import os
import requests
from urllib.parse import parse_qs


"""
Определить переменные окружения GITHUB_ID и GITHUB_SECRET,
а также конечные точки.
"""
CLIENT_ID = os.getenv("GITHUB_ID")
CLIENT_SECRET = os.getenv("GITHUB_SECRET")
AUTHORIZATION_ENDPOINT = f"https://github.com/login/oauth/authorize?response_type=code&client_id={os.getenv('GITHUB_ID')}"
TOKEN_ENDPOINT = "https://github.com/login/oauth/access_token"
USER_ENDPOINT = "https://api.github.com/user"


"""
1. Войти через браузер, используя 'Authorization URL', выведенный в терминале.
   (Если вы уже залогинены в GitHub, нужно или вылогиниться, или тестировать в браузере в режиме инкогнито).
2. Сразу после входа страница будет перенаправлена. Скопируйте код из URL перенаправления.
3. Вставьте код в терминал.
"""
print(f"Authorization URL: {AUTHORIZATION_ENDPOINT}")
code = input("Enter the code: ")


"""
Используя код авторизации, мы можем запросить токен доступа.
"""
# Получив код, мы отсылаем его эндпоинту токена авторизации
# (с id и secret). Ответ содержит
# access_token, который мы парсим при помощи parse_qs
res = requests.post(
    TOKEN_ENDPOINT,
    data=dict(
        client_id=os.getenv("GITHUB_ID"),
        client_secret=os.getenv("GITHUB_SECRET"),
        code=code,
    ),
)
res = parse_qs(res.content.decode("utf-8"))
token = res["access_token"][0]


"""
Наконец, мы можем использовать токен доступа для получения информации о пользователе.
"""
user_data = requests.get(USER_ENDPOINT, headers=dict(Authorization=f"token {token}"))
username = user_data.json()["login"]
print(f"You are {username} on GitHub")

Чтобы протестировать, сохраните этот код в файл под названием oauth.py. Не забудьте просмотреть комментарии.

Далее вам нужно создать OAuth-приложение и получить OAuth-ключи от GitHub. Войдите в свой аккаунт на GitHub, а затем перейдите на https://github.com/settings/applications/new, чтобы создать новое OAuth-приложение:

Application name: Testing Flask-Dance
Homepage URL: http://127.0.0.1:5000
Callback URL: http://127.0.0.1:5000/login/github/authorized
Форма регистрации нового OAuth-приложения. Красные стрелки указывают на поля имени приложения, URL домашней страницы и URL обратного вызова авторизации.

Нажмите «Register application». Вы будете перенаправлены в ваше приложение. Обратите внимание на Client ID и Client Secret:

Вкладка Testing Flask-Dance. Красные стрелки указывают на Client ID и Client Secret

Если Client Secret не сгенерировался, нажмите «Generate a new client secret».

Установите сгенерированные Client ID и Client Secret в качестве переменных окружения:

$ export GITHUB_ID=<your-github-id>
$ export GITHUB_SECRET=<your-github-secret>
# for windows machine, use `set` instead of `export`

Установите библиотеку Requests, а затем запустите скрипт:

$ pip install requests
$ python oauth.py

Вы должны увидеть следующее:

Authorization URL: https://github.com/login/oauth/authorize?response_type=code&client_id=cde067521efaefe0c927
Enter the code:

Перейдите по этому URL-адресу. Авторизуйте приложение. Затем возьмите код из URL-адреса перенаправления. Например:

http://127.0.0.1:5000/login/github/authorized?code=5e54f2d755e450a64af3

Добавьте код обратно в окно терминала:

Authorization URL: https://github.com/login/oauth/authorize?response_type=code&client_id=cde067521efaefe0c927
Enter the code: 5e54f2d755e450a64af3

Вы должны увидеть имя пользователя GitHub, выведенное следующим образом:

You are mjhea0 on GitHub

А теперь давайте рассмотрим, как добавить социальную аутентификацию в приложение на Flask.

Flask-Dance

OAuthLib — это популярная, хорошо поддерживаемая библиотека Python, которая реализует OAuth. Хотя вы можете использовать эту библиотеку отдельно, в этом руководстве мы будем использовать Flask-Dance.

Flask-Dance — это библиотека, построенная поверх OAuthLib и предназначенная специально для Flask. Она имеет простой API, позволяющий быстро добавить социальный аутентификатор в приложение на Flask. Она также является самой популярной среди библиотек OAuth, разработанных для Flask.

Создайте новое приложение Flask, активируйте виртуальное окружение и установите необходимые зависимости:

$ mkdir flask-social-auth && cd flask-social-auth
$ python3.12 -m venv .venv
$ source .venv/bin/activate
(.venv)$ pip install Flask==3.0.2 Flask-Dance==7.0.1 python-dotenv==1.0.1

Далее создайте файл main.py:

# main.py

from flask import Flask, jsonify


app = Flask(__name__)


@app.route("/ping")
def ping():
    return jsonify(ping="pong")


if __name__ == "__main__":
    app.run(debug=True)

Запустите сервер:

(.venv)$ python main.py

Перейдите по адресу http://127.0.0.1:5000/ping. Вы должны увидеть следующее:

{
  "ping": "pong"
}

Социальная аутентификация через GitHub

Сохраните GitHub Client ID и Client Secret, которые вы создали ранее, в новом файле .env:

GITHUB_ID=<YOUR_ID_HERE>
GITHUB_SECRET=<YOUR_SECRET_HERE>

OAUTHLIB_INSECURE_TRANSPORT=1

Примечания:

  1. Поскольку мы установили python-dotenv, Flask будет автоматически устанавливать переменные окружения из файла .env при запуске приложения.
  2. OAUTHLIB_INSECURE_TRANSPORT=1 требуется в целях тестирования, так как OAuthLib по умолчанию требует HTTPS.

Flask-Dance предоставляет Flask blueprints (шаблоны) для каждого провайдера. Давайте создадим такой blueprint для провайдера GitHub в файле app/oauth.py:

# app/oauth.py

import os

from flask_dance.contrib.github import make_github_blueprint


github_blueprint = make_github_blueprint(
    client_id=os.getenv("GITHUB_ID"),
    client_secret=os.getenv("GITHUB_SECRET"),
)

Импортируйте и зарегистрируйте blueprint в main.py и проложите новый маршрут:

# main.py

from flask import Flask, jsonify, redirect, url_for
from flask_dance.contrib.github import github

from app.oauth import github_blueprint


app = Flask(__name__)
app.secret_key = "supersecretkey"
app.register_blueprint(github_blueprint, url_prefix="/login")


@app.route("/ping")
def ping():
    return jsonify(ping="pong")


@app.route("/github")
def login():
    if not github.authorized:
        return redirect(url_for("github.login"))
    res = github.get("/user")

    return f"You are @{res.json()['login']} on GitHub"


if __name__ == "__main__":
    app.run(debug=True)

Маршрут /github перенаправляет на GitHub для проверки, если пользователь еще не вошел в систему. После входа отображается имя пользователя.

Запустите приложение, выполнив команду python main.py. Перейдите на http://127.0.0.1:5000/github и протестируйте приложение. После проверки на GitHub вы будете перенаправлены обратно. Вы должны увидеть что-то типа этого:

You are @mjhea0 on GitHub

Управление пользователями

Теперь давайте подключим Flask-Login для управления сессиями пользователей и Flask-SQLAlchemy для добавления поддержки SQLAlchemy, чтобы хранить данные о пользователях в базе данных.

Установите зависимости:

(.venv)$ pip install Flask-Login==0.6.3 Flask-SQLAlchemy==3.1.1 SQLAlchemy-Utils==0.41.1

Модели

Создайте модели для хранения информации о пользователях и OAuth в новом файле app/models.py:

# app/models.py

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin, LoginManager
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin


db = SQLAlchemy()


class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(250), unique=True)


class OAuth(OAuthConsumerMixin, db.Model):
    user_id = db.Column(db.Integer, db.ForeignKey(User.id))
    user = db.relationship(User)


login_manager = LoginManager()


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

Примечания:

  1. OAuthConsumerMixin из Flask-Dance автоматически добавит необходимые поля для хранения информации OAuth.
  2. LoginManager из Flask-Login будет извлекать пользователей из таблицы user.

В результате будут созданы две таблицы, user и flask_dance_oauth:

# user table

name          type
--------  ------------
id        INTEGER
username  VARCHAR(250)

# flask_dance_oauth table

name        type
----------  -----------
id          INTEGER
provider    VARCHAR(50)
created_at  DATETIME
token       TEXT
user_id     INTEGER

GitHub blueprint

Далее измените созданный ранее GitHub blueprint, чтобы добавить таблицу OAuth в качестве хранилища:

# app/oauth.py

import os

from flask_login import current_user
from flask_dance.contrib.github import make_github_blueprint
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage

from app.models import OAuth, db


github_blueprint = make_github_blueprint(
    client_id=os.getenv("GITHUB_ID"),
    client_secret=os.getenv("GITHUB_SECRET"),
    storage=SQLAlchemyStorage(
        OAuth,
        db.session,
        user=current_user,
        user_required=False,
    ),
)

Здесь мы передали:

  • storage как хранилище SQLAlchemy с моделью OAuth
  • db.session, который является sqlalchemy.session
  • пользователя как current_user из Flask Login

Конечные точки

Давайте определим соответствующие конечные точки в main.py — login, logout и homepage:

# main.py

from flask import Flask, jsonify, redirect, render_template, url_for
from flask_dance.contrib.github import github
from flask_login import logout_user, login_required

from app.models import db, login_manager
from app.oauth import github_blueprint


app = Flask(__name__)
app.secret_key = "supersecretkey"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///./users.db"
app.register_blueprint(github_blueprint, url_prefix="/login")

db.init_app(app)
login_manager.init_app(app)

with app.app_context():
    db.create_all()


@app.route("/ping")
def ping():
    return jsonify(ping="pong")


@app.route("/")
def homepage():
    return render_template("index.html")


@app.route("/github")
def login():
    if not github.authorized:
        return redirect(url_for("github.login"))
    res = github.get("/user")
    username = res.json()["login"]
    return f"You are @{username} on GitHub"


@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for("homepage"))


if __name__ == "__main__":
    app.run(debug=True)

Здесь мы инициализировали db и login_manager, определенные ранее в models.py.

Представление homepage отображает шаблон index.html, который мы скоро добавим. Далее представление login аутентифицирует пользователя на GitHub и возвращает имя пользователя. Маршрут logout выводит пользователя из системы.

Все маршруты уже настроены, но мы еще не вошли в систему. Для этого мы воспользуемся сигналами Flask (Signals).

Сигналы

Сигналы позволяют выполнять действия при наступлении предопределенных событий. В нашем случае мы будем регистрировать пользователя при успешной аутентификации через GitHub.

Для работы сигналов требуется Binker, поэтому установите его прямо сейчас:

(.venv)$ pip install blinker==1.7.0

Добавьте новый helper в app/oauth.py:

# app/oauth.py

import os

from flask_login import current_user, login_user
from flask_dance.consumer import oauth_authorized
from flask_dance.contrib.github import github, make_github_blueprint
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage
from sqlalchemy.orm.exc import NoResultFound

from app.models import db, OAuth, User


github_blueprint = make_github_blueprint(
    client_id=os.getenv("GITHUB_ID"),
    client_secret=os.getenv("GITHUB_SECRET"),
    storage=SQLAlchemyStorage(
        OAuth,
        db.session,
        user=current_user,
        user_required=False,
    ),
)


@oauth_authorized.connect_via(github_blueprint)
def github_logged_in(blueprint, token):
    info = github.get("/user")
    if info.ok:
        account_info = info.json()
        username = account_info["login"]

        query = User.query.filter_by(username=username)
        try:
            user = query.one()
        except NoResultFound:
            user = User(username=username)
            db.session.add(user)
            db.session.commit()
        login_user(user)

Когда пользователь подключается через github_blueprint, выполняется функция github_logged_in. Она принимает два параметра: blueprint и token (от GitHub). Затем мы получаем имя пользователя от провайдера и выполняем одно из двух действий:

  1. Если имя пользователя уже присутствует в таблицах, мы обеспечиваем пользователю вход.
  2. Если нет — создаем нового пользователя, а затем обеспечиваем ему вход.

Шаблоны

Наконец, давайте добавим шаблоны:

(.venv)$ mkdir templates && cd templates
(.venv)$ touch _base.html
(.venv)$ touch index.html

Шаблон _base.html содержит общий макет:

<!-- templates/_base.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Flask Social Login</title>
  </head>
  <body style="padding-top: 10%;">
    {% block content %} {% endblock content %}
  </body>
</html>

Далее добавим кнопку «Login with GitHub» в index.html:

<!-- templates/index.html -->

{% extends '_base.html' %}

{% block content %}
  <div style="text-align:center;">
    {% if current_user.is_authenticated %}
      <h1>You are logged in as {{current_user.username}}</h1>
      <br><br>
      <a href="{{url_for('logout')}}" class="btn btn-danger">Logout</a>
    {% else %}
      <!-- GitHub button starts here -->
      <a href="{{url_for('login')}}"  class="btn btn-secondary">
        <i class="fa fa-github fa-fw"></i>
        <span>Login with GitHub</span>
      </a>
      <!-- GitHub button ends here -->
    {% endif %}
  </div>
{% endblock content %}

После этого запустите приложение и перейдите по адресу http://127.0.0.1:5000. Протестируйте поток аутентификации.

Структура проекта:

├── .env
├── app
│   ├── __init__.py
│   ├── models.py
│   └── oauth.py
├── main.py
└── templates
    ├── _base.html
    └── index.html

Социальная аутентификация через Google

Теперь, когда вы знаете шаги по подключению нового провайдера OAuth и настройке Flask-Login, вы должны суметь настроить нового провайдера.

Например, вот шаги для Google:

  1. Создать приложение OAuth в Google
  2. Настроить Google blueprint в файле app/oauth.py
  3. Настроить маршрут для перенаправления на вход в Google в файле main.py
  4. Создать новую конечную точку для входа в Google (@app.route("/google")) в main.py
  5. Создать новый сигнал Flask для входа пользователя в систему, когда он аутентифицируется через Google (@oauth_authorized.connect_via(google_blueprint)) в app/oauth.py
  6. Обновить шаблон templates/index.html

Попробуйте сделать это самостоятельно.

Заключение

В этом руководстве мы подробно рассказали о том, как добавить социальный аутентификатор в приложение на Flask с помощью Flask-Dance. Настроив GitHub и Google, вы должны получить представление о том, как подключать новых провайдеров социальной аутентификации:

  1. Получить токены для каждого провайдера, создав OAuth-приложения
  2. Настроить модели баз данных для хранения данных пользователей и OAuth
  3. Создать blueprint для каждого провайдера и добавить созданную модель OAuth в качестве хранилища
  4. Добавить маршрут для аутентификации у провайдера
  5. Добавить сигнал для входа пользователя в систему после аутентификации

Ищете дополнительные задачи?

  1. Разберитесь, как связать несколько логинов в социальных сетях с одним аккаунтом (чтобы при входе пользователя через аккаунт в другой социальной сети не создавалась новая строка в таблице user, а новый аккаунт в социальной сети был связан с существующим пользователем).
  2. Получите от социального провайдера дополнительную информацию о пользователе (например, email, язык, страну), указав области OAuth.

Код можно взять из репозитория flask-social-auth на GitHub.

Перевод статьи «Adding Social Authentication to Flask».