# Простая авторизация по протоколу OAuth2 с токеном типа Bearer

Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить безопасную систему.

## Получение `имени пользователя` и `пароля`

Для получения `имени пользователя` и `пароля` мы будем использовать утилиты безопасности **FastAPI**.

Протокол OAuth2 определяет, что при использовании "аутентификации по паролю"  (которую мы и используем)  клиент/пользователь должен передавать поля `username` и `password` в полях формы.

В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут.

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

А ваши модели баз данных могут использовать любые другие имена.

Но при авторизации согласно спецификации, требуется использовать именно эти имена, что даст нам возможность воспользоваться встроенной системой документации API.

В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет).

### Oбласть видимости (scope)

В спецификации также говорится, что клиент может передать еще одно поле формы "`scope`".

Имя поля формы - `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных областей видимости (scopes), разделенных пробелами.

Каждая "область видимости" (scope) - это просто строка (без пробелов).

Обычно они используются для указания уровней доступа, например:

* `users:read` или `users:write` являются распространенными примерами.
* `instagram_basic` используется Facebook / Instagram.
* `https://www.googleapis.com/auth/drive` используется компанией Google.

/// info | Дополнительнаяя информация
В OAuth2 "scope" - это просто строка, которая уточняет уровень доступа.

Не имеет значения, содержит ли он другие символы, например `:`, или является ли он URL.

Эти детали зависят от конкретной реализации.

Для OAuth2 это просто строки.
///

## Код получения `имени пользователя` и `пароля`

Для решения задачи давайте воспользуемся утилитами, предоставляемыми **FastAPI**.

### `OAuth2PasswordRequestForm`

Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте ее как зависимость с `Depends` в *эндпоинте* `/token`:


{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}

`OAuth2PasswordRequestForm` - это класс для использования в качестве зависимости для *функции обрабатывающей эндпоинт*, который определяет тело формы со следующими полями:

* `username`.
* `password`.
* Необязательное поле `scope` в виде большой строки, состоящей из строк, разделенных пробелами.
* Необязательное поле `grant_type`.

/// tip | Подсказка
По спецификации OAuth2 поле `grant_type` является обязательным и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` не обеспечивает этого.

Если вам необходимо использовать `grant_type`, воспользуйтесь `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
///

* Необязательное поле `client_id` (в нашем примере он не нужен).
* Необязательное поле `client_secret` (в нашем примере он не нужен).

/// info | Дополнительная информация
Форма `OAuth2PasswordRequestForm` не является специальным классом для **FastAPI**, как `OAuth2PasswordBearer`.

`OAuth2PasswordBearer` указывает **FastAPI**, что это схема безопасности. Следовательно, она будет добавлена в OpenAPI.

Но `OAuth2PasswordRequestForm` - это всего лишь класс зависимости, который вы могли бы написать самостоятельно или вы могли бы объявить параметры `Form` напрямую.

Но, поскольку это распространённый вариант использования, он предоставляется **FastAPI** напрямую, просто чтобы облегчить задачу.
///

### Использование данных формы

/// tip | Подсказка
В экземпляре зависимого класса `OAuth2PasswordRequestForm` атрибут `scope`, состоящий из одной длинной строки, разделенной пробелами, заменен на атрибут `scopes`, состоящий из списка отдельных строк, каждая из которых соответствует определенному уровню доступа.

В данном примере мы не используем `scopes`, но если вам это необходимо, то такая функциональность имеется.
///

Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `username` из поля формы.

Если такого пользователя нет, то мы возвращаем ошибку "неверное имя пользователя или пароль".

Для ошибки мы используем исключение `HTTPException`:

{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}

### Проверка пароля

На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль.

Давайте сначала поместим эти данные в модель Pydantic `UserInDB`.

Ни в коем случае нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.

Если пароли не совпадают, мы возвращаем ту же ошибку.

#### Хеширование паролей

"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.

Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину.

Но преобразовать тарабарщину обратно в пароль невозможно.

##### Зачем использовать хеширование паролей

Если ваша база данных будет украдена, то у вора не будет паролей пользователей в открытом виде, только хэши.

Таким образом, вор не сможет использовать эти же пароли в другой системе (поскольку многие пользователи используют одни и те же пароли повсеместно, это было бы опасно).

{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}

#### Про `**user_dict`

`UserInDB(**user_dict)` означает:

*Передавать ключи и значения `user_dict` непосредственно в качестве аргументов ключ-значение, что эквивалентно:*

```Python
UserInDB(
    username = user_dict["username"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
    disabled = user_dict["disabled"],
    hashed_password = user_dict["hashed_password"],
)
```

/// info | Дополнительная информация
Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user_indict){.internal-link target=_blank}.
///

## Возврат токена

Ответ эндпоинта `token` должен представлять собой объект в формате JSON.

Он должен иметь `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть "`bearer`".

И в нем должна быть строка `access_token`, содержащая наш токен доступа.

В этом простом примере мы нарушим все правила безопасности, и будем считать, что имя пользователя (username) полностью соответствует токену (token)

/// tip | Подсказка
В следующей главе мы рассмотрим реальную защищенную реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens">JWT</abbr>.

Но пока давайте остановимся на необходимых нам деталях.
///

{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}

/// tip | Подсказка
Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере.

Это то, что вы должны сделать сами в своем коде и убедиться, что вы используете эти JSON-ключи.

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

Все остальное за вас сделает **FastAPI**.
///

## Обновление зависимостей

Теперь мы обновим наши зависимости.

Мы хотим получить значение `current_user` *только* если этот пользователь активен.

Поэтому мы создаем дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.

Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен.

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

{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}

/// info | Дополнительная информация
Дополнительный заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.

Ответ сервера с  HTTP-кодом 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.

В случае с bearer-токенами (наш случай) значение этого заголовка должно быть `Bearer`.

На самом деле этот дополнительный заголовок можно пропустить и все будет работать.

Но он приведён здесь для соответствия спецификации.

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

В этом и заключается преимущество стандартов...
///

## Посмотим как это работает

Откроем интерактивную документацию: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.

### Аутентификация

Нажмите кнопку "Авторизация".

Используйте учётные данные:

Пользователь: `johndoe`

Пароль: `secret`

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

После авторизации в системе вы увидите следующее:

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

### Получение собственных пользовательских данных

Теперь, используя операцию `GET` с путем `/users/me`, вы получите данные пользователя, например:

```JSON
{
  "username": "johndoe",
  "email": "johndoe@example.com",
  "full_name": "John Doe",
  "disabled": false,
  "hashed_password": "fakehashedsecret"
}
```

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

Если щелкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, то будет выдана ошибка HTTP 401:

```JSON
{
  "detail": "Not authenticated"
}
```

### Неактивный пользователь

Теперь попробуйте пройти аутентификацию с неактивным пользователем:

Пользователь: `alice`

Пароль: `secret2`

И попробуйте использовать операцию `GET` с путем `/users/me`.

Вы получите ошибку "Inactive user", как тут:

```JSON
{
  "detail": "Inactive user"
}
```

## Резюме

Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `имени пользователя` и `пароля` для вашего API.

Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных, с любым пользователем или моделью данных.

 Единственным недостатком нашей системы является то, что она всё ещё не защищена.

В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens">JWT</abbr>.
