# Einfaches OAuth2 mit Password und Bearer

Lassen Sie uns nun auf dem vorherigen Kapitel aufbauen und die fehlenden Teile hinzufügen, um einen vollständigen Sicherheits-Flow zu erhalten.

## `username` und `password` entgegennehmen

Wir werden **FastAPIs** Sicherheits-Werkzeuge verwenden, um den `username` und das `password` entgegenzunehmen.

OAuth2 spezifiziert, dass der Client/Benutzer bei Verwendung des „Password Flow“ (den wir verwenden) die Felder `username` und `password` als Formulardaten senden muss.

Und die Spezifikation sagt, dass die Felder so benannt werden müssen. `user-name` oder `email` würde also nicht funktionieren.

Aber keine Sorge, Sie können sie Ihren Endbenutzern im Frontend so anzeigen, wie Sie möchten.

Und Ihre Datenbankmodelle können beliebige andere Namen verwenden.

Aber für die Login-*Pfadoperation* müssen wir diese Namen verwenden, um mit der Spezifikation kompatibel zu sein (und beispielsweise das integrierte API-Dokumentationssystem verwenden zu können).

Die Spezifikation besagt auch, dass `username` und `password` als Formulardaten gesendet werden müssen (hier also kein JSON).

### <abbr title="Geltungsbereich">`scope`</abbr>

Ferner sagt die Spezifikation, dass der Client ein weiteres Formularfeld "`scope`" („Geltungsbereich“) senden kann.

Der Name des Formularfelds lautet `scope` (im Singular), tatsächlich handelt es sich jedoch um einen langen String mit durch Leerzeichen getrennten „Scopes“.

Jeder „Scope“ ist nur ein String (ohne Leerzeichen).

Diese werden normalerweise verwendet, um bestimmte Sicherheitsberechtigungen zu deklarieren, zum Beispiel:

* `users:read` oder `users:write` sind gängige Beispiele.
* `instagram_basic` wird von Facebook / Instagram verwendet.
* `https://www.googleapis.com/auth/drive` wird von Google verwendet.

!!! info
    In OAuth2 ist ein „Scope“ nur ein String, der eine bestimmte erforderliche Berechtigung deklariert.

    Es spielt keine Rolle, ob er andere Zeichen wie `:` enthält oder ob es eine URL ist.

    Diese Details sind implementierungsspezifisch.

    Für OAuth2 sind es einfach nur Strings.

## Code, um `username` und `password` entgegenzunehmen.

Lassen Sie uns nun die von **FastAPI** bereitgestellten Werkzeuge verwenden, um das zu erledigen.

### `OAuth2PasswordRequestForm`

Importieren Sie zunächst `OAuth2PasswordRequestForm` und verwenden Sie es als Abhängigkeit mit `Depends` in der *Pfadoperation* für `/token`:

=== "Python 3.10+"

    ```Python hl_lines="4  78"
    {!> ../../../docs_src/security/tutorial003_an_py310.py!}
    ```

=== "Python 3.9+"

    ```Python hl_lines="4  78"
    {!> ../../../docs_src/security/tutorial003_an_py39.py!}
    ```

=== "Python 3.8+"

    ```Python hl_lines="4  79"
    {!> ../../../docs_src/security/tutorial003_an.py!}
    ```

=== "Python 3.10+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="2  74"
    {!> ../../../docs_src/security/tutorial003_py310.py!}
    ```

=== "Python 3.8+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="4  76"
    {!> ../../../docs_src/security/tutorial003.py!}
    ```

`OAuth2PasswordRequestForm` ist eine Klassenabhängigkeit, die einen Formularbody deklariert mit:

* Dem `username`.
* Dem `password`.
* Einem optionalen `scope`-Feld als langem String, bestehend aus durch Leerzeichen getrennten Strings.
* Einem optionalen `grant_type` („Art der Anmeldung“).

!!! tip "Tipp"
    Die OAuth2-Spezifikation *erfordert* tatsächlich ein Feld `grant_type` mit dem festen Wert `password`, aber `OAuth2PasswordRequestForm` erzwingt dies nicht.

    Wenn Sie es erzwingen müssen, verwenden Sie `OAuth2PasswordRequestFormStrict` anstelle von `OAuth2PasswordRequestForm`.

* Eine optionale `client_id` (benötigen wir für unser Beispiel nicht).
* Ein optionales `client_secret` (benötigen wir für unser Beispiel nicht).

!!! info
    `OAuth2PasswordRequestForm` ist keine spezielle Klasse für **FastAPI**, so wie `OAuth2PasswordBearer`.

    `OAuth2PasswordBearer` lässt **FastAPI** wissen, dass es sich um ein Sicherheitsschema handelt. Daher wird es auf diese Weise zu OpenAPI hinzugefügt.

    Aber `OAuth2PasswordRequestForm` ist nur eine Klassenabhängigkeit, die Sie selbst hätten schreiben können, oder Sie hätten `Form`ular-Parameter direkt deklarieren können.

    Da es sich jedoch um einen häufigen Anwendungsfall handelt, wird er zur Vereinfachung direkt von **FastAPI** bereitgestellt.

### Die Formulardaten verwenden

!!! tip "Tipp"
    Die Instanz der Klassenabhängigkeit `OAuth2PasswordRequestForm` verfügt, statt eines Attributs `scope` mit dem durch Leerzeichen getrennten langen String, über das Attribut `scopes` mit einer tatsächlichen Liste von Strings, einem für jeden gesendeten Scope.

    In diesem Beispiel verwenden wir keine `scopes`, aber die Funktionalität ist vorhanden, wenn Sie sie benötigen.

Rufen Sie nun die Benutzerdaten aus der (gefakten) Datenbank ab, für diesen `username` aus dem Formularfeld.

Wenn es keinen solchen Benutzer gibt, geben wir die Fehlermeldung „Incorrect username or password“ zurück.

Für den Fehler verwenden wir die Exception `HTTPException`:

=== "Python 3.10+"

    ```Python hl_lines="3  79-81"
    {!> ../../../docs_src/security/tutorial003_an_py310.py!}
    ```

=== "Python 3.9+"

    ```Python hl_lines="3  79-81"
    {!> ../../../docs_src/security/tutorial003_an_py39.py!}
    ```

=== "Python 3.8+"

    ```Python hl_lines="3  80-82"
    {!> ../../../docs_src/security/tutorial003_an.py!}
    ```

=== "Python 3.10+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="1  75-77"
    {!> ../../../docs_src/security/tutorial003_py310.py!}
    ```

=== "Python 3.8+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="3  77-79"
    {!> ../../../docs_src/security/tutorial003.py!}
    ```

### Das Passwort überprüfen

Zu diesem Zeitpunkt liegen uns die Benutzerdaten aus unserer Datenbank vor, das Passwort haben wir jedoch noch nicht überprüft.

Lassen Sie uns diese Daten zunächst in das Pydantic-Modell `UserInDB` einfügen.

Sie sollten niemals Klartext-Passwörter speichern, daher verwenden wir ein (gefaktes) Passwort-Hashing-System.

Wenn die Passwörter nicht übereinstimmen, geben wir denselben Fehler zurück.

#### Passwort-Hashing

„Hashing“ bedeutet: Konvertieren eines Inhalts (in diesem Fall eines Passworts) in eine Folge von Bytes (ein schlichter String), die wie Kauderwelsch aussieht.

Immer wenn Sie genau den gleichen Inhalt (genau das gleiche Passwort) übergeben, erhalten Sie genau den gleichen Kauderwelsch.

Sie können jedoch nicht vom Kauderwelsch zurück zum Passwort konvertieren.

##### Warum Passwort-Hashing verwenden?

Wenn Ihre Datenbank gestohlen wird, hat der Dieb nicht die Klartext-Passwörter Ihrer Benutzer, sondern nur die Hashes.

Der Dieb kann also nicht versuchen, die gleichen Passwörter in einem anderen System zu verwenden (da viele Benutzer überall das gleiche Passwort verwenden, wäre dies gefährlich).

=== "Python 3.10+"

    ```Python hl_lines="82-85"
    {!> ../../../docs_src/security/tutorial003_an_py310.py!}
    ```

=== "Python 3.9+"

    ```Python hl_lines="82-85"
    {!> ../../../docs_src/security/tutorial003_an_py39.py!}
    ```

=== "Python 3.8+"

    ```Python hl_lines="83-86"
    {!> ../../../docs_src/security/tutorial003_an.py!}
    ```

=== "Python 3.10+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="78-81"
    {!> ../../../docs_src/security/tutorial003_py310.py!}
    ```

=== "Python 3.8+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="80-83"
    {!> ../../../docs_src/security/tutorial003.py!}
    ```

#### Über `**user_dict`

`UserInDB(**user_dict)` bedeutet:

*Übergib die Schlüssel und Werte des `user_dict` direkt als Schlüssel-Wert-Argumente, äquivalent zu:*

```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
    Eine ausführlichere Erklärung von `**user_dict` finden Sie in [der Dokumentation für **Extra Modelle**](../extra-models.md#uber-user_indict){.internal-link target=_blank}.

## Den Token zurückgeben

Die Response des `token`-Endpunkts muss ein JSON-Objekt sein.

Es sollte einen `token_type` haben. Da wir in unserem Fall „Bearer“-Token verwenden, sollte der Token-Typ "`bearer`" sein.

Und es sollte einen `access_token` haben, mit einem String, der unseren Zugriffstoken enthält.

In diesem einfachen Beispiel gehen wir einfach völlig unsicher vor und geben denselben `username` wie der Token zurück.

!!! tip "Tipp"
    Im nächsten Kapitel sehen Sie eine wirklich sichere Implementierung mit Passwort-Hashing und <abbr title="JSON Web Tokens">JWT</abbr>-Tokens.

    Aber konzentrieren wir uns zunächst auf die spezifischen Details, die wir benötigen.

=== "Python 3.10+"

    ```Python hl_lines="87"
    {!> ../../../docs_src/security/tutorial003_an_py310.py!}
    ```

=== "Python 3.9+"

    ```Python hl_lines="87"
    {!> ../../../docs_src/security/tutorial003_an_py39.py!}
    ```

=== "Python 3.8+"

    ```Python hl_lines="88"
    {!> ../../../docs_src/security/tutorial003_an.py!}
    ```

=== "Python 3.10+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="83"
    {!> ../../../docs_src/security/tutorial003_py310.py!}
    ```

=== "Python 3.8+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="85"
    {!> ../../../docs_src/security/tutorial003.py!}
    ```

!!! tip "Tipp"
    Gemäß der Spezifikation sollten Sie ein JSON mit einem `access_token` und einem `token_type` zurückgeben, genau wie in diesem Beispiel.

    Das müssen Sie selbst in Ihrem Code tun und sicherstellen, dass Sie diese JSON-Schlüssel verwenden.

    Es ist fast das Einzige, woran Sie denken müssen, es selbst richtigzumachen und die Spezifikationen einzuhalten.

    Den Rest erledigt **FastAPI** für Sie.

## Die Abhängigkeiten aktualisieren

Jetzt werden wir unsere Abhängigkeiten aktualisieren.

Wir möchten den `current_user` *nur* erhalten, wenn dieser Benutzer aktiv ist.

Daher erstellen wir eine zusätzliche Abhängigkeit `get_current_active_user`, die wiederum `get_current_user` als Abhängigkeit verwendet.

Beide Abhängigkeiten geben nur dann einen HTTP-Error zurück, wenn der Benutzer nicht existiert oder inaktiv ist.

In unserem Endpunkt erhalten wir also nur dann einen Benutzer, wenn der Benutzer existiert, korrekt authentifiziert wurde und aktiv ist:

=== "Python 3.10+"

    ```Python hl_lines="58-66  69-74  94"
    {!> ../../../docs_src/security/tutorial003_an_py310.py!}
    ```

=== "Python 3.9+"

    ```Python hl_lines="58-66  69-74  94"
    {!> ../../../docs_src/security/tutorial003_an_py39.py!}
    ```

=== "Python 3.8+"

    ```Python hl_lines="59-67  70-75  95"
    {!> ../../../docs_src/security/tutorial003_an.py!}
    ```

=== "Python 3.10+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="56-64  67-70  88"
    {!> ../../../docs_src/security/tutorial003_py310.py!}
    ```

=== "Python 3.8+ nicht annotiert"

    !!! tip "Tipp"
        Bevorzugen Sie die `Annotated`-Version, falls möglich.

    ```Python hl_lines="58-66  69-72  90"
    {!> ../../../docs_src/security/tutorial003.py!}
    ```

!!! info
    Der zusätzliche Header `WWW-Authenticate` mit dem Wert `Bearer`, den wir hier zurückgeben, ist ebenfalls Teil der Spezifikation.

    Jeder HTTP-(Fehler-)Statuscode 401 „UNAUTHORIZED“ soll auch einen `WWW-Authenticate`-Header zurückgeben.

    Im Fall von Bearer-Tokens (in unserem Fall) sollte der Wert dieses Headers `Bearer` lauten.

    Sie können diesen zusätzlichen Header tatsächlich weglassen und es würde trotzdem funktionieren.

    Aber er wird hier bereitgestellt, um den Spezifikationen zu entsprechen.

    Außerdem gibt es möglicherweise Tools, die ihn erwarten und verwenden (jetzt oder in der Zukunft) und das könnte für Sie oder Ihre Benutzer jetzt oder in der Zukunft nützlich sein.

    Das ist der Vorteil von Standards ...

## Es in Aktion sehen

Öffnen Sie die interaktive Dokumentation: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.

### Authentifizieren

Klicken Sie auf den Button „Authorize“.

Verwenden Sie die Anmeldedaten:

Benutzer: `johndoe`

Passwort: `secret`.

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

Nach der Authentifizierung im System sehen Sie Folgendes:

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

### Die eigenen Benutzerdaten ansehen

Verwenden Sie nun die Operation `GET` mit dem Pfad `/users/me`.

Sie erhalten Ihre Benutzerdaten:

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

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

Wenn Sie auf das Schlosssymbol klicken und sich abmelden und dann den gleichen Vorgang nochmal versuchen, erhalten Sie einen HTTP 401 Error:

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

### Inaktiver Benutzer

Versuchen Sie es nun mit einem inaktiven Benutzer und authentisieren Sie sich mit:

Benutzer: `alice`.

Passwort: `secret2`.

Und versuchen Sie, die Operation `GET` mit dem Pfad `/users/me` zu verwenden.

Sie erhalten die Fehlermeldung „Inactive user“:

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

## Zusammenfassung

Sie verfügen jetzt über die Tools, um ein vollständiges Sicherheitssystem basierend auf `username` und `password` für Ihre API zu implementieren.

Mit diesen Tools können Sie das Sicherheitssystem mit jeder Datenbank und jedem Benutzer oder Datenmodell kompatibel machen.

Das einzige fehlende Detail ist, dass es noch nicht wirklich „sicher“ ist.

Im nächsten Kapitel erfahren Sie, wie Sie eine sichere Passwort-Hashing-Bibliothek und <abbr title="JSON Web Tokens">JWT</abbr>-Token verwenden.
