DEV Community

Antonio Radesca
Antonio Radesca

Posted on

Building Secure Applications with Permguard and FastAPI

Previously, we explored how Django can be integrated with Permguard to enhance application security. Now, let’s take a look at a similar example using FastAPI.

Permguard is an Open Source ZTAuth* Provider for cloud-native, edge, and multi-tenant apps, decoupled from application code. It implements a centralized authorization layer using Policy-as-code, enabling enforcement of permission from anywhere, including VMs, Containers, and Serverless. Permguard is a modern, open-source authorization provider designed to follow Zero Trust principles. It uses the Zero Trust Auth* (ZTAuth*) architecture to ensure that every access request is continuously verified, regardless of application boundaries or context.

The main idea is to ensure that trust is never assumed but always validated at the application boundary. Integrating Permguard to handle incoming requests ensures that every request is verified before access is granted.

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. It allows for easy creation of RESTful APIs with automatic interactive documentation (using Swagger UI), and is known for its speed and simplicity. FastAPI is designed to be intuitive, easy to use, and highly performant, making it ideal for building APIs quickly while ensuring data validation and security.

Unlike Django, which comes with built-in security features, FastAPI does not have integrated security mechanisms. However, we can implement security using JWT (JSON Web Tokens) to manage authentication and PErmguard to manage Authorization.

In FastAPI, Dependency Injection (DI) is a powerful way to manage the dependencies of your route handlers (such as database connections, authentication classes, or services) in a clean and decoupled manner. It allows you to inject necessary resources or functions into your endpoints without manually passing them each time.

Basic Example of Dependency Injection in FastAPI
Let’s walk through how to use Dependency Injection in FastAPI with a simple example.

Step 1: Define the Dependency (Authentication)
Let’s create a simple authentication dependency. This could be a service that checks if a user has provided a valid token.

from fastapi import HTTPException, status

class AuthService:
    def authenticate(self, token: str):
        if token != "valid_token":
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
        return {"username": "john_doe"}

def get_auth_service():
    return AuthService()
Enter fullscreen mode Exit fullscreen mode

Step 2: Use the Authentication Dependency in a Route
Now, let’s use this authentication service in an endpoint. We will inject the AuthService dependency into the route handler using FastAPI’s Depends().

from fastapi import FastAPI, Depends

app = FastAPI()

@app.get("/secure-data/")
def get_secure_data(auth: AuthService = Depends(get_auth_service)):
    # Authenticate with a valid token (for simplicity, we're just using a fixed token here)
    user = auth.authenticate("valid_token")
    return {"message": f"Access granted to {user['username']}"}
Enter fullscreen mode Exit fullscreen mode

In this example:

The Depends(get_auth_service) will inject an instance of the AuthService class into the auth parameter of the route handler.
The get_secure_data route uses the injected AuthService to authenticate the request.
Step 3: Using Dependency Injection for Authentication
You can apply this concept to any part of your application where authentication or authorization is needed. Dependency Injection makes it easy to modularize authentication logic and keep it decoupled from the individual route handlers.

“In this article, we’ll use the same sample previously applied to Django to demonstrate how to implement security in FastAPI. For a detailed explanation, please refer to this link.

https://medium.com/@antonio.radesca/enhancing-authorization-in-django-with-permguard-a-zero-trust-approach-b85b813d252d

We’ll create all necessary policies in Permguard and use dependency injection to integrate the authorization layer into the FastAPI application.”

import json
import logging
import os
import uuid


from fastapi import Depends, HTTPException, Request
from permguard.az.azreq.builder_principal import PrincipalBuilder
from permguard.az.azreq.builder_request_atomic import AZAtomicRequestBuilder
from permguard.az_client import AZClient
from permguard.az_config import with_endpoint
from typing_extensions import Annotated


from security_support import User, get_current_user




async def resource_action_evaluate(request: Request, current_user: User, input_string: str):
   try:
       az_client = AZClient(with_endpoint(os.environ.get("host", "127.0.0.1"), os.environ.get("port", 9094)))
       body = await request.body()
       body_json = json.loads(body)
       principal = PrincipalBuilder(current_user.username).build()
       req = (
           AZAtomicRequestBuilder(
               os.environ.get("zone", 197102289968),
               os.environ.get("ledger", "359f7ed82fac42f0a438f4f80174c52a"),
               input_string,
               body_json.get("resource_type"),
               body_json.get("action"),
           )
           .with_request_id(str(uuid.uuid4()))
           .with_principal(principal)
           .with_entities_items("cedar", [])
           .with_subject_role_actor_type()
           .with_subject_source("fastapi")
           .with_subject_property("groups", current_user.groups)
           .with_resource_id(body_json.get("resource_id"))
           .build()
       )


       ok, response = az_client.check(req)
       logging.info(f"Permguard check: {response}")
       if not ok:
           raise HTTPException(status_code=403, detail="Permguard check failed: " + str(response))
       return True
   except Exception as e:
       raise HTTPException(status_code=400, detail=str(e))




def get_resource_action_evaluate(input_string: str):
   async def wrapper(request: Request, current_user: Annotated[User, Depends(get_current_user)]):
       return await resource_action_evaluate(request, current_user, input_string)
   return wrapper
Enter fullscreen mode Exit fullscreen mode

This code is designed to integrate with Permguard for evaluating whether a user is authorized to perform an action on a resource. It involves several key steps and components that work together to handle authorization requests.

Overview
Authentication and Authorization Logic:

The code is structured around an asynchronous function that evaluates whether a user has permission to perform a specified action on a resource.
The user’s identity is extracted from the request using a dependency injection mechanism (get_current_user), which retrieves the currently authenticated user.
Building the Authorization Request:

The main task is to construct an authorization request that specifies the action the user wants to perform, the resource they are acting upon, and the user’s identity.
This is done using the PrincipalBuilder, which creates a principal representing the current user, and the AZAtomicRequestBuilder, which organizes the request data.
The request includes details such as the user’s role, groups, resource type, and action, among other things.

Sending the Authorization Request:
The constructed request is sent to the Permguard authorization service through the AZClient.
Permguard checks if the user has the necessary permissions for the action they want to perform.
Handling Responses:

If the authorization check returns a success, the system continues and the user is authorized for the action.
If the check fails (i.e., the user is not authorized), an HTTP 403 error is raised, informing the user that they do not have permission to perform the requested action.
Error Handling:

The code is wrapped in a try-except block to handle any errors that might arise during the process, such as malformed input or communication issues with the authorization service. In case of an error, a 400 Bad Request response is returned with the error message.
Customizable Authorization Check:

The code provides a factory function (get_resource_action_evaluate) that generates a wrapper function. This wrapper can be used as a FastAPI route handler, which allows for dynamic evaluation of different actions and resources based on the input passed to it.
Key Concepts
Dependency Injection: FastAPI uses dependency injection to manage dependencies like the current user. The get_current_user function is responsible for providing the user, and it’s injected into the route handler automatically.
Permguard Integration: The code integrates with the Permguard service, which handles the actual authorization checks. It uses specific classes and methods (AZClient, PrincipalBuilder, AZAtomicRequestBuilder) to build and send authorization requests.
Modular and Reusable Code: The factory function makes the authorization logic reusable and customizable for different actions or resources without repeating the same code.
In essence, this code is a secure and efficient way of checking if a user is authorized to perform specific actions on resources, leveraging Permguard for the authorization checks and FastAPI for handling the HTTP requests and responses.

This function demonstrates how to protect our API by utilizing our custom dependency:

@app.post("/items/")
async def read_items(result: dict = Depends(get_resource_action_evaluate("platform-editor"))):
   logging.info(result)
   return {"items": ["item1", "item2"]}
Enter fullscreen mode Exit fullscreen mode

Complete source code is available here:

https://github.com/antrad1978/fastapi-permguard

Top comments (0)