Authentication in Generated Projects
Castlecraft Architect leverages the authentication capabilities of the castlecraft-engineer
library to provide robust token-based authentication for your generated applications. This document outlines how it works and how to configure it.
Core Mechanism: castlecraft-engineer
's AuthenticationService
The foundation of authentication in projects generated by Architect is the AuthenticationService
from the castlecraft-engineer
library. This service is responsible for:
- Token Verification: Primarily through OpenID Connect (OIDC) flows.
- Fetching and validating JSON Web Key Sets (JWKS) for token signature verification.
- Introspecting tokens via a dedicated endpoint.
- Fetching user information from a UserInfo endpoint.
- Caching: Utilizes caching (configurable with Redis) to store JWKS responses, verified token payloads, and introspection results, minimizing external calls and improving performance.
- Backchannel Logout: Supports OpenID Connect Back-Channel Logout 1.0, allowing your application to be notified when a user's session is terminated elsewhere.
For a detailed understanding of the AuthenticationService
's capabilities, refer to the castlecraft-engineer
documentation.
Architect's Integration
When you scaffold a project using Castlecraft Architect, it sets up the necessary components to integrate with this AuthenticationService
.
1. get_current_user
Dependency
Architect generates a FastAPI dependency function, typically located at {app_root_name}/api/deps.py
, named get_current_user
. This function is the primary way your API endpoints will interact with the authentication system.
Here's a typical structure of the generated get_current_user
function:
async def get_current_user(
settings: Settings = Depends(get_settings),
authenticate: AuthenticationService = Depends(provide(AuthenticationService)),
authorization: Annotated[Optional[str], Header()] = None,
) -> Dict[str, Any]:
if not authorization:
return {
"sub": None,
settings.oidc_userinfo_roles_claim_name or "roles": ["anonymous"],
}
try:
scheme, token = authorization.split()
if scheme.lower() != "bearer":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication scheme",
)
except ValueError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authorization header format",
)
user = await authenticate.verify_user(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token or user not found",
)
user.setdefault(settings.oidc_userinfo_subject_claim_name, None)
user.setdefault(settings.oidc_userinfo_roles_claim_name, [])
return user
How it works:
- It expects an
Authorization
header in the formatBearer <token>
. - It extracts the token and uses the injected
AuthenticationService
instance'sverify_user(token)
method. - If the token is valid,
verify_user
returns a dictionary containing the user's claims (payload). - If the token is invalid or verification fails, an
HTTPException
(401 Unauthorized) is raised. - If no
Authorization
header is present, it defaults to an "anonymous" user structure. - It ensures that standard subject and roles claims (configurable via
Settings
) are present in the returned user dictionary, defaulting toNone
or an empty list if not provided by the token.
2. Configuration via Environment Variables
The AuthenticationService
(and therefore your generated application's authentication) is configured heavily through environment variables. These variables dictate how it interacts with your OIDC provider.
Key environment variables (as used by castlecraft-engineer
):
FRONTEND_OIDC_ENABLED
: Master switch, typically set totrue
to enable OIDC authentication.JWKS_URL
: URL to fetch the JSON Web Key Set.ALLOWED_AUD
: Comma-separated list of allowed audiences for ID token verification.INTROSPECT_URL
: URL of the token introspection endpoint.USERINFO_URL
: URL of the UserInfo endpoint.CLIENT_ID
,CLIENT_SECRET
: For client authentication if required by the OIDC provider.ENABLE_VERIFY_ID_TOKEN
,ENABLE_INTROSPECT_TOKEN
,ENABLE_FETCH_USERINFO
: Booleans to control which verification flows are active.- Variables for Backchannel Logout:
ENABLE_BACKCHANNEL_LOGOUT
,BACKCHANNEL_LOGOUT_TOKEN_ISS
, etc. - Cache configuration (e.g.,
CACHE_REDIS_URL
). - Claim names:
OIDC_USERINFO_SUBJECT_CLAIM_NAME
,OIDC_USERINFO_ROLES_CLAIM_NAME
.
Refer to the deploy/compose.yaml
in your Architect project for a list of these variables and the castlecraft-engineer
documentation for their detailed descriptions.
3. Usage in API Endpoints
To protect an API endpoint, you simply add Depends(get_current_user)
to its signature:
# Example FastAPI route
# from {app_root_name}.api.deps import get_current_user
# @app.get("/users/me")
# async def read_users_me(current_user: Dict[str, Any] = Depends(get_current_user)):
# # If execution reaches here, the user is authenticated (or anonymous if allowed).
# # 'current_user' contains the payload.
# # You can now use current_user for authorization checks or business logic.
# return current_user
If authentication fails (e.g., invalid token), the get_current_user
dependency will raise an HTTPException
, and the request will not reach your route handler.
Next Steps
Once authentication is set up and get_current_user
provides user information (including roles), the next step is to configure authorization to control what authenticated users can do. See the "Authorization Setup Overview" guide for more details.