DEV Community

David Mezzetti for NeuML

Posted on • Originally published at neuml.hashnode.dev

API Authorization and Authentication

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]
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

Next, we'll generate a test token to use for this article.

import uuid
str(uuid.uuid5(uuid.NAMESPACE_DNS, "TokenTest"))
Enter fullscreen mode Exit fullscreen mode
'edd590d3-bfab-5425-8a85-79b01e3127ee'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 401 Unauthorized
date: Thu, 04 Jan 2024 15:08:38 GMT
server: uvicorn
content-length: 40
content-type: application/json
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 401 Unauthorized
date: Thu, 04 Jan 2024 15:08:38 GMT
server: uvicorn
content-length: 40
content-type: application/json
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode
[{"id":"4","text":"Maine man wins $1M from $25 lottery ticket","score":0.08329025655984879}]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
curl -X GET -I 'http://localhost:8000/search?query=feel+good+story&limit=1'
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
dHh0YWk6dGhlZW1iZWRkaW5nc2Ri
Enter fullscreen mode Exit fullscreen mode
curl -X GET 'http://localhost:8000/search?query=feel+good+story&limit=1' -H "Authorization: Basic dHh0YWk6dGhlZW1iZWRkaW5nc2Ri"
Enter fullscreen mode Exit fullscreen mode
[{"id":"4","text":"Maine man wins $1M from $25 lottery ticket","score":0.08329025655984879}]
Enter fullscreen mode Exit fullscreen mode

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)