Что означает система единого входа?

Единый вход (SSO) — это удобный и безопасный метод аутентификации, который позволяет пользователям получать доступ к нескольким приложениям и службам с помощью единого набора учетных данных для входа. Вместо того, чтобы запоминать и вводить отдельные имена пользователей и пароли для каждого приложения, SSO упрощает процесс, предоставляя централизованную систему входа в систему. Это не только улучшает взаимодействие с пользователем, но и повышает безопасность за счет снижения риска уязвимостей, связанных с паролями. Появились различные поставщики SSO, чтобы удовлетворить растущий спрос на упрощенное управление доступом.

Цель

В этой статье мы продемонстрируем реализацию единого входа в FastAPI для входа пользователей в систему через вход через Google.

Для эквивалентных процессов с другими провайдерами вы можете перейти по этим ссылкам на статьи этой серии:

1. Система единого входа Facebook в FastAPI

2. GitHub SSO в FastAPI

3. Spotify SSO в FastAPI

4. Microsoft SSO в FastAPI

Выполнение

Все фрагменты кода из этой статьи включены в специальный репозиторий GitHub, который можно клонировать/разветвить, чтобы протестировать код или использовать его для одного из ваших проектов!

Если вы сочтете это полезным, поставьте звездочку и поделитесь со всеми, кому это может быть интересно!

Приложение

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

FastAPI-аутентификация

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

единый вход FastAPI

FastAPI SSO — это библиотека с открытым исходным кодом, которая нормализует процесс единого входа в некоторых наиболее популярных провайдерах и позволяет разработчику легко интегрировать процесс входа.

Я уверен, что должно существовать больше способов, но я обнаружил, что этот репозиторий раскрывает функциональность достаточно просто и прямолинейно.

Настройка приложения Google

Чтобы использовать механизм единого входа Google, необходимо создать приложение с помощью облачной консоли Google, давайте вместе выполним следующие шаги:

  • Во-первых, потребуется учетная запись Google и проект в Google Cloud Platform. Поэтому для выполнения следующих шагов вам необходимо настроить свою учетную запись и получить доступ к консоли Google Cloud для создания проекта. Не беспокойтесь о выставлении счетов. Облачная платформа Google предоставляет кредитные баллы для каждой новой учетной записи, но нам они все равно не нужны, так как в этой демонстрации ничего не оплачивается.
  • Находясь в консоли Google Cloud, выполните поиск учетных данных в строке поиска.
  • Включите все API, которые перечислены там как неактивные.
  • Перейдите на вкладку «Учетные данные» в меню слева.

  • Нажмите «Создать учетные данные» в меню верхней панели и выберите параметр «Идентификатор клиента OAuth».

  • В форме выберите вариант веб-приложения, укажите любое имя и добавьте URI перенаправления. Этот URI является конечной точкой обратного вызова, на которую механизм входа Google будет перенаправляться после выполнения собственной аутентификации, передавая информацию о пользователе. Обратите внимание, что здесь я использую URI localhost. Это потому, что я попробую все это локально, так как я не собираюсь развертывать эту реализацию на каком-то хосте. Один раз, и если ваш код развернут на каком-либо сервере, этот URI следует обновить, чтобы он указывал на точный IP/домен и путь URI для вашей конечной точки обратного вызова.

  • Нажмите «Создать», после чего появится вот такое окно:

Обязательно загрузите этот файл JSON или скопируйте идентификатор клиента и секретные значения. Мы будем использовать их позже в нашей конфигурации кода.

Пользовательский интерфейс

Наш минимальный пользовательский интерфейс выглядит так:

Здесь нет ничего особенного, просто форма, в которой пользователь может войти в систему, и несколько различных способов входа.

Реализация пользовательского интерфейса осуществляется с помощью шаблона Jinja. Конечно, любой интерфейсный фреймворк можно использовать для разделения реализации внешнего интерфейса, но все это выходит за рамки этой демонстрации.

Все, что нам нужно знать об этом пользовательском интерфейсе, это то, что происходит, когда мы нажимаем кнопку входа в Google, которая описана в этом фрагменте HTML-кода:

<form class="col-lg-2" method="get" action="/v1/google/login" style="text-align: center;">
    <button class="login-btn" type="submit">
        <i class="fa-brands fa-google"></i>
    </button>
</form>

Таким образом, после нажатия этой кнопки (отправить) будет выполнено действие GETrequest по относительному пути /v1/google/login. Давайте запомним это и вскоре посмотрим, что это значит для наших конечных точек API.

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

Наши необходимые конечные точки для работы этого процесса выглядят так:

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import RedirectResponse
from database_crud import users_db_crud as db_crud
from schemas import UserSignUp
from sqlalchemy.orm import Session
from database import get_db
from fastapi_sso.sso.google import GoogleSSO
from starlette.requests import Request
from authentication import create_access_token, SESSION_COOKIE_NAME
from dotenv import load_dotenv
from pathlib import Path
import os


directory_path = Path(__file__).parent
env_file_path = directory_path.parent / '.env'

load_dotenv()
GOOGLE_CLIENT_ID =  os.getenv("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET =  os.getenv("GOOGLE_CLIENT_SECRET")

os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

google_sso = GoogleSSO(
    GOOGLE_CLIENT_ID,
    GOOGLE_CLIENT_SECRET, 
    "http://localhost:9999/v1/google/callback",
    allow_insecure_http=True
)

router = APIRouter(prefix="/v1/google")


@router.get("/login", tags=['Google SSO'])
async def google_login():
    return await google_sso.get_login_redirect(params={"prompt": "consent", "access_type": "offline"})


@router.get("/callback", tags=['Google SSO'])
async def google_callback(request: Request, db: Session = Depends(get_db)):
    """Process login response from Google and return user info"""

    try:
        user = await google_sso.verify_and_process(request)
        user_stored = db_crud.get_user(db, user.email, provider=user.provider)
        if not user_stored:
            user_to_add = UserSignUp(
                username=user.email,
                fullname=user.display_name
            )
            user_stored = db_crud.add_user(db, user_to_add, provider=user.provider)
        access_token = create_access_token(username=user_stored.username, provider=user.provider)
        response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
        response.set_cookie(SESSION_COOKIE_NAME, access_token)
        return response
    except db_crud.DuplicateError as e:
        raise HTTPException(status_code=403, detail=f"{e}")
    except ValueError as e:
        raise HTTPException(status_code=400, detail=f"{e}")
    except Exception as e:
        raise HTTPException(
            status_code=500, detail=f"An unexpected error occurred. Report this message to support: {e}")

Давайте посмотрим, что здесь происходит:

  • Загруженные переменные среды — это идентификатор и секрет клиента Google, которые мы получили после настройки нашего приложения Google и учетных данных в консоли Google Cloud Platform.
  • Класс GoogleSSO включен в импортированную библиотеку SSO, о которой мы говорили чуть ранее. Обратите внимание, что во время установки нам нужно указать там учетные данные нашего приложения, а также наш URI перенаправления, такой же, как URI перенаправления, объявленный в нашем приложении Google. Кроме того, поскольку нам нужно протестировать это локально и без включенного протокола https, нам нужно объявить это и разрешить небезопасный http-трафик, установив для соответствующего аргумента значение True.
  • /v1/google/loginendpoint — это место, где запускается весь механизм. Помните, что это был маршрут, на который ориентировалась наша кнопка входа в Google в форме пользовательского интерфейса. Выполняемая функция вызывает метод get_login_redirect экземпляра google_sso , который запускает процесс входа в Google в нашем браузере. Обратите внимание, что в окне формы указано имя нашего приложения Google, созданного на платформе Google Cloud.

  • Поток входа проходит через обычную аутентификацию пользователя и пароля и включает любые дополнительные правила безопасности, такие как 2FA через телефон и т. д.

  • Как только процесс входа в Google завершится, приложение Google перенаправит нас на наш объявленный URI перенаправления, следовательно, http://localhost:9999/v1/google/callback, который вернет нас обратно в наше локальное приложение FastAPI и, в частности, на вторую конечную точку в нашем коде.
  • Вся информация о пользователе получена в объекте user в этой строке
user = await google_sso.verify_and_process(request)

После этого момента вы можете делать все, что хотите, с этой информацией для этого пользователя.

  • Лично в этой реализации я решил сохранить электронную почту пользователя в моей базе данных в качестве имени пользователя для моей пользовательской модели и продолжить, подписав этого пользователя в API. Выбранный здесь метод — создать токен JWT и установить его как файл cookie в ответе. Таким образом, пользователь останется авторизованным в браузере, и функция завершит свое выполнение, перенаправив пользователя на домашнюю страницу по корневому пути /, где отображаемый шаблон Jinja распознает полученную информацию о пользователе.

Отображаемое имя пользователя — это электронное письмо, полученное от механизма Google SSO, а представленная таблица — это просто статистика из локальной базы данных. Обратите внимание, что провайдер Google уже аутентифицировал одного пользователя.

Это все люди!

Спасибо за ваше время, надеюсь, что-нибудь из этого полезно для кого-то!

До следующего,

счастливого кодирования!