[PYTHON] [Fast API + Firebase] Build an API server for Bearer authentication

FastAPI is convenient. I personally use it as a framework that makes it easy to build an API server. This time, we will add an authentication function to the Fast API.

** Caution **: Setup such as FastAPI and Firebase installation is not mentioned here as a prerequisite.

Bearer certification

I think there is a lot of demand to identify and authenticate the user who requested the server and control the appropriate permission for the requested resource. Here, we will implement Bearer authentication, which can be easily implemented by simply adding it to the HTTP header.

[Qiita] "About Bearer Authentication"

Can be specified as a scheme in the HTTP Authorization header and is specified as Authorization: Bearer <token>. The token format is specified in the token68 format.

Since it is difficult to implement the issuance and verification of token by yourself, we will use Firebase this time.

Overall composition

Illustrates the whole picture of Bearer authentication using Firebase

image.png

  1. The client queries the API server Log in to Firebase as the appropriate user and get the token
  2. The client throws it to the API server over HTTP with token
  3. The server queries Firebase for the HTTP header token to validate it. If the verification is successful, the user identification / authentication is completed.
  4. The server responds to the request properly

Setting up the Firebase Admin SDK

Implement authentication function using Firebase Admin SDK

Preparation of private key

Get a Firebase account in advance. First, open the project console (https://console.firebase.google.com/u/0/)

Open the settings from the gear icon in the upper right image.png

Get the private key as a JSON file from the button at the bottom of the Service Accounts tab. Here, save it as account_key.json. image.png

SDK preparation

$ pip install firebase_admin

Implementation

API endpoint

First, prepare a simple endpoint and build a minimum API server.

main.py


from fastapi import FastAPI

app = FastAPI()

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

Let's set up a test server with uvicorn

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

Let's hit the API server as a trial (Web browser is also 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"}

Add Bearer certification

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

First, you need to extract token from the Authorization header. FastAPI is convenient because you can also validate the authentication header of the request.

[FastAPI] Query Parameters and String Validations
[Qiita] Firebase Authentication token acquisition with Python and token verification with Fast API

Next, verify token. The above code defines what to do if the token does not exist and is invalid. The content of the response to the error conforms to RFC 6750, which defines Bearer authentication. Just throw HTTPExeption and the Fast API will pick it up and generate a response, so it's easy.

Official Firebase documentation Firebase Admin Python SDK
[[Qiita] To implement an RFC 6750 compliant Bearer scheme](https://qiita.com/uasi/items/cfb60588daa18c2ec6f5#rfc-6750-%E3%81%AB%E6%BA%96%E6 % 8B% A0% E3% 81% 97% E3% 81% 9F-bearer-% 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)

** Caution **: When HTTPBearer (auto_error = True) (default) is set, for requests without token

PS > curl http://localhost:8001/api/me                            
curl : {"detail":"Not authenticated"}
PS > $error[0].exception
Remote server returned an error: (403)Unavailable

And FastAPI will generate exception handling + response without permission.

Authenticated API endpoint

Add an API endpoint that requires user authentication.

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 is the identifier of the user who logged in to Firebase, and can identify not only e-mail & password but also users authenticated by various services such as Twitter and Google.

test

Let's actually hit the API from the client and see the response.

Get token

Get API key

Select a project from Firebase Console and copy from Settings> General. image.png

Hit the REST API

Assuming that the user (e-mail & password) is already registered in the project

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

[Firebase] Auth REST API-Official Documentation

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...

The response is also JSON and uses the value of idToken for Bearer authentication.

Run

Success story

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":... 

Illegal token example

Try to play with the value of token and pass it

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

Example of token defect

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

Recommended Posts

[Fast API + Firebase] Build an API server for Bearer authentication
[Study memo] Build GeoJSON vector tile server with Fast API
Firebase Authentication token issuance in Python and token verification with Fast API
Build an environment for Blender built-in Python
Build an NFS server on Arch Linux
Build a Fast API environment with docker-compose
For beginners to build an Anaconda environment. (Memo)
Implement Custom Authorizer for Firebase Authentication in Chalice
Get an access token for the Pocket API
I tried using firebase for Django's cache server
Create an API server quickly with Python + Falcon
Build API server for checking the operation of front implementation with python3 and Flask