txtai is an all-in-one embeddings database for semantic search, LLM orchestration and language model workflows. txtai can run in Python, with YAML configuration and through an API service.
The default API service implementation runs without any security. This may be OK for a local prototype or if it's run on a small internal network. But in most cases, additional security measures should be taken.
This article will demonstrate how to add authorization, authentication and middleware dependencies to a txtai API service.
Install dependencies
Install txtai
and all dependencies. Since this article uses the API, we need to install the api extras package.
# Install txtai
pip install txtai[api]
Create an API Service
For this example, we'll load an existing txtai index from the Hugging Face Hub.
cloud:
provider: huggingface-hub
container: neuml/txtai-intro
embeddings:
Next, we'll generate a test token to use for this article.
import uuid
str(uuid.uuid5(uuid.NAMESPACE_DNS, "TokenTest"))
'edd590d3-bfab-5425-8a85-79b01e3127ee'
txtai has a default API token authorization method built-in. We'll set a token and start the service.
It's important to note that this service is running via HTTP as this is only for demonstration purposes. HTTPS must be added either with a proxy service like NGINX or by passing a SSL cert to Uvicorn. See this link for more.
CONFIG=config.yml TOKEN=`echo -n 'edd590d3-bfab-5425-8a85-79b01e3127ee' | sha256sum | head -c 64` uvicorn "txtai.api:app" &> api.log &
sleep 60
Connect to Service
First, we'll try a request with no token to see what happens.
curl -X GET -I 'http://localhost:8000/search?query=feel+good+story&limit=1'
HTTP/1.1 401 Unauthorized
date: Thu, 04 Jan 2024 15:08:38 GMT
server: uvicorn
content-length: 40
content-type: application/json
As expected, we received a HTTP 401 saying the request is not authorized.
Now let's try an invalid token.
curl -X GET -I 'http://localhost:8000/search?query=feel+good+story&limit=1' -H 'Authorization: Bearer junk'
HTTP/1.1 401 Unauthorized
date: Thu, 04 Jan 2024 15:08:38 GMT
server: uvicorn
content-length: 40
content-type: application/json
Once again, the request is rejected.
Let's try again, this time passing a valid API token.
curl -X GET 'http://localhost:8000/search?query=feel+good+story&limit=1' -H 'Authorization: Bearer edd590d3-bfab-5425-8a85-79b01e3127ee'
[{"id":"4","text":"Maine man wins $1M from $25 lottery ticket","score":0.08329025655984879}]
This time we get search results!
Dependencies
Next, let's add a custom dependency to test out authentication. A dependency could integrate with external identity providers to validate user credentials such as OAuth, Active Directory, LDAP or another identity management service.
For this simple example, we'll validate user credentials using basic HTTP authentication. The code below checks if a specific username and password are provided. It's based on this FastAPI example.
import secrets
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
security = HTTPBasic()
class Authentication:
def __call__(self, credentials: HTTPBasicCredentials = Depends(security)):
user = credentials.username.encode("utf8")
validuser = secrets.compare_digest(user, b"txtai")
password = credentials.password.encode("utf8")
validpassword = secrets.compare_digest(password, b"theembeddingsdb")
if not (validuser and validpassword):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect user or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
Now let's restart the application and add this dependency in.
Once again, same note as above, this demonstration uses HTTP. Real use-cases must use HTTPS.
killall -9 uvicorn
CONFIG=config.yml DEPENDENCIES=authentication.Authentication uvicorn "txtai.api:app" &> api.log &
sleep 30
curl -X GET -I 'http://localhost:8000/search?query=feel+good+story&limit=1'
HTTP/1.1 401 Unauthorized
date: Thu, 04 Jan 2024 15:09:10 GMT
server: uvicorn
www-authenticate: Basic
content-length: 30
content-type: application/json
The request is rejected as expected. Next let's try an invalid username/password.
curl -X GET -I 'http://localhost:8000/search?query=feel+good+story&limit=1' -H "Authorization: Basic junk"
HTTP/1.1 401 Unauthorized
date: Thu, 04 Jan 2024 15:09:10 GMT
server: uvicorn
www-authenticate: Basic
content-length: 47
content-type: application/json
Once again, the request is rejected.
Now we'll add the expected username/password to the request. HTTP basic authentication simply concats the username-password separated by a colon and then base64 encodes it.
echo -n txtai:theembeddingsdb | base64
dHh0YWk6dGhlZW1iZWRkaW5nc2Ri
curl -X GET 'http://localhost:8000/search?query=feel+good+story&limit=1' -H "Authorization: Basic dHh0YWk6dGhlZW1iZWRkaW5nc2Ri"
[{"id":"4","text":"Maine man wins $1M from $25 lottery ticket","score":0.08329025655984879}]
Now that we have the correct username/password, a response is returned!
Wrapping up
This article introduced how to add authorization, authentication and middleware dependencies to a txtai API service. As noted multiple times, ensure that HTTPS is enabled when using this in production environments.
For more advanced authentication methods, check out the FastAPI security documentation.
Top comments (0)