[PYTHON] [Fast API + Firebase] Construction d'un serveur API pour l'authentification au porteur

FastAPI est pratique. Je l'utilise personnellement comme un framework qui facilite la création d'un serveur API. Cette fois, nous ajouterons une fonction d'authentification à l'API Fast.

** Attention **: une configuration telle que l'installation de FastAPI et Firebase n'est pas mentionnée ici comme une condition préalable.

Certification du porteur

Je pense qu'il y a beaucoup de demande pour identifier et authentifier l'utilisateur qui a demandé le serveur et contrôler l'autorisation appropriée pour la ressource demandée. Ici, nous allons implémenter l'authentification au porteur, qui peut être facilement implémentée en l'ajoutant simplement à l'en-tête HTTP.

[Qiita] "À propos de l'authentification du porteur"

Peut être spécifié comme un schéma dans l'en-tête HTTP Authorization, tel que Authorization: Bearer <token>. Le format du jeton est spécifié dans le format token68.

Puisqu'il est difficile d'implémenter l'émission et la vérification du «token» par vous-même, nous utiliserons Firebase cette fois.

Composition globale

L'image complète de l'authentification du support à l'aide de Firebase est illustrée.

image.png

  1. Le client interroge le serveur API Connectez-vous à Firebase en tant qu'utilisateur approprié et obtenez le token
  2. Le client le lance au serveur API via HTTP avec token
  3. Le serveur demande à Firebase l'en-tête HTTP token pour le valider. Si la vérification réussit, l'identification / l'authentification de l'utilisateur est terminée.
  4. Le serveur répond à la demande de manière appropriée

Configuration du SDK Firebase Admin

Implémenter la fonctionnalité d'authentification à l'aide du SDK Firebase Admin

Préparation de la clé privée

Obtenez un compte Firebase à l'avance. Tout d'abord, ouvrez la console du projet (https://console.firebase.google.com/u/0/)

Ouvrez les paramètres à partir de l'icône d'engrenage en haut à droite image.png

Obtenez la clé privée sous forme de fichier JSON à partir du bouton en bas de l'onglet Comptes de service. Ici, enregistrez-le sous account_key.json. image.png

Préparation du SDK

$ pip install firebase_admin

la mise en oeuvre

Point de terminaison de l'API

Tout d'abord, préparez un point de terminaison simple et créez un serveur API minimum.

main.py


from fastapi import FastAPI

app = FastAPI()

@app.get("/api/")
async def hello():
    return {"msg":"Hello, this is API server"}

Configurons un serveur de test avec uvicorn

$ uvicorn main:app --port 8001 --reload

Frappons le serveur API à titre d'essai (le navigateur Web est également OK)

PS > curl http://localhost:8001/api
StatusCode        : 200
StatusDescription : OK
Content           : {"msg":"Hello, this is API server"}
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 35
                    Content-Type: application/json
                    Date: Wed, 18 Nov 2020 11:11:20 GMT
                    Server: uvicorn

                    {"msg":"Hello, this is API server"}

Ajouter une certification de porteur

user.py


from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi import Depends, HTTPException, status, Response
from firebase_admin import auth, credentials
import firebase_admin

cred = credentials.Certificate('./account_key.json')
firebase_admin.initialize_app(cred)

def get_user(res: Response, cred: HTTPAuthorizationCredentials=Depends(HTTPBearer(auto_error=False))):
    if cred is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Bearer authentication required",
            headers={'WWW-Authenticate': 'Bearer realm="auth_required"'},
        )
    try:
        decoded_token = auth.verify_id_token(cred.credentials)
    except Exception as err:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f"Invalid authentication credentials. {err}",
            headers={'WWW-Authenticate': 'Bearer error="invalid_token"'},
        )
    res.headers['WWW-Authenticate'] = 'Bearer realm="auth_required"'
    return decoded_token

Vous devez d'abord extraire le token de l'en-tête Authorization. FastAPI est pratique car vous pouvez également valider l'en-tête d'authentification de la demande.

[FastAPI] Query Parameters and String Validations
[Qiita] Acquisition de jetons d'authentification Firebase avec Python et vérification de jetons avec Fast API

Ensuite, vérifiez token. Le code ci-dessus définit ce qu'il faut faire si «token» n'existe pas et n'est pas valide. Le contenu de la réponse à l'erreur est conforme à la RFC 6750 qui définit l'authentification du support. Lancez simplement HTTPExeption et l'API rapide le récupérera et générera une réponse, donc c'est facile.

Documentation officielle de Firebase Firebase Admin Python SDK
[[Qiita] Pour implémenter un schéma de support conforme à la RFC 6750](https://qiita.com/uasi/items/cfb60588daa18c2ec6f5#rfc-6750-%E3%81%AB%E6%BA%96%E6 % 8B% A0% E3% 81% 97% E3% 81% 9F-porteur-% E3% 82% B9% E3% 82% AD% E3% 83% BC% E3% 83% A0% E3% 82% 92% E5% AE% 9F% E8% A3% 85% E3% 81% 99% E3% 82% 8B% E3% 81% AB% E3% 81% AF)

** Attention **: Avec HTTPBearer (auto_error = True) (par défaut), pour les requêtes sans token

PS > curl http://localhost:8001/api/me                            
curl : {"detail":"Not authenticated"}
PS > $error[0].exception
Le serveur distant a renvoyé une erreur: (403)Indisponible

Et Fast API générera une gestion des exceptions + réponse sans autorisation.

Point de terminaison d'API authentifié

Ajoutez un point de terminaison d'API qui nécessite une authentification utilisateur.

main.py


from fastapi import FastAPI, Depends
from user import get_user

app = FastAPI()

@app.get("/api/")
async def hello():
    return {"msg":"Hello, this is API server"} 


@app.get("/api/me")
async def hello_user(user = Depends(get_user)):
    return {"msg":"Hello, user","uid":user['uid']} 

uid est l'identifiant de l'utilisateur qui s'est connecté à Firebase, et peut identifier non seulement l'e-mail et le mot de passe, mais également les utilisateurs authentifiés par divers services tels que Twitter et Google.

tester

Frappons réellement l'API du client et voyons la réponse.

Obtenez token

Obtenir la clé API

Sélectionnez un projet dans Firebase Console et copiez-le dans Paramètres> Général. image.png

Frappez l'API REST

En supposant que l'utilisateur (e-mail et mot de passe) est déjà enregistré dans le projet

{
  "email":"[email protected]",
  "password":"your password",
  "returnSecureToken":true
}

[Firebase] Auth REST API - Documentation officielle

PS >  curl -Method Post -Body $Body -Headers @{"content-type"="application/json"} $URL
StatusCode        : 200
StatusDescription : OK
Content           : {
                      "kind": "identitytoolkit#VerifyPasswordResponse",
                      "localId": "OZzdeAtK4VM4OlHHbUXTY6YNr8C3",
                      "email": "[email protected]",
                      "displayName": "",
                      "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjNlNTQ...
RawContent        : HTTP/1.1 200 OK
                    Pragma: no-cache
                    Vary: X-Origin,Referer,Origin,Accept-Encoding
                    X-XSS-Protection: 0
                    X-Frame-Options: SAMEORIGIN
                    X-Content-Type-Options: nosniff
                    Alt-Svc: h3-Q050=":443"; ma=2592000...

La réponse est également JSON et utilise la valeur de idToken pour l'authentification du porteur.

Courir

Succès

PS > curl -Headers @{"Authorization"="Bearer ${token}"} http://localhost:8001/api/me
StatusCode        : 200                                                         
StatusDescription : OK                                                          
Content           : {"msg":"Hello, user","uid":"OZzdeAtK4VM4OlHHbUXTY6YNr8C3"}  
RawContent        : HTTP/1.1 200 OK                                                                 
                    Content-Length: 58                                                              
                    Content-Type: application/json                                                  
                    Date: Fri, 20 Nov 2020 15:28:18 GMT                                             
                    Server: uvicorn                                                                 
                    WWW-Authenticate: Bearer realm="auth_required"       
                                                                                                           
                    {"msg":"Hello, user","uid":... 

Exemple de token illégal

Essayez de jouer avec la valeur de token et passez-la

PS > curl -Headers @{"Authorization"="Bearer ${token}"} http://localhost:8001/api/me
curl : {"detail":"Invalid authentication credentials. Could not verify token signature."}

Exemple de défaut de «jeton»

PS > curl http://localhost:8001/api/me                                              
curl : {"detail":"Bearer authentication required"}

Recommended Posts

[Fast API + Firebase] Construction d'un serveur API pour l'authentification au porteur
[Note d'étude] Créez un serveur de tuiles vectorielles GeoJSON avec Fast API
Émission de jetons d'authentification Firebase en Python et validation de jetons avec Fast API
Créer un environnement pour Python intégré à Blender
Créer un serveur NFS sur Arch Linux
Créez un environnement d'API rapide avec docker-compose
Pour les débutants à créer un environnement Anaconda. (Note)
Implémenter l'autorisation personnalisée pour l'authentification Firebase dans Chalice
Obtenez un jeton d'accès pour l'API Pocket
J'ai essayé d'utiliser Firebase pour le serveur de cache de Django
Créez rapidement un serveur API avec Python + Falcon
Construisez un serveur API pour vérifier le fonctionnement de l'implémentation frontale avec python3 et Flask