Skip to content

Custom Error Responses

By default, when a permission check fails, fastapi-has-permissions raises an HTTPException with status code 403 Forbidden and detail "Permission denied". You can customize both the message and the status code.

Class-Level Defaults

Set default_exc_message and default_exc_status_code as class variables to change the defaults for all instances of a permission:

from fastapi import Depends, FastAPI, Request, status

from fastapi_has_permissions import Permission


class RequiresAuthentication(Permission):
    default_exc_message = "Authentication required"
    default_exc_status_code = status.HTTP_401_UNAUTHORIZED

    async def check_permissions(self, request: Request) -> bool:
        return "Authorization" in request.headers


app = FastAPI()


@app.get(
    "/protected",
    dependencies=[Depends(RequiresAuthentication())],
)
async def protected():
    return {"message": "You have access!"}

When the check fails, the response will be:

{
    "detail": "Authentication required"
}

with HTTP status 401.

Instance-Level Overrides

You can override the message and status code per instance using the message and status_code keyword arguments:

@app.get(
    "/custom-message",
    dependencies=[
        Depends(
            RequiresAuthentication(
                message="Please provide a valid token",
                status_code=status.HTTP_401_UNAUTHORIZED,
            ),
        ),
    ],
)
async def custom_message():
    return {"message": "You have access!"}

Note

Instance-level message and status_code take precedence over class-level defaults.

Override Methods

For dynamic error messages, you can override get_exc_message() and get_exc_status_code():

from dataclasses import dataclass

from fastapi_has_permissions import Permission


@dataclass
class HasRole(Permission):
    role: str

    def get_exc_message(self) -> str:
        return f"You need the '{self.role}' role to access this resource"

    async def check_permissions(self, request: Request) -> bool:
        return request.headers.get("role") == self.role

Composed Permission Defaults

The built-in composition classes have their own default messages:

Class Default Message
AllPermissions (&) "Not all permissions were satisfied"
AnyPermissions (\|) "None of the permissions were satisfied"
NotPermission (~) "The permission was satisfied, but it should not have been"

You can override these by passing message to the composition result:

perm = HasAuthorizationHeader() & HasAdminRole()
perm.message = "You must be an authenticated admin"

Using Failed for Per-Check Messages

You can also return Failed(reason="...") from check_permissions to provide a specific error message for that particular failure:

from fastapi_has_permissions import Permission
from fastapi_has_permissions._results import Failed


class HasValidToken(Permission):
    async def check_permissions(self, request: Request) -> bool | Failed:
        token = request.headers.get("Authorization")

        if token is None:
            return Failed(reason="Authorization header is missing")

        if not token.startswith("Bearer "):
            return Failed(reason="Token must use Bearer scheme")

        return True

The reason from the Failed result will be used as the HTTP exception detail.