# How NekoImageGallery Verifies Access Tokens and Admin Tokens

> Learn how NekoImageGallery verifies access tokens and admin tokens using FastAPI dependency injection comparing header values to configuration files for secure access control.

- Repository: [EdgeNeko/nekoimagegallery](https://github.com/hv0905/nekoimagegallery)
- Tags: how-to-guide
- Published: 2026-03-03

---

**NekoImageGallery verifies access tokens and admin tokens using FastAPI dependency injection by comparing incoming `x-access-token` and `x-admin-token` headers against plaintext configuration values in [`app/config.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/config.py), automatically returning HTTP 401 errors when protection modes are enabled and credentials are invalid.**

NekoImageGallery implements a lightweight, configuration-driven authentication system designed for self-hosted image galleries. The repository `hv0905/nekoimagegallery` stores token values and enablement flags in a centralized config module, then validates requests through reusable FastAPI dependency functions. This approach allows administrators to selectively protect search endpoints and administrative APIs using simple environment variables without complex database schemas or external identity providers.

## Configuration-Driven Token Storage

The authentication system reads runtime settings from [`app/config.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/config.py), where boolean flags and token strings define the security posture:

- `access_protected`: Enables or disables token checking for normal users
- `access_token`: Expected string value for the `x-access-token` header
- `admin_api_enable`: Toggles the administrative API protection layer
- `admin_token`: Expected string value for the `x-admin-token` header

These values initialize from environment variables or Docker secrets (lines 90-98 in [`app/config.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/config.py)), allowing containerized deployments to inject credentials securely without hardcoding secrets into the source.

## Core Verification Logic in app/Services/authentication.py

The [`app/Services/authentication.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/authentication.py) module implements two verification layers: **permissive checks** that return boolean results for status reporting, and **force functions** that raise HTTP exceptions to block invalid requests.

### Access Token Verification Pipeline

The `verify_access_token()` function implements the core comparison logic:

```python
def verify_access_token(token: str | None) -> bool:
    return (not config.access_protected) or (token is not None and token == config.access_token)

```

When `access_protected` is disabled, all requests pass automatically. Otherwise, the function requires both header presence and exact string equality against the configured value.

The `permissive_access_token_verify()` function wraps this logic as a FastAPI dependency, extracting the header automatically using type annotations:

```python
def permissive_access_token_verify(
        x_access_token: Annotated[str | None, Header(
            description="Access token set in configuration (if access_protected is enabled)")]=None) -> bool:
    return verify_access_token(x_access_token)

```

Finally, `force_access_token_verify()` converts the boolean result into an enforced barrier that interrupts request processing:

```python
def force_access_token_verify(token_passed: Annotated[bool, Depends(permissive_access_token_verify)]):
    if not token_passed:
        raise HTTPException(status_code=401, detail="Access token is not present or invalid.")

```

### Admin Token Verification Pipeline

Admin tokens follow an identical pattern but additionally check the `admin_api_enable` flag. The `permissive_admin_token_verify()` function requires both the flag to be true and header equality:

```python
def permissive_admin_token_verify(
        x_admin_token: Annotated[str | None, Header(
            description="Admin token set in configuration (if admin_api_enable is enabled)")]=None) -> bool:
    return config.admin_api_enable and x_admin_token == config.admin_token

```

The `force_admin_token_verify()` dependency raises HTTP 401 when the admin token check fails:

```python
def force_admin_token_verify(token_passed: Annotated[bool, Depends(permissive_admin_token_verify)]):
    if not token_passed:
        raise HTTPException(status_code=401, detail="Admin token is not present or invalid.")

```

## Route-Level Enforcement in FastAPI Routers

Protected endpoints declare these force functions as router dependencies. The system applies verification before any business logic executes, leveraging FastAPI's dependency injection system.

In [`app/Controllers/search.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Controllers/search.py), the search router conditionally applies access protection based on the configuration flag:

```python
search_router = APIRouter(
    dependencies=([Depends(force_access_token_verify)] if config.access_protected else None)
)

```

In [`app/Controllers/admin.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Controllers/admin.py), the admin router unconditionally requires the admin token when the API is enabled:

```python
admin_router = APIRouter(
    dependencies=[Depends(force_admin_token_verify)], tags=["Admin"]
)

```

When a request arrives without valid headers, FastAPI automatically returns a 401 Unauthorized response before the endpoint handler executes.

## Practical Implementation Examples

### Creating a Protected Custom Endpoint

To protect a new route, import the force verification function and declare it as a router dependency:

```python
from fastapi import APIRouter, Depends
from app.Services.authentication import force_access_token_verify

router = APIRouter(
    prefix="/private",
    dependencies=[Depends(force_access_token_verify)],
)

@router.get("/data")
def get_private_data():
    return {"msg": "You have a valid access token!"}

```

### Testing Token Validation with HTTP Clients

Clients must include the correct header value configured in the environment:

```python
import httpx

BASE = "http://localhost:8000"
headers = {"x-access-token": "secret123"}

resp = httpx.get(f"{BASE}/private/data", headers=headers)
print(resp.json())  # {"msg": "You have a valid access token!"}

```

Requests missing the header or providing incorrect values receive HTTP 401 errors immediately.

### Enabling Protection via Environment Variables

Configure the application at runtime using environment variables before starting Uvicorn:

```bash
export APP_ACCESS_PROTECTED=true
export APP_ACCESS_TOKEN=secret123
export APP_ADMIN_API_ENABLE=true
export APP_ADMIN_TOKEN=admin987
uvicorn app.webapp:app

```

With these settings, search endpoints require `x-access-token: secret123` and admin endpoints require `x-admin-token: admin987`.

## Summary

- NekoImageGallery stores authentication settings in [`app/config.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/config.py) with boolean enablement flags and plaintext token values that initialize from environment variables.
- The [`app/Services/authentication.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/authentication.py) module provides `verify_access_token()` and `permissive_*` functions that compare incoming headers against configuration using exact string equality.
- `force_access_token_verify()` and `force_admin_token_verify()` raise `HTTPException` with status 401 when validation fails, blocking access to protected routes.
- Routers in [`app/Controllers/search.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Controllers/search.py) and [`app/Controllers/admin.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Controllers/admin.py) declare these dependencies to enforce protection at the route level before handlers execute.
- The home endpoint in [`app/webapp.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/webapp.py) uses permissive checks to report authentication status without blocking requests, enabling health checks while confirming token recognition.

## Frequently Asked Questions

### What happens when access_protected is set to false?

When `access_protected` is disabled in [`app/config.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/config.py), the `verify_access_token()` function immediately returns `True` for all requests regardless of headers, and the search router skips the dependency injection entirely. This allows completely open access to the image search API without requiring authentication headers.

### Can the authentication system handle multiple valid tokens or rotating credentials?

The current implementation in [`app/Services/authentication.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/authentication.py) only supports single static tokens via direct string equality comparison (`token == config.access_token`). The system does not implement token lists, expiration dates, or automatic rotation; changing credentials requires updating the `APP_ACCESS_TOKEN` or `APP_ADMIN_TOKEN` environment variables and restarting the application.

### How does the system differentiate between regular users and administrators?

Regular users authenticate via the `x-access-token` header validated by `force_access_token_verify()`, while administrators use the separate `x-admin-token` header processed by `force_admin_token_verify()`. The admin verification additionally requires the `admin_api_enable` flag to be true, and the two token systems operate independently with different configuration values, allowing separate protection levels for public search and administrative functions.

### Why does the home endpoint use permissive verification instead of force verification?

The root route (`/`) in [`app/webapp.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/webapp.py) intentionally uses `permissive_access_token_verify` and `permissive_admin_token_verify` to allow unauthenticated status checks while reporting whether valid tokens were present in the request. This design enables monitoring tools and status pages to test connectivity without credentials, while still confirming that the authentication system correctly recognizes valid tokens when they are provided.