# Простая авторизация OAuth2 с паролем и «Bearer» { #simple-oauth2-with-password-and-bearer }

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

## Получение `username` и `password` { #get-the-username-and-password }

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

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

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

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

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

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

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

### `scope` { #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 это просто строки.
///

## Код для получения `username` и `password` { #code-to-get-the-username-and-password }

Теперь воспользуемся утилитами, предоставляемыми **FastAPI**, чтобы обработать это.

### `OAuth2PasswordRequestForm` { #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` это не проверяет строго.

Если вам нужно это строгое требование, используйте `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
///

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

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

`OAuth2PasswordBearer` сообщает **FastAPI**, что это схема безопасности. Поэтому она добавляется в OpenAPI соответствующим образом.

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

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

### Использование данных формы { #use-the-form-data }

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

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

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

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

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

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

### Проверка пароля { #check-the-password }

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

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

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

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

#### Хеширование паролей { #password-hashing }

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

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

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

##### Зачем использовать хеширование паролей { #why-use-password-hashing }

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

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

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

#### Про `**user_dict` { #about-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-in-dict){.internal-link target=_blank}.
///

## Возврат токена { #return-the-token }

Ответ операции пути `/token` должен быть объектом JSON.

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

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

В этом простом примере мы намеренно поступим небезопасно и вернём тот же `username` в качестве токена.

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

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

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

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

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

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

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

## Обновление зависимостей { #update-the-dependencies }

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

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

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

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

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

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

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

Любой HTTP статус-код 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.

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

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

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

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

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

## Посмотрим, как это работает { #see-it-in-action }

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

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

Нажмите кнопку "Authorize".

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

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

Пароль: `secret`

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

После аутентификации вы увидите следующее:

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

### Получение собственных пользовательских данных { #get-your-own-user-data }

Теперь используйте операцию `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"
}
```

### Неактивный пользователь { #inactive-user }

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

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

Пароль: `secret2`

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

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

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

## Резюме { #recap }

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

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

Единственная деталь, которой не хватает, — система пока ещё не "защищена" по-настоящему.

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