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.
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.
Illustrates the whole picture of Bearer authentication using Firebase
token
token
token
to validate it.
If the verification is successful, the user identification / authentication is completed.Implement authentication function using Firebase Admin SDK
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
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
.
$ pip install firebase_admin
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"}
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.
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.
Let's actually hit the API from the client and see the response.
token
Select a project from Firebase Console and copy from Settings> General.
Assuming that the user (e-mail & password) is already registered in the project
POST
https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${API_KEY}
--Body: Information required for login written in JSON{
"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.
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":...
token
exampleTry 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."}
token
defectPS > curl http://localhost:8001/api/me
curl : {"detail":"Bearer authentication required"}
Recommended Posts