# How Presigned URL Generation Works for Image Retrieval in NekoImageGallery

> Learn how NekoImageGallery generates presigned URLs for secure image retrieval. Download images directly from storage without exposing credentials or routing traffic.

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

---

**NekoImageGallery generates temporary, cryptographically-signed URLs that allow clients to download images directly from storage backends without exposing credentials or routing traffic through the application server.**

The repository implements a storage-agnostic abstraction layer that supports both local filesystems and S3-compatible object stores. When the search API returns image metadata, it replaces static file paths with time-limited presigned URLs, enabling secure direct-to-client content delivery regardless of where binaries are physically stored.

## BaseStorage Contract and Interface Design

Every storage provider in NekoImageGallery implements the abstract `BaseStorage` class defined in [`app/Services/storage/base.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/storage/base.py) (lines 55‑63). This contract declares the asynchronous coroutine `presign_url(remote_file: RemoteFilePathType, expire_seconds: int = 3600) -> str`, which subclasses must override to return a client-accessible URL valid for the specified duration.

## The Request Workflow

### 1. Controller-Level Image Processing

When processing search results in [`app/Controllers/search.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Controllers/search.py) (lines 52‑56), the application iterates over image records and identifies entries stored remotely by checking `item.img.local is False`. For each qualifying image, it invokes the storage service to obtain a presigned URL:

```python
item.img.url = await services.storage_service.active_storage.presign_url(
    img_remote_filename
)

```

The same pattern applies to thumbnail generation, ensuring both full-resolution images and previews receive distinct, expiring access tokens.

### 2. Storage Backend Selection

`StorageService` in [`app/Services/storage/__init__.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/storage/__init__.py) instantiates the concrete implementation based on `config.storage.method.enabled` and `config.storage.method.type`. The factory returns either `LocalStorage` for on-disk files or `S3CompatibleStorage` for remote object stores, while exposing a unified `presign_url()` method to controllers.

### 3. Local Storage Implementation

For deployments using the filesystem backend, `LocalStorage.presign_url()` in [`app/Services/storage/local_storage.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/storage/local_storage.py) (lines 76‑80) returns a static path served directly by FastAPI’s static-files middleware:

```python
return f"/static/{str(remote_file)}"

```

No cryptographic signing occurs because the web server handles authorization and delivery internally.

### 4. S3-Compatible Storage Implementation

Remote storage leverages the **opendal** library to generate AWS Signature Version 4 presigned requests. In [`app/Services/storage/s3_compatible_storage.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/storage/s3_compatible_storage.py) (lines 93‑98), the `presign_url()` method calls the underlying operator to create a temporary read URL:

```python
presign = await self.op.presign_read(self._file_path_wrap(remote_file), expire_seconds)
return self._rewrite_s3_presign_url(presign.url)

```

### URL Rewriting and Expiration Control

The optional `_rewrite_s3_presign_url()` method (lines 71‑84) replaces the internal S3 endpoint with a user-facing domain when `config.storage.s3.user_endpoint_url` is configured, ensuring clients receive cleaner URLs while the signature remains valid for the underlying bucket.

Callers control link lifetime via the `expire_seconds` parameter, which propagates to the storage SDK. The default **3600 seconds** (one hour) balances security with client caching needs, after which the URL automatically becomes invalid.

## Summary

- **Abstract Interface**: `BaseStorage.presign_url()` in [`app/Services/storage/base.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/storage/base.py) defines a uniform contract for temporary URL generation across all storage providers.
- **Dual Backends**: Local storage returns static FastAPI paths, while S3-compatible stores produce cryptographically signed URLs via opendal.
- **Configurable Lifetimes**: The `expire_seconds` parameter (default 3600) lets API consumers tune how long direct-access links remain valid.
- **Transparent Injection**: The search controller in [`app/Controllers/search.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Controllers/search.py) automatically hydrates `item.img.url` and `item.img.thumbnail_url` fields with presigned values before serializing responses.

## Frequently Asked Questions

### How long do presigned URLs remain valid?

By default, URLs expire after **3600 seconds** (one hour). You can adjust this window by passing a custom `expire_seconds` value to the `presign_url()` method, which propagates to the underlying opendal presign_read operation or remains ignored for local storage paths.

### What storage backends support presigned URLs?

NekoImageGallery supports two implementations: `LocalStorage` for filesystem assets and `S3CompatibleStorage` for any S3-compatible object store. Both implement the `presign_url()` contract defined in [`app/Services/storage/base.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/storage/base.py), ensuring controllers remain agnostic to the underlying provider.

### Can I customize the URL endpoint for S3-compatible storage?

Yes. When `config.storage.s3.user_endpoint_url` is set, the `_rewrite_s3_presign_url()` method in [`app/Services/storage/s3_compatible_storage.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Services/storage/s3_compatible_storage.py) (lines 71‑84) rewrites the presigned URL’s domain before returning it to clients, allowing you to front private buckets with public CDN endpoints or custom domains.

### How does the application handle thumbnail generation?

Thumbnails follow the same presigning pipeline as full images. In [`app/Controllers/search.py`](https://github.com/hv0905/nekoimagegallery/blob/main/app/Controllers/search.py), the controller checks thumbnail storage status and calls `presign_url()` separately for thumbnail files, ensuring smaller preview images receive their own time-limited access tokens rather than reusing the full-image credentials.