# OAuth2 scopes { #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-and-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 це просто строки.

///

## Загальний огляд { #global-view }

Спочатку швидко подивімося на частини, що відрізняються від прикладів у головному **Навчальному посібнику - Керівництві користувача** для [OAuth2 з паролем (і хешуванням), Bearer з JWT-токенами](../../tutorial/security/oauth2-jwt.md). Тепер із використанням 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-security-scheme }

Перша зміна - тепер ми оголошуємо схему безпеки 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 тощо:

<img src="/img/tutorial/security/image11.png">

## JWT токен зі scopes { #jwt-token-with-scopes }

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

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

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

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

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

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

///

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

## Оголосіть scopes в операціях шляху та залежностях { #declare-scopes-in-path-operations-and-dependencies }

Тепер ми оголошуємо, що операція шляху для `/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` { #use-securityscopes }

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

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

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

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

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

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

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

## Використовуйте scopes { #use-the-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` і структуру даних { #verify-the-username-and-data-shape }

Ми перевіряємо, що отримали `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` { #verify-the-scopes }

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

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

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

## Дерево залежностей і scopes { #dependency-tree-and-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` { #more-details-about-securityscopes }

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

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

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

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

## Перевірте { #check-it }

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

<img src="/img/tutorial/security/image11.png">

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

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

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

## Про сторонні інтеграції { #about-third-party-integrations }

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

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

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

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

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

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

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

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

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

///

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

## `Security` у параметрі декоратора `dependencies` { #security-in-decorator-dependencies }

Так само як ви можете визначити `list` із `Depends` у параметрі `dependencies` декоратора (як пояснено в [Залежності в декораторах операцій шляху](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md)), ви також можете використовувати там `Security` зі `scopes`.
