Skip to main content

Add your description here

This project has been archived.

The maintainers of this project have marked this project as archived. No new releases are expected.

Project description

Fastapi Api Key

Python Version from PEP 621 TOML Tested with pytest Coverage Code style: Ruff Security: bandit Deps: uv Commitizen friendly

fastapi-api-key provides reusable building blocks to issue, persist, and verify API keys in FastAPI applications. It ships with a domain model, hashing helpers, repository contracts, and an optional FastAPI router for CRUD management of keys.

Features

  • Security-first: secrets are hashed with a salt and a pepper, and never logged or returned after creation

  • Ready-to-use: just create your repository (storage) and use service

  • Prod-ready: services and repositories are async, and battle-tested

  • Agnostic hasher: you can use any async-compatible hashing strategy (default: Argon2)

  • Agnostic backend: you can use any async-compatible database (default: SQLAlchemy)

  • Factory: create a Typer, FastAPI router wired to api key systems (only SQLAlchemy for now)

Installation

This project is not published to PyPI. Use a tool like uv to manage dependencies.

uv add git+https://github.com/Athroniaeth/fastapi-api-key
uv pip install git+https://github.com/Athroniaeth/fastapi-api-key

Development installation

Clone the repository and install the project with the extras that fit your stack. Examples below use uv:

uv sync --extra all  # fastapi + sqlalchemy + argon2 + bcrypt
uv pip install -e ".[all]"

For lighter setups you can choose individual extras:

Installation mode Command Description
Base installation fastapi-api-key Installs the core package without any optional dependencies.
With bcrypt support fastapi-api-key[bcrypt] Adds support for password hashing using bcrypt (bcrypt>=5.0.0).
With Argon2 support fastapi-api-key[argon2] Adds support for password hashing using Argon2 (argon2-cffi>=25.1.0).
With SQLAlchemy support fastapi-api-key[sqlalchemy] Adds database integration via SQLAlchemy (sqlalchemy>=2.0.43).
Core setup fastapi-api-key[core] Installs the core dependencies (SQLAlchemy + Argon2 + bcrypt).
FastAPI only fastapi-api-key[fastapi] Installs FastAPI as an optional dependency (fastapi>=0.118.0).
Full installation fastapi-api-key[all] Installs all optional dependencies: FastAPI, SQLAlchemy, Argon2, and bcrypt.
uv add git+https://github.com/Athroniaeth/fastapi-api-key[sqlalchemy]
uv pip install git+https://github.com/Athroniaeth/fastapi-api-key[sqlalchemy]
uv sync --extra sqlalchemy
uv pip install -e ".[sqlalchemy]"

Development dependencies (pytest, ruff, etc.) are available under the dev group:

uv sync --extra dev
uv pip install -e ".[dev]"

Quick start

Use the service with an in-memory repository

import asyncio

from fastapi_api_key import ApiKeyService, ApiKey
from fastapi_api_key.repositories.in_memory import InMemoryApiKeyRepository


async def main():
    repo = InMemoryApiKeyRepository()
    service = ApiKeyService(repo=repo)  # default hasher is Argon2 with a default pepper (to be changed in prod)
    entity = ApiKey(name="docs")

    entity, api_key = await service.create(entity)
    print("Give this secret to the client:", api_key)

    verified = await service.verify_key(api_key)
    print("Verified key belongs to:", verified.id_)


asyncio.run(main())

Override the default pepper in production:

import os
from fastapi_api_key import ApiKeyService
from fastapi_api_key.hasher.argon2 import Argon2ApiKeyHasher
from fastapi_api_key.repositories.in_memory import InMemoryApiKeyRepository

pepper = os.environ["API_KEY_PEPPER"]
hasher = Argon2ApiKeyHasher(pepper=pepper)

repo = InMemoryApiKeyRepository()
service = ApiKeyService(
    repo=repo,
    hasher=hasher,
)

How API Keys Work

This is a classic API key if you don't modify the service behavior:

ak-7a74caa323a5410d-mAfP3l6yAxqFz0FV2LOhu2tPCqL66lQnj3Ubd08w9RyE4rV4skUcpiUVIfsKEbzw

  • "-" separators so that systems can easily split
  • Prefix ak (for "Api Key"), to identify the key type (useful to indicate that it is an API key).
  • 16 first characters are the identifier (UUIDv4 without dashes)
  • 48 last characters are the secret (random URL-safe base64 string)

When verifying an API key, the service extracts the identifier, retrieves the corresponding record from the repository, and compares the hashed secret. If found, it hashes the provided secret (with the same salt and pepper) and compares it to the stored hash. If they match, the key is valid.

Mount the FastAPI router

This example uses SQLAlchemy with FastAPI. It creates the database tables at startup if they do not exist.

import os
from contextlib import asynccontextmanager
from pathlib import Path
from typing import AsyncIterator

from fastapi import FastAPI, Depends, APIRouter
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase

from fastapi_api_key import ApiKey, ApiKeyService
from fastapi_api_key.api import create_api_keys_router, create_depends_api_key
from fastapi_api_key.hasher.argon2 import Argon2ApiKeyHasher
from fastapi_api_key.repositories.sql import SqlAlchemyApiKeyRepository, ApiKeyModelMixin


class Base(DeclarativeBase): ...


class ApiKeyModel(Base, ApiKeyModelMixin): ...


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
    # Create the database tables
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield


# Set env var to override default pepper
# Using a strong, unique pepper is crucial for security
# Default pepper is insecure and should not be used in production
pepper = os.getenv("API_KEY_PEPPER")
hasher = Argon2ApiKeyHasher(pepper=pepper)

path = Path(__file__).parent / "db.sqlite3"
database_url = os.environ.get("DATABASE_URL", f"sqlite+aiosqlite:///{path}")

async_engine = create_async_engine(database_url, future=True)
async_session_maker = async_sessionmaker(
    async_engine,
    class_=AsyncSession,
    expire_on_commit=False,
)

app = FastAPI(title="API with API Key Management", lifespan=lifespan)


async def async_session() -> AsyncIterator[AsyncSession]:
    """Dependency to provide an active SQLAlchemy async session."""
    async with async_session_maker() as session:
        async with session.begin():
            yield session


async def inject_svc_api_keys(async_session: AsyncSession = Depends(async_session)) -> ApiKeyService:
    """Dependency to inject the API key service with an active SQLAlchemy async session."""
    # No need to ensure table here, done in lifespan
    repo = SqlAlchemyApiKeyRepository(async_session)
    return ApiKeyService(repo=repo, hasher=hasher)


security = create_depends_api_key(inject_svc_api_keys)
router_protected = APIRouter(prefix="/protected", tags=["Protected"])

router = APIRouter(prefix="/api-keys", tags=["API Keys"])
router_api_keys = create_api_keys_router(
    inject_svc_api_keys,
    router=router,
)


@router_protected.get("/")
async def read_protected_data(api_key: ApiKey = Depends(security)):
    return {
        "message": "This is protected data",
        "apiKey": {
            "id": api_key.id_,
            "name": api_key.name,
            "description": api_key.description,
            "isActive": api_key.is_active,
            "createdAt": api_key.created_at,
            "expiresAt": api_key.expires_at,
            "lastUsedAt": api_key.last_used_at,
        },
    }


app.include_router(router_api_keys)
app.include_router(router_protected)

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="localhost", port=8000)

The router exposes:

  • POST /api-keys - create a key and return the plaintext secret once.
  • GET /api-keys - list keys with offset/limit pagination.
  • GET /api-keys/{id} - fetch a key by identifier.
  • PATCH /api-keys/{id} - update name, description, or active flag.
  • DELETE /api-keys/{id} - remove a key.

Contributing

Additional notes

  • Python 3.9+ is required.
  • The library issues warnings if you keep the default pepper; always configure a secret value outside source control.
  • Never log peppers or plaintext API keys, change the pepper of prod will prevent you from reading API keys

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_api_key-0.2.1.tar.gz (127.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_api_key-0.2.1-py3-none-any.whl (24.7 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_api_key-0.2.1.tar.gz.

File metadata

  • Download URL: fastapi_api_key-0.2.1.tar.gz
  • Upload date:
  • Size: 127.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.2

File hashes

Hashes for fastapi_api_key-0.2.1.tar.gz
Algorithm Hash digest
SHA256 bccd29ab5251cbf6dccc831c6051c584f3f16ba30431bd47966622ed7da04ceb
MD5 5a173962ceaae91a11be43d15c13d2bf
BLAKE2b-256 eb2ac273173a1fbf08b3f0b8adafa2a32335105f8e57dd26c3322e0b77d26635

See more details on using hashes here.

File details

Details for the file fastapi_api_key-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_api_key-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 104a9094be54aea696343d37d67870e8d51f255e7cf8cf5469bba3612a37ca26
MD5 c7d4f8002417a4725a5d7b6ac831967d
BLAKE2b-256 a56db8f4f3e73774e3720f2803164a381f5d3cfcea2076207a1885b71ca135ec

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page