Skip to content

FastAPIAuth

Authentication for FastAPI applications.

FastAPIAuth

Bases: BaseAuth

Authentication for FastAPI apps.

RECOMMENDED: Use protect_app() to protect your entire application with one line.

Example (RECOMMENDED - protect entire app): from fastapi import FastAPI, Depends from cognito_auth.fastapi import FastAPIAuth

app = FastAPI()
auth = FastAPIAuth()
auth.protect_app(app)  # Protects entire app!

@app.get("/")
def index(user: User = Depends(auth.get_auth_user)):
    return {"message": f"Welcome {user.email}!"}

Example (Alternative - protect specific routes): from fastapi import FastAPI, Depends from cognito_auth.fastapi import FastAPIAuth

app = FastAPI()
auth = FastAPIAuth()

@app.get("/public")
def public():
    return {"message": "Public page"}

@app.get("/protected")
def protected(user: User = Depends(auth.get_auth_user)):
    return {"message": f"Welcome {user.email}!"}
Source code in src/cognito_auth/fastapi.py
class FastAPIAuth(BaseAuth):
    """
    Authentication for FastAPI apps.

    RECOMMENDED: Use protect_app() to protect your entire application with one line.

    Example (RECOMMENDED - protect entire app):
        from fastapi import FastAPI, Depends
        from cognito_auth.fastapi import FastAPIAuth

        app = FastAPI()
        auth = FastAPIAuth()
        auth.protect_app(app)  # Protects entire app!

        @app.get("/")
        def index(user: User = Depends(auth.get_auth_user)):
            return {"message": f"Welcome {user.email}!"}

    Example (Alternative - protect specific routes):
        from fastapi import FastAPI, Depends
        from cognito_auth.fastapi import FastAPIAuth

        app = FastAPI()
        auth = FastAPIAuth()

        @app.get("/public")
        def public():
            return {"message": "Public page"}

        @app.get("/protected")
        def protected(user: User = Depends(auth.get_auth_user)):
            return {"message": f"Welcome {user.email}!"}
    """

    def protect_app(self, app: FastAPI) -> None:
        """
        Protect the entire application with authentication.

        This is the RECOMMENDED approach. Call this once after creating your app,
        and all routes will require authentication. Use get_auth_user() dependency
        to access the authenticated user.

        Args:
            app: FastAPI application instance

        Example:
            app = FastAPI()
            auth = FastAPIAuth()
            auth.protect_app(app)  # One line protects everything!

            @app.get("/")
            def index(user: User = Depends(auth.get_auth_user)):
                return {"message": f"Welcome {user.email}!"}
        """

        class AuthMiddleware(BaseHTTPMiddleware):
            def __init__(self, app, auth_instance):
                super().__init__(app)
                self.auth = auth_instance

            async def dispatch(self, request: Request, call_next):
                """Validate authentication before every request."""
                try:
                    headers = dict(request.headers)
                    user = self.auth._get_user_from_headers(headers)

                    if not self.auth._is_authorised(user):
                        return RedirectResponse(url=self.auth.redirect_url)

                    # Store user in request state (FastAPI's equivalent of Flask's g)
                    request.state.user = user

                    return await call_next(request)

                except Exception:
                    return RedirectResponse(url=self.auth.redirect_url)

        app.add_middleware(AuthMiddleware, auth_instance=self)

    def get_auth_user(self, request: Request) -> User:
        """
        Get the authenticated and authorised user for this request.

        This method is designed to be used with FastAPI's Depends() for
        dependency injection.

        When using protect_app() (RECOMMENDED), this retrieves the user that was
        validated by the middleware.

        When not using protect_app(), this validates the user on-demand.

        Args:
            request: FastAPI Request object (automatically injected by Depends)

        Returns:
            Authenticated and authorised User

        Raises:
            HTTPException: 401 if authentication fails, 403 if unauthorised

        Example:
            auth.protect_app(app)

            @app.get("/protected")
            def protected_route(user: User = Depends(auth.get_auth_user)):
                return {"email": user.email}
        """
        # If protect_app() was used, user is stored in request.state
        if hasattr(request.state, "user"):
            return request.state.user

        # Otherwise, validate on-demand (for route-specific protection)
        try:
            headers = dict(request.headers)
            user = self._get_user_from_headers(headers)

            if not self._is_authorised(user):
                raise HTTPException(
                    status_code=403,
                    detail="Access denied. You don't have permission.",
                )

            return user

        except HTTPException:
            # Re-raise HTTPException as-is
            raise
        except Exception as e:
            raise HTTPException(
                status_code=401,
                detail="Authentication failed. Unable to verify your identity.",
            ) from e

Functions

protect_app(app)

Protect the entire application with authentication.

This is the RECOMMENDED approach. Call this once after creating your app, and all routes will require authentication. Use get_auth_user() dependency to access the authenticated user.

Parameters:

Name Type Description Default
app FastAPI

FastAPI application instance

required
Example

app = FastAPI() auth = FastAPIAuth() auth.protect_app(app) # One line protects everything!

@app.get("/") def index(user: User = Depends(auth.get_auth_user)): return {"message": f"Welcome {user.email}!"}

Source code in src/cognito_auth/fastapi.py
def protect_app(self, app: FastAPI) -> None:
    """
    Protect the entire application with authentication.

    This is the RECOMMENDED approach. Call this once after creating your app,
    and all routes will require authentication. Use get_auth_user() dependency
    to access the authenticated user.

    Args:
        app: FastAPI application instance

    Example:
        app = FastAPI()
        auth = FastAPIAuth()
        auth.protect_app(app)  # One line protects everything!

        @app.get("/")
        def index(user: User = Depends(auth.get_auth_user)):
            return {"message": f"Welcome {user.email}!"}
    """

    class AuthMiddleware(BaseHTTPMiddleware):
        def __init__(self, app, auth_instance):
            super().__init__(app)
            self.auth = auth_instance

        async def dispatch(self, request: Request, call_next):
            """Validate authentication before every request."""
            try:
                headers = dict(request.headers)
                user = self.auth._get_user_from_headers(headers)

                if not self.auth._is_authorised(user):
                    return RedirectResponse(url=self.auth.redirect_url)

                # Store user in request state (FastAPI's equivalent of Flask's g)
                request.state.user = user

                return await call_next(request)

            except Exception:
                return RedirectResponse(url=self.auth.redirect_url)

    app.add_middleware(AuthMiddleware, auth_instance=self)

get_auth_user(request)

Get the authenticated and authorised user for this request.

This method is designed to be used with FastAPI's Depends() for dependency injection.

When using protect_app() (RECOMMENDED), this retrieves the user that was validated by the middleware.

When not using protect_app(), this validates the user on-demand.

Parameters:

Name Type Description Default
request Request

FastAPI Request object (automatically injected by Depends)

required

Returns:

Type Description
User

Authenticated and authorised User

Raises:

Type Description
HTTPException

401 if authentication fails, 403 if unauthorised

Example

auth.protect_app(app)

@app.get("/protected") def protected_route(user: User = Depends(auth.get_auth_user)): return {"email": user.email}

Source code in src/cognito_auth/fastapi.py
def get_auth_user(self, request: Request) -> User:
    """
    Get the authenticated and authorised user for this request.

    This method is designed to be used with FastAPI's Depends() for
    dependency injection.

    When using protect_app() (RECOMMENDED), this retrieves the user that was
    validated by the middleware.

    When not using protect_app(), this validates the user on-demand.

    Args:
        request: FastAPI Request object (automatically injected by Depends)

    Returns:
        Authenticated and authorised User

    Raises:
        HTTPException: 401 if authentication fails, 403 if unauthorised

    Example:
        auth.protect_app(app)

        @app.get("/protected")
        def protected_route(user: User = Depends(auth.get_auth_user)):
            return {"email": user.email}
    """
    # If protect_app() was used, user is stored in request.state
    if hasattr(request.state, "user"):
        return request.state.user

    # Otherwise, validate on-demand (for route-specific protection)
    try:
        headers = dict(request.headers)
        user = self._get_user_from_headers(headers)

        if not self._is_authorised(user):
            raise HTTPException(
                status_code=403,
                detail="Access denied. You don't have permission.",
            )

        return user

    except HTTPException:
        # Re-raise HTTPException as-is
        raise
    except Exception as e:
        raise HTTPException(
            status_code=401,
            detail="Authentication failed. Unable to verify your identity.",
        ) from e

Quick Start

from fastapi import FastAPI, Depends
from cognito_auth import User
from cognito_auth.fastapi import FastAPIAuth

app = FastAPI()

# Auto-loads from environment variables
auth = FastAPIAuth()
auth.protect_app(app)  # Protects entire app!

@app.get("/")
def index(user: User = Depends(auth.get_auth_user)):
    return {"message": f"Welcome {user.email}!"}

Configuration

FastAPIAuth inherits from BaseAuth and accepts these parameters:

  • authoriser (optional): Pre-configured Authoriser instance. If not provided, auto-loads from environment variables
  • redirect_url (optional): Where to redirect unauthorised users (default: "https://gds-idea.click/401.html")
  • region (optional): AWS region (default: "eu-west-2")
from cognito_auth import Authoriser
from cognito_auth.fastapi import FastAPIAuth

# Custom configuration
authoriser = Authoriser.from_lists(allowed_groups=["developers"])
auth = FastAPIAuth(
    authoriser=authoriser,
    redirect_url="https://myapp.com/unauthorised",
    region="us-east-1"
)

Behavior

FastAPIAuth uses dependency injection with Depends(). When authentication or authorisation fails:

  • With protect_app(): Middleware redirects to redirect_url before any route executes
  • Without protect_app(): Routes with Depends(auth.get_auth_user) raise HTTPException (401 for auth failure, 403 for unauthorised)

The user is stored in request.state.user, making it efficient to call get_auth_user() multiple times.

Development Mode

Enable dev mode for local development without ALB:

export COGNITO_AUTH_DEV_MODE=true

When dev mode is enabled and headers are missing, get_auth_user() returns a mock user instead of failing.

Customizing the Mock User

To customize the mock user returned in dev mode, create a dev-mock-user.json file in your project root:

{
  "email": "developer@example.com",
  "sub": "12345678-1234-1234-1234-123456789abc",
  "username": "12345678-1234-1234-1234-123456789abc",
  "groups": ["developers", "users"]
}

The mock user will use these values instead of the defaults. This is useful for testing different authorisation scenarios.

Available fields: - email - Mock user's email address - sub - Mock user's Cognito subject (UUID) - username - Mock user's username (usually same as sub) - groups - Mock user's Cognito groups for authorisation testing

See dev-mock-user.example.json in the repository for a complete template with comments.

Alternative config location:

You can specify a custom path via environment variable:

export COGNITO_AUTH_DEV_CONFIG=/path/to/your/mock-user.json

Complete Example

from fastapi import FastAPI, Depends
from cognito_auth import User
from cognito_auth.fastapi import FastAPIAuth

app = FastAPI()

# Initialize and protect entire app
auth = FastAPIAuth()
auth.protect_app(app)

@app.get("/")
def index(user: User = Depends(auth.get_auth_user)):
    return {
        "message": f"Welcome {user.email}!",
        "groups": user.groups,
        "is_admin": user.is_admin
    }

@app.get("/admin")
def admin_only(user: User = Depends(auth.get_auth_user)):
    if not user.is_admin:
        raise HTTPException(status_code=403, detail="Admin access required")
    return {"message": "Admin panel"}

Protect Specific Routes Only

from fastapi import FastAPI, Depends
from cognito_auth import User
from cognito_auth.fastapi import FastAPIAuth

app = FastAPI()
auth = FastAPIAuth()
# Note: NOT calling protect_app()

@app.get("/public")
def public():
    return {"message": "Public endpoint"}

@app.get("/protected")
def protected(user: User = Depends(auth.get_auth_user)):
    return {"email": user.email}