Рубрики
Без рубрики

Демистификация аутентификации с fastapi и fressend

Аутентификация трудна, поэтому давайте попробуем просты для современных приложений с Bastapi Fastapi и Frestend JS, используя токены OAUTH и JWT. Помечено fastapi, python, аутентификация, github.

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

Новая рамка Fastapi Теперь наша веб-библиотека Web-библиотеки для всех наших проектов, так как она очень эффективна для развития, и она поддерживает удивительные набрав из коробки. Единственную проблему, имею дело с аутентификацией при использовании интерфейса JS перед ним. Давайте закрытим эту дискуссию раз и навсегда, описывая схему аутентификации, которую я думаю, что каждый нуждается в простом веб-приложении с Fastapi, используя внешний провайдер.

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

То, что я подразумеваю под Modern Web Application здесь, – это Backend Python Python Fastapi (но вы можете пойти с тем, что вам нравится, пока он выводит JSON) с интерфейсом JS (например, React или Vue). Поэтому нам нужно сделать аутентифицировать кого-то, навигацию на Frontend, и устанавливает безопасное соединение с бэккой, чтобы узнать, кто называет ваш API.

Просто правильно? И потому, что мы находимся в 21 веке, давайте не будем использовать форму по электронной почте/паролям/паролям на Frontend: они на самом деле не очень безопасны, и SSO (Single One-on) явно является маркетинговым преимуществом в настоящее время. Поэтому мы будем использовать схему аутентификации OAUTH для получения информации и аутентификации пользователя на нашем сайте.

Цель ОАУТ

Вы, возможно, слышали об этом, потому что каждый крупный поставщик авторизации поддерживает: Google, Twitter, GitHub, Facebook, … Но вы можете удивить, почему он существует в первую очередь!

Целью ОАУТ в целом является оптимизировать и стандартизировать использование Авторизация Вокруг Интернета, особенно когда дело доходит до нескольких вечеринок, таких как наш сайт и Google, например. Как вы можете получить доступ к информации Google с нашего веб-сайта с помощью логина Google? Это то, для чего сделан ОАУТ.

В этой технологии у нас есть как всегда обе стороны: сервер и клиент. В случае внешнего провайдера, такого как GitHub, Github – это сервер OAUTH, а наше приложение является клиентом. Целью OAUTH – это позволить клиенту запросить сервер API, прикрепленный к серверу OAUTH безопасным способом.

Что мы хотим сделать, это спросить Google, кто пользователь на сайте для достижения аутентификация . Мы храним эту информацию о пользователе в базе данных и тогда Закрепите соединение для этого пользователя между Frontend и Backend, чтобы идентифицировать и разрешать вызовы API.

Но имейте в виду: OAUTH2 сделан, чтобы облегчить разрешить наш сервер, чтобы взаимодействовать с другими сторонами, такими как Google или Github от имени пользователя. Затем вы можете спросить их, кто пользователь на веб-сайте заключается в том, чтобы сделать аутентификацию, для аутентификации связи между интерфейсом и нашим API.

Обеспечение связи между интерфейсом и нашим API

В конце мы нужны, предназначены для аутентификации вашего соединения между нашим Frestend (обычно SPA или SSR JavaScript) и нашим собственным API. Для этого мы будем использовать знаменитый токен JWT, который имеет несколько преимуществ и что мне очень нравится.

Протокол следующий:

  • Google перенаправляет в ваше приложение с кодом
  • Просим Google для получения дополнительной информации, как изображение профиля
  • Затем мы выпустим токен JWT с помощью этих пользовательских данных, которые мы будем делиться между Frestend и бэкэнда

Этот токен будет использоваться для каждого вызова API для нашей собственной бэкэнды и, следовательно, определит пользователь, делающий звонки.

Мы не используем OAUTH непосредственно для защиты соединения между Frontend и нашим API, потому что нам это не нужно, и это не то, что он сделан. Мы выпускаем только небольшой токен, и мы проверяем его для каждого запроса на бэкэнде. Безопасный. Простой:)

Узор аутентификации

Если вы уже смотрели на OAUTH2, вы, возможно, заметили, что несколько Схемы доступны и необходимы для различных типов приложений. Двое, которые мы заинтересованы в том, это Неявный Один и Код авторизации схема.

Неявная схема

С неявной схемой наш пользователь перенаправлен в логин SSO (1) а затем перенаправлено после аутентификации на наш интерфейс (2) с Access_Token необходимо получить доступ к ресурсам OAUTH (с изображением профиля). Это, как правило, полезно, когда нам нужно получить доступ к этому типу ресурса непосредственно в интерфейсе, не беспокоя о том, чтобы сделать ответную ответственность за это.

Чтобы закрепить соединение между Frestend и нашим собственным API, вам нужно дать этот токен к API (3) это будет проверять с сервером OAUTH, что это действительно (4) . Если токен действителен, то этот пользователь явно на сайте, так что давайте давайте дадим ему другой знак (5) (Чтобы проверить его Super быстро, не запрашивая сервер OAUTH каждый раз и контролировать его истечение, например).

Как уже упоминалось, наш Frestend также способен запросить напрямую внешнюю API (6) , с Access_Token , чтобы обработать на его стороне ресурсы, к которым пользователь дал доступ.

Схема кода авторизации

С помощью схемы кода авторизации наш пользователь также перенаправлен в логин SSO (1) а затем перенаправлено на наш интерфейс (2) , но с загадочным кодом, которым нам нужно дать бэкэнду (3) Отказ

Затем бэкэнда даст этот код (наряду с данными Client Client Client Client Client_ID и Client_Secret ) на сервер OAUTH, который затем вернет Access_Token Если код правильный (4) .

Если код правильный, мы можем использовать токен, заданный сервером OAUTH для доступа к ресурсам пользователя непосредственно с Backend (5) , чтобы сохранить фотографию профиля или файл Google DOC, который принадлежит пользователю, например. Затем наш пользователь аутентифицирован, так что давайте, наконец, дадим ему его токен (6) Отказ

Преимущество этой схемы заключается в том, что мы не помещаем в Frestend, отвечаю за Access_Token Сервера OAUTH, который имеет несколько преимуществ безопасности:

  • Frontend не приходится давать этот токен на бэкэнду, что лучше для безопасности, так как атака человека в среднем может украсть вашу личность. Это невозможно с кодом, поскольку ему нужно Client_Secret Что знают только бэкэнда.
  • Бэкэнда тот, с кем Access_Token Предотвращение любого вредоносных расширений воровать его в вашей передней области.

В нашем опыте мы предпочитаем эту схему для производственных приложений. Это единственное, которое поддерживается GitHub, например.

Нам очень нравится Fastapi в STRIO: эта структура проста, эффективна и набрала дружелюбную. Создание схемы аутентификации поверх нее было не так сложно, и действительно чисто. Мы просто должны помнить, что несколько советов, которые я описал ранее:

  • OAUTH только для внешнего доступа API
  • Простая кодировка jwt и декодирование между нашим интерфейсом и бэкэнда

В этом примере мы будем использовать Код авторизации Схема, с этим кодом переадресовано между нашим интерфейсом и нашей бэкэндом, а также Github внешний провайдер.

URL-адреса

Первое, что для настройки – это разные URL-адреса, которые мы будем использовать для этой схемы:

  • Login_url Нужен ли базовый URL-адрес, необходимый для создания URL, наш интерфейс будет перенаправлен на
  • Token_url Наша наша бэкэнда будет запрашивать с кодом авторизации, чтобы получить Access_Token Отказ
  • User_url Это конечная точка API, используемая для получения данных о пользователе, это типичный пример внешнего API, описанного ранее.
  • Redirect_url Является ли URL нашего интерфейса, что GitHub нужно будет перенаправить, это полезно, потому что он будет передан с Login_url Отказ
from app.settings import settings

LOGIN_URL = "https://github.com/login/oauth/authorize"
TOKEN_URL = "https://github.com/login/oauth/access_token"
USER_URL = "https://api.github.com/user"

REDIRECT_URL = f"{settings.app_url}/auth/github"

Создание URL входа в систему

Frontend необходимо перенаправить браузер пользователя к URL, сгенерированному из Login_url Но также с какой-то информацией, специфичной для нашего применения:

  • Client_id , данный GitHub для нашего приложения
  • Redirect_uri , что мы хотим GitHub переслать пользователя, с помощью кода авторизации
  • Государство , случайная сгенерированная строка, которую GitHub снова проверит, когда мы хотим создать Access_Token С по соображениям безопасности

Код довольно прост, мы создали маршрут под названием /авторизоваться Так что мы можем сказать, что интерфейс, какой URL использовать. Мы используем APIROUTER Вот потому что мы хотим интегрировать этот кусок кода непосредственно в существующее приложение Fastapi.

from urllib.parse import urlencode

from fastapi import APIRouter

from app.settings import settings
from .schemas import Url
from .helpers import generate_token

LOGIN_URL = "https://github.com/login/oauth/authorize"
REDIRECT_URL = f"{settings.app_url}/auth/github"

router = APIRouter()

@router.get("/login")
def get_login_url() -> Url:
    params = {
        "client_id": settings.github_client_id,
        "redirect_uri": REDIRECT_URL,
        "state": generate_token(),
    }
    return Url(url=f"{LOGIN_URL}?{urlencode(params)}")

Схема URL определяется так:

from pydantic import BaseModel

class Url(BaseModel):
    url: str

Подтверждение кода авторизации и создание токена

Как только интерфейс принимает код авторизации, он может переслать его на определенную конечную точку API, здесь /Уполномочить Для бэкки, чтобы проверить его и получить все необходимое у пользователя API. Первое, что мы хотим определить, являются схемы, которые будут использоваться:

from pydantic import BaseModel

class AuthorizationResponse(BaseModel):
    state: str
    code: str

class GithubUser(BaseModel):
    login: str
    name: str
    company: str
    location: str
    email: str
    avatar_url: str

class User(BaseModel):
    id: int
    login: str
    name: str
    company: str
    location: str
    email: str
    picture: str

    class Config:
        orm_mode = True

class Token(BaseModel):
    access_token: str
    token_type: str
    user: User

АвторизацияResponse Это тело запроса, сделанного Frontend с состоянием и кодом авторизации, а Githubuser и Пользователь Представляют пользователям разных источников. Токен Схема определяет то, что мы отправим к интерфейсу для аутентификации наших запросов между нашим API и интерфейсом.

На стороне маршрута мы используем httpx Сделать запросы на GitHub и проверьте код авторизации, а также извлечение информации пользователя. Затем мы создаем запись базы данных, если пользователь еще не присутствует в базе данных, используя SQLACHEMY Орм. Это простое реализация Github Документация на их Auth API Отказ

from urllib.parse import parse_qsl
from typing import Dict

import httpx
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

from app.database import get_db
from .helpers import create_access_token
from .schemas import AuthorizationResponse, GithubUser, User, Token
from .crud import get_user_by_login, create_user

TOKEN_URL = "https://github.com/login/oauth/access_token"
USER_URL = "https://api.github.com/user"

router = APIRouter()

@router.post("/authorize")
async def verify_authorization(
 body: AuthorizationResponse, db: Session = Depends(get_db)
) -> Token:
    params = {
        "client_id": settings.github_client_id,
        "client_secret": settings.github_client_secret,
        "code": body.code,
        "state": body.state,
    }

    async with httpx.AsyncClient() as client:
        token_request = await client.post(TOKEN_URL, params=params)
        response: Dict[bytes, bytes] = dict(parse_qsl(token_request.content))
        github_token = response[b"access_token"].decode("utf-8")
        github_header = {"Authorization": f"token {github_token}"}
        user_request = await client.get(USER_URL, headers=github_header)
        github_user = GithubUser(**user_request.json())

    db_user = get_user_by_login(db, github_user.login)
    if db_user is None:
        db_user = create_user(db, github_user)

    verified_user = User.from_orm(db_user)
    access_token = create_access_token(data=verified_user)

    return Token(access_token=access_token, token_type="bearer", user=db_user)

Функция create_access_token Создает простой токен JWT и поэтому в помощники файл:

from datetime import datetime, timedelta

import jwt

from app.settings import settings
from .schemas import User

def create_access_token(*, data: User, exp: int = None) -> bytes:
    to_encode = data.dict()
    if exp is not None:
        to_encode.update({"exp": exp})
    else:
        expire = datetime.utcnow() + timedelta(minutes=60)
        to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(
        to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm
    )
    return encoded_jwt

Получить пользователя из токена JWT

Теперь, когда на нашем Frontend есть токен JWT, нам просто нужно обеспечить наши частные маршруты с помощью зависимости Fastapi, которая будет декодировать токен и поднять исключение, если необходимо. Зависимость принимается так, как это, в своем собственном файле:

import jwt
from fastapi import Header, HTTPException, status
from fastapi.security.utils import get_authorization_scheme_param
from pydantic import ValidationError

from app.settings import settings
from .schemas import User

def get_user_from_header(*, authorization: str = Header(None)) -> User:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )

    scheme, token = get_authorization_scheme_param(authorization)
    if scheme.lower() != "bearer":
        raise credentials_exception

    try:
        payload = jwt.decode(
            token, settings.jwt_secret_key,
            algorithms=[settings.jwt_algorithm]
        )
        try:
            token_data = User(**payload)
            return token_data
        except ValidationError:
            raise credentials_exception
    except jwt.PyJWTError:
        raise credentials_exception

Мы можем использовать эту зависимость в некоторых частных конечных точках, как общий /меня Это даст информацию пользователя, делая запрос (через его токен JWT):

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session

from app.database import get_db

from .crud import get_user
from .schemas import User
from .models import User as DbUser
from .dependency import get_user_from_header

router = APIRouter()

@router.get("/me", response_model=User)
def read_profile(
    user: User = Depends(get_user_from_header),
    db: Session = Depends(get_db),
) -> DbUser:
    db_user = get_user(db, user.id)
    if db_user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return db_user

Аутентификация – очень сложный зверь для ручного зверя, а улучшения могут быть сделаны по нашему собственному подходу. Тем не менее, этот пример fastapi должен дать вам хорошее начало, чтобы реализовать свою собственную схему, используя любой внешний провайдер, который вам нравится (Google, Facebook, Twitter, …).

Оригинал: “https://dev.to/fuegoio/demystifying-authentication-with-fastapi-and-a-frontend-26f5”