fastapi-shield


Namefastapi-shield JSON
Version 0.1.4 PyPI version JSON
download
home_pageNone
SummaryA comprehensive FastAPI decorator library providing request validation, authentication, rate limiting, and security shields for API endpoints
upload_time2025-10-19 16:54:44
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords api async authentication decorator fastapi python3 rate-limiting security shield validation web
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <div align="center">

<img src="./assets/logos/logo_hori_one.jpg" width=80% height=20%></img>

# FastAPI Shield

## Documentation
<a href="https://docs.fastapi-shield.asyncmove.com">
  <img src="https://img.shields.io/badge/docs-passing-brightgreen.svg" width="100" alt="docs passing">
</a>

### Compatibility and Version
<img src="https://img.shields.io/badge/%3E=python-3.9-blue.svg" alt="Python compat">
<a href="https://pypi.python.org/pypi/fastapi-shield"><img src="https://img.shields.io/pypi/v/fastapi-shield.svg" alt="PyPi"></a>

### CI/CD
<a href="https://codecov.io/github/jymchng/fastapi-shield?branch=main"><img src="https://codecov.io/github/jymchng/fastapi-shield/coverage.svg?branch=main" alt="Coverage"></a>

### License and Issues
<a href="https://github.com/jymchng/fastapi-shield/blob/main/LICENSE"><img src="https://img.shields.io/github/license/jymchng/fastapi-shield" alt="License"></a>
<a href="https://github.com/jymchng/fastapi-shield/issues"><img src="https://img.shields.io/github/issues/jymchng/fastapi-shield" alt="Issues"></a>
<a href="https://github.com/jymchng/fastapi-shield/issues?q=is%3Aissue+is%3Aclosed"><img src="https://img.shields.io/github/issues-closed/jymchng/fastapi-shield" alt="Closed Issues"></a>
<a href="https://github.com/jymchng/fastapi-shield/issues?q=is%3Aissue+is%3Aopen"><img src="https://img.shields.io/github/issues/jymchng/fastapi-shield" alt="Open Issues"></a>

### Development and Quality
<a href="https://github.com/jymchng/fastapi-shield/network/members"><img src="https://img.shields.io/github/forks/jymchng/fastapi-shield" alt="Forks"></a>
<a href="https://github.com/jymchng/fastapi-shield/stargazers"><img src="https://img.shields.io/github/stars/jymchng/fastapi-shield" alt="Stars"></a>
<a href="https://pypi.python.org/pypi/fastapi-shield"><img src="https://img.shields.io/pypi/dm/fastapi-shield" alt="Downloads"></a>
<a href="https://github.com/jymchng/fastapi-shield/graphs/contributors"><img src="https://img.shields.io/github/contributors/jymchng/fastapi-shield" alt="Contributors"></a>
<a href="https://github.com/jymchng/fastapi-shield/commits/main"><img src="https://img.shields.io/github/commit-activity/m/jymchng/fastapi-shield" alt="Commits"></a>
<a href="https://github.com/jymchng/fastapi-shield/commits/main"><img src="https://img.shields.io/github/last-commit/jymchng/fastapi-shield" alt="Last Commit"></a>
<a href="https://github.com/jymchng/fastapi-shield"><img src="https://img.shields.io/github/languages/code-size/jymchng/fastapi-shield" alt="Code Size"></a>
<a href="https://github.com/jymchng/fastapi-shield"><img src="https://img.shields.io/github/repo-size/jymchng/fastapi-shield" alt="Repo Size"></a>
<a href="https://github.com/jymchng/fastapi-shield/watchers"><img src="https://img.shields.io/github/watchers/jymchng/fastapi-shield" alt="Watchers"></a>
<a href="https://github.com/jymchng/fastapi-shield"><img src="https://img.shields.io/github/commit-activity/y/jymchng/fastapi-shield" alt="Activity"></a>
<a href="https://github.com/jymchng/fastapi-shield/pulls"><img src="https://img.shields.io/github/issues-pr/jymchng/fastapi-shield" alt="PRs"></a>
<a href="https://github.com/jymchng/fastapi-shield/pulls?q=is%3Apr+is%3Aclosed"><img src="https://img.shields.io/github/issues-pr-closed/jymchng/fastapi-shield" alt="Merged PRs"></a>
<a href="https://github.com/jymchng/fastapi-shield/pulls?q=is%3Apr+is%3Aopen"><img src="https://img.shields.io/github/issues-pr/open/jymchng/fastapi-shield" alt="Open PRs"></a>

</div>

A powerful, intuitive, and flexible authentication and authorization library for FastAPI applications. Stack your shields to create robust and customizable layers which effectively shields your endpoints from unwanted requests.

# Features

- **Decorator-based Security**: Apply shields as simple decorators to protect your endpoints
- **Layered Protection**: Stack multiple shields for fine-grained access control
- **Clean Design Pattern**: Shields provide a clear and intuitive metaphor for API protection
- **Fully Integrated**: Works seamlessly with FastAPI's dependency injection system
- **Type Safety**: Full type hint support for better IDE integration and code quality
- **Customizable Responses**: Configure error responses when access is denied
- **ShieldedDepends**: Special dependency mechanism for protected resources
- **Lazy-Loading of Dependencies**: Dependencies are only loaded from FastAPI after the request passes through all the decorated shields

# Installation

With `pip`:
```bash
pip install fastapi-shield
```

With `uv`:
```bash
uv add fastapi-shield
```

With `poetry`:
```bash
poetry add fastapi-shield
```

# Basic Usage

## πŸ›‘οΈ Create your First Shield

Let's create a simple `@auth_shield` to shield against unauthenticated requests! πŸ›‘οΈ

```python
from fastapi import Header
from fastapi_shield import shield

# Create a simple authentication shield
@shield
def auth_shield(api_token: str = Header()):
    """
    A basic shield that validates an API token.
    Returns the token if valid, otherwise returns None which blocks the request.
    """
    if api_token in ("admin_token", "user_token"):
        return api_token
    return None
```

Now that you've created your first shield, you can easily apply it to any FastAPI endpoint! πŸš€

The shield acts as a decorator that protects your endpoint from unauthorized access. 

When a request comes in, the shield evaluates the API token before the endpoint function is called.

If the token is invalid (returning None), the request is blocked! 🚫 

This creates a clean separation between authentication logic and business logic, making your code more maintainable.

Just like a real shield, it stands in front of your endpoint to protect it! πŸ’ͺ


## See your First Shield in Action

Now let's see how our shield works in the wild! πŸš€ When a user tries to access the protected endpoint, the shield jumps into action like a superhero! πŸ¦Έβ€β™€οΈ

```python
from fastapi import FastAPI

app = FastAPI()

# Protected endpoint - requires authentication
@app.get("/protected/{name}")
@auth_shield # apply `@auth_shield`
async def protected_endpoint(name: str):
    return {
        "message": f"Hello {name}. This endpoint is protected!",
    }
```

```python
from fastapi.testclient import TestClient

client = TestClient(app)

def test_protected():
    client = TestClient(app)
    response = client.get("/protected/John", headers={"API-TOKEN": "valid_token"})
    assert response.status_code == 200
    assert response.json() == {"message": "Hello John. This endpoint is protected!"}


def test_protected_unauthorized():
    client = TestClient(app)
    response = client.get("/protected/John", headers={"API-TOKEN": "invalid_token"})
    assert response.status_code == 500
    assert response.json() == {'detail': 'Shield with name `unknown` blocks the request'}, response.json()
```

From the above, we can see how the shield works in practice. The `auth_shield` decorator is applied to our endpoint, checking the API token in the request headers before allowing access to the protected endpoint. When a valid token is provided, the request proceeds normally and returns a friendly greeting. However, when an invalid token is sent, the shield blocks the request, returning a 500 error with a message indicating that the shield has prevented access. This demonstrates the power of shields as a clean, declarative way to implement authentication in FastAPI applications without cluttering your endpoint logic with authorization checks.

<div align="center">
  <img src="./assets/pictures/IMG_20250423_003431_018.jpg" alt="Shield Congratulations" width="40%">
  
  ### πŸŽ‰ Congratulations! You've made your First Wonderful Shield! πŸŽ‰
</div>

## Your Second Shield! πŸ›‘οΈπŸ›‘οΈ

First, let's see what's the final endpoint is going to look like:

```python
@app.get("/products")
@auth_shield
@roles_shield(["user"])
async def get_all_products(db: Dict[str, Any]=Depends(get_db), username: str=ShieldedDepends(get_username_from_payload)):
    """Only user with role `user` can get their own product"""
    products = list(map(lambda name: db["products"][name], db["users"][username]["products"]))
    return {
        "message": f"These are your products: {products}",
    }
```

We're going to make the `@roles_shield(["user"])`.

But before that, there's one point to note: one of the advantages of `fastapi-shield` is that it enables lazy injection of FastAPI's dependencies.

In the signature of the endpoint: `async def get_all_products(db: Dict[str, Any]=Depends(get_db), username: str=ShieldedDepends(get_username_from_payload))`, the `db: Dict[str, Any]=Depends(get_db)` is only injected after `@roles_shield(["user"])` becomes 'unblocked', i.e. allowing the request to reach the endpoint `get_all_products`.

Prior to that, if the request is blocked by any of the decoratored shields, e.g. `@auth_shield` and `@roles_shield(["user"])`, then the FastAPI's dependencies are not injected. We will discuss this in more detail later, on why this is an advantage compared to other decorators-based libraries.

Let's see how the `@roles_shield(["user"])` is written.

```python
# Create a simple roles shield
def roles_shield(roles: list[str]):
    """
    A shield that validates a list of roles.
    """
    
    @shield
    def wrapper(payload = ShieldedDepends(get_payload_from_token)):
        if any(role in payload["roles"] for role in roles):
            return payload
        return None
    
    return wrapper
```

The `roles_shield` function is a shield factory that creates a shield for role-based access control. It takes a list of roles as input and returns a shield function. 

The inner `wrapper` function of `roles_shield` uses `ShieldedDepends` to get the payload from the token, which contains user information including their roles.

The shield then checks if any of the user's roles match the required roles using the `any()` function with a generator expression. 

If there's a match, it returns the payload, allowing the request to proceed. If no matching role is found, it returns `None`, which blocks the request. 

This pattern demonstrates how shields can be composed and parameterized to create flexible authorization rules. 

### What is `ShieldedDepends`?

`ShieldedDepends` is a mechanism to pass information from a `Shield` to a dependency to retrieve it.

It is a specialized dependency injection class that extends FastAPI's `Security` class. `ShieldedDepends` is used to inject dependencies that should only be resolved if a shield allows the request to proceed. It takes a callable function as its `shielded_dependency` parameter.

The `shielded_dependency` function:
1. Can be synchronous or asynchronous (coroutine)
2. Should accept parameters that can be resolved by FastAPI's dependency injection system
3. Will only be executed when the shield is "unblocked" (when the shield function returns a truthy value)
4. Can access request information and other dependencies just like regular FastAPI dependencies

### How `ShieldedDepends` Passes Data Between Shields

One powerful feature of `ShieldedDepends` is its ability to pass data between shields in a decorator chain, from top decorator to the lower ones.

When a shield returns a value (other than `None`), that value can be captured and used by subsequent shields, via the `ShieldedDepends` dependencies specified in the function signature of the shield, or by the endpoint function itself via the same mechanism.

When a shield is blocked, the `ShieldedDepends` instance returns itself instead of executing the dependency function, preventing unnecessary computation and database access. This lazy evaluation is one of the key advantages of the `fastapi-shield` library.

For example, in our `get_payload_from_token` function (which is an argument to be passed into `ShieldedDepends` function; seen on the line `def wrapper(payload = ShieldedDepends(get_payload_from_token)): ...`):

```python
def get_payload_from_token(token: str):
    if token == "admin_token":
        return {"username": "Peter", "roles": ["admin", "user"]}
    elif token == "user_token":
        return {"username": "John", "roles": ["user"]}
    return None
```

The `token` parameter will be passed by the guard function of the `shield` decorating it.

```python
@shield
def auth_shield(api_token: str = Header()):
    """
    A basic shield that validates an API token.
    Returns the token if valid, otherwise returns None which blocks the request.
    """
    if api_token in ("admin_token", "user_token"):
        return api_token
    return None
```

The guard function of `auth_shield` returns the `api_token` which is then passed into `get_payload_from_token` shielded dependency function of the `roles_shield`.

```python
@app.get("/products")
@auth_shield
@roles_shield(["user"])
async def get_all_products(db: Dict[str, Any]=Depends(get_db), username: str=ShieldedDepends(get_username_from_payload)):
    """Only user with role `user` can get their own product"""
    products = list(map(lambda name: db["products"][name], db["users"][username]["products"]))
    return {
        "message": f"These are your products: {products}",
    }
```

The `ShieldedDepends` ensures that the payload is only retrieved if previous shields in the chain (like `auth_shield`) have already passed.


## Advanced Example

Check out the complete product catalog API example in the [`examples/app`](examples/app) directory, which demonstrates:

- Authentication with token-based shields
- Role-based access control
- Protecting user information
- Admin-only operations for products
- Testing protected endpoints with TestClient

```python
# Shield for requiring specific roles
def roles_required(roles: List[str]):
    """
    Role-based authorization shield that checks if the authenticated user
    has any of the required roles.
    """
    @shield
    def role_shield(token: str = ShieldedDepends(lambda t: t)):
        token_data = get_token_data(token)
        user_roles = token_data.get("roles", [])
        
        # Check if user has any of the required roles
        if any(role in user_roles for role in roles):
            return token_data
        
        # No matching roles, return None to block the request
        return None
        
    return role_shield

# Shortcut shields for common role checks
admin_required = roles_required(["admin"])
user_required = roles_required(["user", "admin"])
```

## Documentation

Visit our documentation for more details:

- **Getting Started**: Installation, basic usage, and core concepts
- **Shields Guide**: Understanding the Shield pattern
- **Authentication**: Token-based, OAuth, and custom authentication shields
- **Authorization**: Role-based access control and permission shields
- **Advanced Usage**: Complex security scenarios and custom shield creation, e.g. rate limiting shield
- **Examples**: Complete application examples

## How It Works

FastAPI Shield uses a layered decorator pattern to apply security checks:

1. **Define Shields**: Create functions decorated with `@shield` that blocks or passes requests after evaluating the guard function of the shield
2. **Stack Shields**: Apply multiple shields to endpoints in the desired order
3. **Access Protected Resources**: Use `ShieldedDepends` to access data from successful shields and retrieve dependencies
4. **Handle Failures**: Customize error responses when shield validation fails

Each shield acts as an independent layer of security that can:
- Allow the request to continue when it passes validation (returns a value)
- Block the request when validation fails (returns `None`)
- Pass state to dependent shields (via `ShieldedDepends`)

## Development

### Prerequisites

- Python 3.9 or higher
- FastAPI 0.100.1 or higher

### Install Development Dependencies

```bash
pip install uv
uv sync --dev
```

### Building from Source

```bash
git clone https://github.com/jimchng/fastapi-shield.git
cd fastapi-shield
pip install uv
uv sync --dev
```

### Running Tests

```bash
# Install `uv`
pip install uv

# Install `nox`
uv add --dev nox

# OR
uv tool install nox

# Run all tests
uv run python -m nox -s test

# OR
uv tool run nox -s test

# Run specific test suite
nox -s test -- tests/test_basics.py
```

### Release

Run the following commands:

1. `nox -s release-check` - this checks if the library is fit for release.
2. `nox -s release` - this builds the library, update the tag and commits it.
3. `git push origin main` - this pushes the repository to GitHub on branch `main`.
4. `git push origin <tag-name>` - this pushes the tag with `<tag-name>` to GitHub.
5. `nox -s publish` - this publishes the library to PYPI.

## Contributing

We welcome contributions! Please see our Contributing Guide for details.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

Special thanks to all contributors who have helped shape this project.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "fastapi-shield",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "Jim Chng <jimchng@outlook.com>",
    "keywords": "api, async, authentication, decorator, fastapi, python3, rate-limiting, security, shield, validation, web",
    "author": null,
    "author_email": "Jim Chng <jimchng@outlook.com>",
    "download_url": "https://files.pythonhosted.org/packages/d9/ac/88c50837d6176c2590ad92332b7634a6784257385f037e7ecd27e38e70ad/fastapi_shield-0.1.4.tar.gz",
    "platform": null,
    "description": "<div align=\"center\">\n\n<img src=\"./assets/logos/logo_hori_one.jpg\" width=80% height=20%></img>\n\n# FastAPI Shield\n\n## Documentation\n<a href=\"https://docs.fastapi-shield.asyncmove.com\">\n  <img src=\"https://img.shields.io/badge/docs-passing-brightgreen.svg\" width=\"100\" alt=\"docs passing\">\n</a>\n\n### Compatibility and Version\n<img src=\"https://img.shields.io/badge/%3E=python-3.9-blue.svg\" alt=\"Python compat\">\n<a href=\"https://pypi.python.org/pypi/fastapi-shield\"><img src=\"https://img.shields.io/pypi/v/fastapi-shield.svg\" alt=\"PyPi\"></a>\n\n### CI/CD\n<a href=\"https://codecov.io/github/jymchng/fastapi-shield?branch=main\"><img src=\"https://codecov.io/github/jymchng/fastapi-shield/coverage.svg?branch=main\" alt=\"Coverage\"></a>\n\n### License and Issues\n<a href=\"https://github.com/jymchng/fastapi-shield/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/jymchng/fastapi-shield\" alt=\"License\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/issues\"><img src=\"https://img.shields.io/github/issues/jymchng/fastapi-shield\" alt=\"Issues\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/issues?q=is%3Aissue+is%3Aclosed\"><img src=\"https://img.shields.io/github/issues-closed/jymchng/fastapi-shield\" alt=\"Closed Issues\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/issues?q=is%3Aissue+is%3Aopen\"><img src=\"https://img.shields.io/github/issues/jymchng/fastapi-shield\" alt=\"Open Issues\"></a>\n\n### Development and Quality\n<a href=\"https://github.com/jymchng/fastapi-shield/network/members\"><img src=\"https://img.shields.io/github/forks/jymchng/fastapi-shield\" alt=\"Forks\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/stargazers\"><img src=\"https://img.shields.io/github/stars/jymchng/fastapi-shield\" alt=\"Stars\"></a>\n<a href=\"https://pypi.python.org/pypi/fastapi-shield\"><img src=\"https://img.shields.io/pypi/dm/fastapi-shield\" alt=\"Downloads\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors/jymchng/fastapi-shield\" alt=\"Contributors\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/commits/main\"><img src=\"https://img.shields.io/github/commit-activity/m/jymchng/fastapi-shield\" alt=\"Commits\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/commits/main\"><img src=\"https://img.shields.io/github/last-commit/jymchng/fastapi-shield\" alt=\"Last Commit\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield\"><img src=\"https://img.shields.io/github/languages/code-size/jymchng/fastapi-shield\" alt=\"Code Size\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield\"><img src=\"https://img.shields.io/github/repo-size/jymchng/fastapi-shield\" alt=\"Repo Size\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/watchers\"><img src=\"https://img.shields.io/github/watchers/jymchng/fastapi-shield\" alt=\"Watchers\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield\"><img src=\"https://img.shields.io/github/commit-activity/y/jymchng/fastapi-shield\" alt=\"Activity\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/pulls\"><img src=\"https://img.shields.io/github/issues-pr/jymchng/fastapi-shield\" alt=\"PRs\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/pulls?q=is%3Apr+is%3Aclosed\"><img src=\"https://img.shields.io/github/issues-pr-closed/jymchng/fastapi-shield\" alt=\"Merged PRs\"></a>\n<a href=\"https://github.com/jymchng/fastapi-shield/pulls?q=is%3Apr+is%3Aopen\"><img src=\"https://img.shields.io/github/issues-pr/open/jymchng/fastapi-shield\" alt=\"Open PRs\"></a>\n\n</div>\n\nA powerful, intuitive, and flexible authentication and authorization library for FastAPI applications. Stack your shields to create robust and customizable layers which effectively shields your endpoints from unwanted requests.\n\n# Features\n\n- **Decorator-based Security**: Apply shields as simple decorators to protect your endpoints\n- **Layered Protection**: Stack multiple shields for fine-grained access control\n- **Clean Design Pattern**: Shields provide a clear and intuitive metaphor for API protection\n- **Fully Integrated**: Works seamlessly with FastAPI's dependency injection system\n- **Type Safety**: Full type hint support for better IDE integration and code quality\n- **Customizable Responses**: Configure error responses when access is denied\n- **ShieldedDepends**: Special dependency mechanism for protected resources\n- **Lazy-Loading of Dependencies**: Dependencies are only loaded from FastAPI after the request passes through all the decorated shields\n\n# Installation\n\nWith `pip`:\n```bash\npip install fastapi-shield\n```\n\nWith `uv`:\n```bash\nuv add fastapi-shield\n```\n\nWith `poetry`:\n```bash\npoetry add fastapi-shield\n```\n\n# Basic Usage\n\n## \ud83d\udee1\ufe0f Create your First Shield\n\nLet's create a simple `@auth_shield` to shield against unauthenticated requests! \ud83d\udee1\ufe0f\n\n```python\nfrom fastapi import Header\nfrom fastapi_shield import shield\n\n# Create a simple authentication shield\n@shield\ndef auth_shield(api_token: str = Header()):\n    \"\"\"\n    A basic shield that validates an API token.\n    Returns the token if valid, otherwise returns None which blocks the request.\n    \"\"\"\n    if api_token in (\"admin_token\", \"user_token\"):\n        return api_token\n    return None\n```\n\nNow that you've created your first shield, you can easily apply it to any FastAPI endpoint! \ud83d\ude80\n\nThe shield acts as a decorator that protects your endpoint from unauthorized access. \n\nWhen a request comes in, the shield evaluates the API token before the endpoint function is called.\n\nIf the token is invalid (returning None), the request is blocked! \ud83d\udeab \n\nThis creates a clean separation between authentication logic and business logic, making your code more maintainable.\n\nJust like a real shield, it stands in front of your endpoint to protect it! \ud83d\udcaa\n\n\n## See your First Shield in Action\n\nNow let's see how our shield works in the wild! \ud83d\ude80 When a user tries to access the protected endpoint, the shield jumps into action like a superhero! \ud83e\uddb8\u200d\u2640\ufe0f\n\n```python\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\n# Protected endpoint - requires authentication\n@app.get(\"/protected/{name}\")\n@auth_shield # apply `@auth_shield`\nasync def protected_endpoint(name: str):\n    return {\n        \"message\": f\"Hello {name}. This endpoint is protected!\",\n    }\n```\n\n```python\nfrom fastapi.testclient import TestClient\n\nclient = TestClient(app)\n\ndef test_protected():\n    client = TestClient(app)\n    response = client.get(\"/protected/John\", headers={\"API-TOKEN\": \"valid_token\"})\n    assert response.status_code == 200\n    assert response.json() == {\"message\": \"Hello John. This endpoint is protected!\"}\n\n\ndef test_protected_unauthorized():\n    client = TestClient(app)\n    response = client.get(\"/protected/John\", headers={\"API-TOKEN\": \"invalid_token\"})\n    assert response.status_code == 500\n    assert response.json() == {'detail': 'Shield with name `unknown` blocks the request'}, response.json()\n```\n\nFrom the above, we can see how the shield works in practice. The `auth_shield` decorator is applied to our endpoint, checking the API token in the request headers before allowing access to the protected endpoint. When a valid token is provided, the request proceeds normally and returns a friendly greeting. However, when an invalid token is sent, the shield blocks the request, returning a 500 error with a message indicating that the shield has prevented access. This demonstrates the power of shields as a clean, declarative way to implement authentication in FastAPI applications without cluttering your endpoint logic with authorization checks.\n\n<div align=\"center\">\n  <img src=\"./assets/pictures/IMG_20250423_003431_018.jpg\" alt=\"Shield Congratulations\" width=\"40%\">\n  \n  ### \ud83c\udf89 Congratulations! You've made your First Wonderful Shield! \ud83c\udf89\n</div>\n\n## Your Second Shield! \ud83d\udee1\ufe0f\ud83d\udee1\ufe0f\n\nFirst, let's see what's the final endpoint is going to look like:\n\n```python\n@app.get(\"/products\")\n@auth_shield\n@roles_shield([\"user\"])\nasync def get_all_products(db: Dict[str, Any]=Depends(get_db), username: str=ShieldedDepends(get_username_from_payload)):\n    \"\"\"Only user with role `user` can get their own product\"\"\"\n    products = list(map(lambda name: db[\"products\"][name], db[\"users\"][username][\"products\"]))\n    return {\n        \"message\": f\"These are your products: {products}\",\n    }\n```\n\nWe're going to make the `@roles_shield([\"user\"])`.\n\nBut before that, there's one point to note: one of the advantages of `fastapi-shield` is that it enables lazy injection of FastAPI's dependencies.\n\nIn the signature of the endpoint: `async def get_all_products(db: Dict[str, Any]=Depends(get_db), username: str=ShieldedDepends(get_username_from_payload))`, the `db: Dict[str, Any]=Depends(get_db)` is only injected after `@roles_shield([\"user\"])` becomes 'unblocked', i.e. allowing the request to reach the endpoint `get_all_products`.\n\nPrior to that, if the request is blocked by any of the decoratored shields, e.g. `@auth_shield` and `@roles_shield([\"user\"])`, then the FastAPI's dependencies are not injected. We will discuss this in more detail later, on why this is an advantage compared to other decorators-based libraries.\n\nLet's see how the `@roles_shield([\"user\"])` is written.\n\n```python\n# Create a simple roles shield\ndef roles_shield(roles: list[str]):\n    \"\"\"\n    A shield that validates a list of roles.\n    \"\"\"\n    \n    @shield\n    def wrapper(payload = ShieldedDepends(get_payload_from_token)):\n        if any(role in payload[\"roles\"] for role in roles):\n            return payload\n        return None\n    \n    return wrapper\n```\n\nThe `roles_shield` function is a shield factory that creates a shield for role-based access control. It takes a list of roles as input and returns a shield function. \n\nThe inner `wrapper` function of `roles_shield` uses `ShieldedDepends` to get the payload from the token, which contains user information including their roles.\n\nThe shield then checks if any of the user's roles match the required roles using the `any()` function with a generator expression. \n\nIf there's a match, it returns the payload, allowing the request to proceed. If no matching role is found, it returns `None`, which blocks the request. \n\nThis pattern demonstrates how shields can be composed and parameterized to create flexible authorization rules. \n\n### What is `ShieldedDepends`?\n\n`ShieldedDepends` is a mechanism to pass information from a `Shield` to a dependency to retrieve it.\n\nIt is a specialized dependency injection class that extends FastAPI's `Security` class. `ShieldedDepends` is used to inject dependencies that should only be resolved if a shield allows the request to proceed. It takes a callable function as its `shielded_dependency` parameter.\n\nThe `shielded_dependency` function:\n1. Can be synchronous or asynchronous (coroutine)\n2. Should accept parameters that can be resolved by FastAPI's dependency injection system\n3. Will only be executed when the shield is \"unblocked\" (when the shield function returns a truthy value)\n4. Can access request information and other dependencies just like regular FastAPI dependencies\n\n### How `ShieldedDepends` Passes Data Between Shields\n\nOne powerful feature of `ShieldedDepends` is its ability to pass data between shields in a decorator chain, from top decorator to the lower ones.\n\nWhen a shield returns a value (other than `None`), that value can be captured and used by subsequent shields, via the `ShieldedDepends` dependencies specified in the function signature of the shield, or by the endpoint function itself via the same mechanism.\n\nWhen a shield is blocked, the `ShieldedDepends` instance returns itself instead of executing the dependency function, preventing unnecessary computation and database access. This lazy evaluation is one of the key advantages of the `fastapi-shield` library.\n\nFor example, in our `get_payload_from_token` function (which is an argument to be passed into `ShieldedDepends` function; seen on the line `def wrapper(payload = ShieldedDepends(get_payload_from_token)): ...`):\n\n```python\ndef get_payload_from_token(token: str):\n    if token == \"admin_token\":\n        return {\"username\": \"Peter\", \"roles\": [\"admin\", \"user\"]}\n    elif token == \"user_token\":\n        return {\"username\": \"John\", \"roles\": [\"user\"]}\n    return None\n```\n\nThe `token` parameter will be passed by the guard function of the `shield` decorating it.\n\n```python\n@shield\ndef auth_shield(api_token: str = Header()):\n    \"\"\"\n    A basic shield that validates an API token.\n    Returns the token if valid, otherwise returns None which blocks the request.\n    \"\"\"\n    if api_token in (\"admin_token\", \"user_token\"):\n        return api_token\n    return None\n```\n\nThe guard function of `auth_shield` returns the `api_token` which is then passed into `get_payload_from_token` shielded dependency function of the `roles_shield`.\n\n```python\n@app.get(\"/products\")\n@auth_shield\n@roles_shield([\"user\"])\nasync def get_all_products(db: Dict[str, Any]=Depends(get_db), username: str=ShieldedDepends(get_username_from_payload)):\n    \"\"\"Only user with role `user` can get their own product\"\"\"\n    products = list(map(lambda name: db[\"products\"][name], db[\"users\"][username][\"products\"]))\n    return {\n        \"message\": f\"These are your products: {products}\",\n    }\n```\n\nThe `ShieldedDepends` ensures that the payload is only retrieved if previous shields in the chain (like `auth_shield`) have already passed.\n\n\n## Advanced Example\n\nCheck out the complete product catalog API example in the [`examples/app`](examples/app) directory, which demonstrates:\n\n- Authentication with token-based shields\n- Role-based access control\n- Protecting user information\n- Admin-only operations for products\n- Testing protected endpoints with TestClient\n\n```python\n# Shield for requiring specific roles\ndef roles_required(roles: List[str]):\n    \"\"\"\n    Role-based authorization shield that checks if the authenticated user\n    has any of the required roles.\n    \"\"\"\n    @shield\n    def role_shield(token: str = ShieldedDepends(lambda t: t)):\n        token_data = get_token_data(token)\n        user_roles = token_data.get(\"roles\", [])\n        \n        # Check if user has any of the required roles\n        if any(role in user_roles for role in roles):\n            return token_data\n        \n        # No matching roles, return None to block the request\n        return None\n        \n    return role_shield\n\n# Shortcut shields for common role checks\nadmin_required = roles_required([\"admin\"])\nuser_required = roles_required([\"user\", \"admin\"])\n```\n\n## Documentation\n\nVisit our documentation for more details:\n\n- **Getting Started**: Installation, basic usage, and core concepts\n- **Shields Guide**: Understanding the Shield pattern\n- **Authentication**: Token-based, OAuth, and custom authentication shields\n- **Authorization**: Role-based access control and permission shields\n- **Advanced Usage**: Complex security scenarios and custom shield creation, e.g. rate limiting shield\n- **Examples**: Complete application examples\n\n## How It Works\n\nFastAPI Shield uses a layered decorator pattern to apply security checks:\n\n1. **Define Shields**: Create functions decorated with `@shield` that blocks or passes requests after evaluating the guard function of the shield\n2. **Stack Shields**: Apply multiple shields to endpoints in the desired order\n3. **Access Protected Resources**: Use `ShieldedDepends` to access data from successful shields and retrieve dependencies\n4. **Handle Failures**: Customize error responses when shield validation fails\n\nEach shield acts as an independent layer of security that can:\n- Allow the request to continue when it passes validation (returns a value)\n- Block the request when validation fails (returns `None`)\n- Pass state to dependent shields (via `ShieldedDepends`)\n\n## Development\n\n### Prerequisites\n\n- Python 3.9 or higher\n- FastAPI 0.100.1 or higher\n\n### Install Development Dependencies\n\n```bash\npip install uv\nuv sync --dev\n```\n\n### Building from Source\n\n```bash\ngit clone https://github.com/jimchng/fastapi-shield.git\ncd fastapi-shield\npip install uv\nuv sync --dev\n```\n\n### Running Tests\n\n```bash\n# Install `uv`\npip install uv\n\n# Install `nox`\nuv add --dev nox\n\n# OR\nuv tool install nox\n\n# Run all tests\nuv run python -m nox -s test\n\n# OR\nuv tool run nox -s test\n\n# Run specific test suite\nnox -s test -- tests/test_basics.py\n```\n\n### Release\n\nRun the following commands:\n\n1. `nox -s release-check` - this checks if the library is fit for release.\n2. `nox -s release` - this builds the library, update the tag and commits it.\n3. `git push origin main` - this pushes the repository to GitHub on branch `main`.\n4. `git push origin <tag-name>` - this pushes the tag with `<tag-name>` to GitHub.\n5. `nox -s publish` - this publishes the library to PYPI.\n\n## Contributing\n\nWe welcome contributions! Please see our Contributing Guide for details.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\nSpecial thanks to all contributors who have helped shape this project.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A comprehensive FastAPI decorator library providing request validation, authentication, rate limiting, and security shields for API endpoints",
    "version": "0.1.4",
    "project_urls": {
        "Bug Tracker": "https://github.com/jimchng/fastapi-shield/issues",
        "Documentation": "https://fastapi-shield.asyncmove.com/",
        "Feature Requests": "https://github.com/jimchng/fastapi-shield/discussions",
        "Homepage": "https://github.com/jimchng/fastapi-shield",
        "Repository": "https://github.com/jimchng/fastapi-shield.git"
    },
    "split_keywords": [
        "api",
        " async",
        " authentication",
        " decorator",
        " fastapi",
        " python3",
        " rate-limiting",
        " security",
        " shield",
        " validation",
        " web"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b73fef9e21f80397ea30a2158681d992be374909a8a39014bf22853d138791d4",
                "md5": "fbf1aa1d221ba782bf7e2c9e45c24cc3",
                "sha256": "2a1bff7a1eee61ab055b8a9c01aeccb06a5bab0154220e0ec635009800eda241"
            },
            "downloads": -1,
            "filename": "fastapi_shield-0.1.4-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "fbf1aa1d221ba782bf7e2c9e45c24cc3",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 26307,
            "upload_time": "2025-10-19T16:54:42",
            "upload_time_iso_8601": "2025-10-19T16:54:42.557597Z",
            "url": "https://files.pythonhosted.org/packages/b7/3f/ef9e21f80397ea30a2158681d992be374909a8a39014bf22853d138791d4/fastapi_shield-0.1.4-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d9ac88c50837d6176c2590ad92332b7634a6784257385f037e7ecd27e38e70ad",
                "md5": "a8dac0b04bec7de2698775abc2a62a30",
                "sha256": "101b5d1e282a135640df060d5eb759528b4cb1aa99049f80b45ae9a357c58412"
            },
            "downloads": -1,
            "filename": "fastapi_shield-0.1.4.tar.gz",
            "has_sig": false,
            "md5_digest": "a8dac0b04bec7de2698775abc2a62a30",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 37897,
            "upload_time": "2025-10-19T16:54:44",
            "upload_time_iso_8601": "2025-10-19T16:54:44.057762Z",
            "url": "https://files.pythonhosted.org/packages/d9/ac/88c50837d6176c2590ad92332b7634a6784257385f037e7ecd27e38e70ad/fastapi_shield-0.1.4.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-19 16:54:44",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "jimchng",
    "github_project": "fastapi-shield",
    "github_not_found": true,
    "lcname": "fastapi-shield"
}
        
Elapsed time: 1.98670s