You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

20 KiB

OAuth2 scopes

Ви можете використовувати OAuth2 scopes безпосередньо з FastAPI, вони інтегровані для безшовної роботи.

Це дозволить мати більш детальну систему дозволів, відповідно до стандарту OAuth2, інтегровану у ваш застосунок OpenAPI (і документацію API).

OAuth2 зі scopes - це механізм, який використовують багато великих провайдерів автентифікації, як-от Facebook, Google, GitHub, Microsoft, X (Twitter) тощо. Вони застосовують його, щоб надавати конкретні дозволи користувачам і застосункам.

Кожного разу, коли ви «log in with» Facebook, Google, GitHub, Microsoft, X (Twitter), цей застосунок використовує OAuth2 зі scopes.

У цьому розділі ви побачите, як керувати автентифікацією та авторизацією за допомогою того ж OAuth2 зі scopes у вашому застосунку FastAPI.

/// warning | Попередження

Це більш-менш просунутий розділ. Якщо ви тільки починаєте, можете пропустити його.

Вам не обов’язково потрібні OAuth2 scopes, ви можете керувати автентифікацією та авторизацією будь-яким зручним способом.

Але OAuth2 зі scopes можна гарно інтегрувати у ваш API (з OpenAPI) і документацію API.

Водночас ви все одно примушуєте виконувати ці scopes або будь-які інші вимоги безпеки/авторизації так, як потрібно, у своєму коді.

У багатьох випадках OAuth2 зі scopes - це надмірність.

Але якщо ви знаєте, що це потрібно, або просто цікаво, читайте далі.

///

OAuth2 scopes та OpenAPI

Специфікація OAuth2 визначає «scopes» як список строк, розділених пробілами.

Вміст кожної з цих строк може мати будь-який формат, але не повинен містити пробілів.

Ці scopes представляють «дозволи».

В OpenAPI (наприклад, у документації API) ви можете визначати «схеми безпеки».

Коли одна з цих схем безпеки використовує OAuth2, ви також можете оголошувати та використовувати scopes.

Кожен «scope» - це просто строка (без пробілів).

Зазвичай їх використовують для оголошення конкретних дозволів безпеки, наприклад:

  • users:read або users:write - поширені приклади.
  • instagram_basic використовується Facebook / Instagram.
  • https://www.googleapis.com/auth/drive використовується Google.

/// info | Інформація

В OAuth2 «scope» - це просто строка, що декларує конкретний потрібний дозвіл.

Не має значення, чи містить вона інші символи на кшталт : або чи це URL.

Ці деталі специфічні для реалізації.

Для OAuth2 це просто строки.

///

Загальний огляд

Спочатку швидко подивімося на частини, що відрізняються від прикладів у головному Навчальному посібнику - Керівництві користувача для OAuth2 з паролем (і хешуванням), Bearer з JWT-токенами{.internal-link target=_blank}. Тепер із використанням OAuth2 scopes:

{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}

Тепер розгляньмо ці зміни крок за кроком.

Схема безпеки OAuth2

Перша зміна - тепер ми оголошуємо схему безпеки OAuth2 з двома доступними scopes: me і items.

Параметр scopes приймає dict, де кожен scope - це ключ, а опис - значення:

{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}

Оскільки тепер ми оголошуємо ці scopes, вони з’являться в документації API, коли ви увійдете/авторизуєтеся.

І ви зможете обрати, які scopes надати доступ: me і items.

Це той самий механізм, який використовується, коли ви надаєте дозволи під час входу через Facebook, Google, GitHub тощо:

JWT токен зі scopes

Тепер змініть операцію шляху токена, щоб повертати запитані scopes.

Ми все ще використовуємо той самий OAuth2PasswordRequestForm. Він містить властивість scopes зі list з str, по одному scope, отриманому в запиті.

І ми повертаємо scopes як частину JWT токена.

/// danger | Обережно

Для простоти тут ми просто додаємо отримані scopes безпосередньо до токена.

Але у вашому застосунку, з міркувань безпеки, переконайтеся, що ви додаєте лише ті scopes, які користувач дійсно може мати, або ті, що ви попередньо визначили.

///

{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}

Оголосіть scopes в операціях шляху та залежностях

Тепер ми оголошуємо, що операція шляху для /users/me/items/ вимагає scope items.

Для цього імпортуємо і використовуємо Security з fastapi.

Ви можете використовувати Security для оголошення залежностей (так само як Depends), але Security також приймає параметр scopes зі списком scopes (строк).

У цьому випадку ми передаємо функцію-залежність get_current_active_user до Security (так само, як зробили б із Depends).

А також передаємо list scopes, у цьому випадку лише один scope: items (їх могло б бути більше).

І функція-залежність get_current_active_user також може оголошувати підзалежності не лише з Depends, а й з Security. Оголошуючи свою підзалежність (get_current_user) і додаткові вимоги до scopes.

У цьому випадку вона вимагає scope me (вона могла б вимагати більш ніж один scope).

/// note | Примітка

Вам не обов’язково додавати різні scopes у різних місцях.

Ми робимо це тут, щоб показати, як FastAPI обробляє scopes, оголошені на різних рівнях.

///

{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}

/// info | Технічні деталі

Security насправді є підкласом Depends, і має лише один додатковий параметр, який ми побачимо пізніше.

Але використовуючи Security замість Depends, FastAPI знатиме, що можна оголошувати scopes безпеки, використовувати їх внутрішньо та документувати API через OpenAPI.

Коли ви імпортуєте Query, Path, Depends, Security та інші з fastapi, це насправді функції, що повертають спеціальні класи.

///

Використовуйте SecurityScopes

Тепер оновіть залежність get_current_user.

Вона використовується наведеними вище залежностями.

Тут ми використовуємо ту саму схему OAuth2, створену раніше, оголошуючи її як залежність: oauth2_scheme.

Оскільки ця функція-залежність не має власних вимог до scopes, ми можемо використовувати Depends з oauth2_scheme, немає потреби застосовувати Security, коли не потрібно вказувати scopes безпеки.

Ми також оголошуємо спеціальний параметр типу SecurityScopes, імпортований з fastapi.security.

Клас SecurityScopes подібний до RequestRequest ми напряму отримували об’єкт запиту).

{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}

Використовуйте scopes

Параметр security_scopes матиме тип SecurityScopes.

Він матиме властивість scopes зі списком, що містить усі scopes, потрібні самій функції та всім залежним, які використовують її як підзалежність. Тобто всім «залежним»... це може звучати заплутано, нижче пояснено ще раз.

Об’єкт security_scopes (класу SecurityScopes) також надає атрибут scope_str з одним рядком, що містить ці scopes, розділені пробілами (ми його використаємо).

Ми створюємо HTTPException, який зможемо повторно використати (raise) у кількох місцях.

У цьому винятку ми включаємо потрібні scopes (якщо є) як строку, розділену пробілами (використовуючи scope_str). Ми поміщаємо цю строку зі scopes в заголовок WWW-Authenticate (це частина специфікації).

{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}

Перевірте username і структуру даних

Ми перевіряємо, що отримали username, і видобуваємо scopes.

Потім валідовуємо ці дані за допомогою Pydantic-моделі (перехоплюючи виняток ValidationError), і якщо виникає помилка читання JWT токена або валідації даних Pydantic, підіймаємо раніше створений HTTPException.

Для цього ми оновлюємо Pydantic-модель TokenData новою властивістю scopes.

Валідувавши дані через Pydantic, ми гарантуємо, що, наприклад, маємо саме list із str для scopes і str для username.

Замість, наприклад, dict або чогось іншого, що може зламати застосунок далі, створивши ризик безпеки.

Ми також перевіряємо, що існує користувач із цим username, інакше підіймаємо той самий виняток.

{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}

Перевірте scopes

Тепер перевіряємо, що всі потрібні scopes - для цієї залежності та всіх залежних (включно з операціями шляху) - містяться в scopes, наданих у отриманому токені, інакше підіймаємо HTTPException.

Для цього використовуємо security_scopes.scopes, що містить list із усіма цими scopes як str.

{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}

Дерево залежностей і scopes

Ще раз розгляньмо дерево залежностей і scopes.

Оскільки залежність get_current_active_user має підзалежність get_current_user, scope «me», оголошений у get_current_active_user, буде включений до списку потрібних scopes у security_scopes.scopes, переданого до get_current_user.

Сама операція шляху також оголошує scope «items», отже він також буде у списку security_scopes.scopes, переданому до get_current_user.

Ось як виглядає ієрархія залежностей і scopes:

  • Операція шляху read_own_items має:
    • Потрібні scopes ["items"] із залежністю:
    • get_current_active_user:
      • Функція-залежність get_current_active_user має:
        • Потрібні scopes ["me"] із залежністю:
        • get_current_user:
          • Функція-залежність get_current_user має:
            • Власних scopes не потребує.
            • Залежність, що використовує oauth2_scheme.
            • Параметр security_scopes типу SecurityScopes:
              • Цей параметр security_scopes має властивість scopes із list, що містить усі наведені вище scopes, отже:
                • security_scopes.scopes міститиме ["me", "items"] для операції шляху read_own_items.
                • security_scopes.scopes міститиме ["me"] для операції шляху read_users_me, адже він оголошений у залежності get_current_active_user.
                • security_scopes.scopes міститиме [] (нічого) для операції шляху read_system_status, бо вона не оголошує жодного Security зі scopes, і її залежність get_current_user також не оголошує жодних scopes.

/// tip | Порада

Важливе і «магічне» тут у тому, що get_current_user матиме різні списки scopes для перевірки для кожної операції шляху.

Усе залежить від scopes, оголошених у кожній операції шляху та кожній залежності в дереві залежностей для конкретної операції шляху.

///

Більше деталей про SecurityScopes

Ви можете використовувати SecurityScopes у будь-якому місці й у кількох місцях, він не обов’язково має бути в «кореневій» залежності.

Він завжди міститиме scopes безпеки, оголошені в поточних залежностях Security і всіх залежних для цієї конкретної операції шляху і цього конкретного дерева залежностей.

Оскільки SecurityScopes міститиме всі scopes, оголошені залежними, ви можете використовувати його, щоб перевірити, що токен має потрібні scopes, у центральній функції-залежності, а потім оголошувати різні вимоги до scopes у різних операціях шляху.

Вони перевірятимуться незалежно для кожної операції шляху.

Перевірте

Якщо ви відкриєте документацію API, ви зможете автентифікуватися і вказати, які scopes хочете авторизувати.

Якщо ви не оберете жодного scope, ви будете «автентифіковані», але при спробі доступу до /users/me/ або /users/me/items/ отримаєте помилку про недостатні дозволи. Ви все ще матимете доступ до /status/.

Якщо оберете scope me, але не scope items, ви зможете отримати доступ до /users/me/, але не до /users/me/items/.

Так станеться зі стороннім застосунком, який спробує звернутися до однієї з цих операцій шляху з токеном, наданим користувачем, залежно від того, скільки дозволів користувач надав застосунку.

Про сторонні інтеграції

У цьому прикладі ми використовуємо «потік паролю» OAuth2.

Це доречно, коли ми входимо у власний застосунок, ймовірно, з власним фронтендом.

Адже ми можемо довіряти йому отримання username і password, бо ми його контролюємо.

Але якщо ви створюєте OAuth2-застосунок, до якого підключатимуться інші (тобто якщо ви створюєте провайдера автентифікації на кшталт Facebook, Google, GitHub тощо), слід використовувати один з інших потоків.

Найпоширеніший - неявний потік (implicit flow).

Найбезпечніший - потік коду (code flow), але його складніше реалізувати, оскільки він потребує більше кроків. Через складність багато провайдерів у підсумку радять неявний потік.

/// note | Примітка

Часто кожен провайдер автентифікації називає свої потоки по-різному, роблячи це частиною свого бренду.

Але зрештою вони реалізують той самий стандарт OAuth2.

///

FastAPI містить утиліти для всіх цих потоків автентифікації OAuth2 у fastapi.security.oauth2.

Security у параметрі декоратора dependencies

Так само як ви можете визначити list із Depends у параметрі dependencies декоратора (як пояснено в Залежності в декораторах операцій шляху{.internal-link target=_blank}), ви також можете використовувати там Security зі scopes.