fastmcp/docs/servers/authorization.mdx

348 lines
11 KiB
Text

---
title: Authorization
sidebarTitle: Authorization
description: Control access to components using callable-based authorization checks that filter visibility and enforce permissions.
icon: shield-halved
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="3.0.0" />
Authorization controls what authenticated users can do with your FastMCP server. While [authentication](/servers/auth/authentication) verifies identity (who you are), authorization determines access (what you can do). FastMCP provides a callable-based authorization system that works at both the component level and globally via middleware.
The authorization model centers on a simple concept: callable functions that receive context about the current request and return `True` to allow access or `False` to deny it. Multiple checks combine with AND logic, meaning all checks must pass for access to be granted.
<Note>
Authorization relies on OAuth tokens which are only available with HTTP transports (SSE, Streamable HTTP). In STDIO mode, there's no OAuth mechanism, so `get_access_token()` returns `None` and all auth checks are skipped.
</Note>
## Auth Checks
An auth check is any callable that accepts an `AuthContext` and returns a boolean. The `AuthContext` provides access to the current token (if any) and the component being accessed.
```python
from fastmcp.server.auth import AuthContext
def my_custom_check(ctx: AuthContext) -> bool:
# ctx.token is AccessToken | None
# ctx.component is the Tool, Resource, or Prompt being accessed
return ctx.token is not None and "special" in ctx.token.scopes
```
FastMCP provides three built-in auth checks that cover common authorization patterns.
### require_auth
The simplest check verifies that any valid authentication token is present. Unauthenticated requests are denied.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import require_auth
mcp = FastMCP("Protected Server")
@mcp.tool(auth=require_auth)
def protected_operation() -> str:
"""Only accessible to authenticated users."""
return "Success"
```
### require_scopes
For scope-based authorization, `require_scopes` checks that the token contains all specified OAuth scopes. When multiple scopes are provided, all must be present (AND logic).
```python
from fastmcp import FastMCP
from fastmcp.server.auth import require_scopes
mcp = FastMCP("Scoped Server")
@mcp.tool(auth=require_scopes("admin"))
def admin_operation() -> str:
"""Requires the 'admin' scope."""
return "Admin action completed"
@mcp.tool(auth=require_scopes("read", "write"))
def read_write_operation() -> str:
"""Requires both 'read' AND 'write' scopes."""
return "Read/write action completed"
```
### restrict_tag
Tag-based restrictions apply scope requirements conditionally. If a component has the specified tag, the token must have the required scopes. Components without the tag are unaffected.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import restrict_tag
from fastmcp.server.middleware import AuthMiddleware
mcp = FastMCP(
"Tagged Server",
middleware=[
AuthMiddleware(auth=restrict_tag("admin", scopes=["admin"]))
]
)
@mcp.tool(tags={"admin"})
def admin_tool() -> str:
"""Tagged 'admin', so requires 'admin' scope."""
return "Admin only"
@mcp.tool(tags={"public"})
def public_tool() -> str:
"""Not tagged 'admin', so no scope required by the restriction."""
return "Anyone can access"
```
### Combining Checks
Multiple auth checks can be combined by passing a list. All checks must pass for authorization to succeed (AND logic).
```python
from fastmcp import FastMCP
from fastmcp.server.auth import require_auth, require_scopes
mcp = FastMCP("Combined Auth Server")
@mcp.tool(auth=[require_auth, require_scopes("admin")])
def secure_admin_action() -> str:
"""Requires authentication AND the 'admin' scope."""
return "Secure admin action"
```
### Custom Auth Checks
Any callable that accepts `AuthContext` and returns `bool` can serve as an auth check. This enables authorization logic based on token claims, component metadata, or external systems.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import AuthContext
mcp = FastMCP("Custom Auth Server")
def require_premium_user(ctx: AuthContext) -> bool:
"""Check for premium user status in token claims."""
if ctx.token is None:
return False
return ctx.token.claims.get("premium", False) is True
def require_access_level(minimum_level: int):
"""Factory function for level-based authorization."""
def check(ctx: AuthContext) -> bool:
if ctx.token is None:
return False
user_level = ctx.token.claims.get("level", 0)
return user_level >= minimum_level
return check
@mcp.tool(auth=require_premium_user)
def premium_feature() -> str:
"""Only for premium users."""
return "Premium content"
@mcp.tool(auth=require_access_level(5))
def advanced_feature() -> str:
"""Requires access level 5 or higher."""
return "Advanced feature"
```
Auth checks can raise exceptions for explicit denial with custom messages:
- **`AuthorizationError`**: Propagates with its custom message, useful for explaining why access was denied
- **Other exceptions**: Masked for security (logged internally, treated as denial)
```python
from fastmcp.server.auth import AuthContext
from fastmcp.exceptions import AuthorizationError
def require_verified_email(ctx: AuthContext) -> bool:
"""Require verified email with explicit denial message."""
if ctx.token is None:
raise AuthorizationError("Authentication required")
if not ctx.token.claims.get("email_verified"):
raise AuthorizationError("Email verification required")
return True
```
## Component-Level Authorization
The `auth` parameter on decorators controls visibility of individual components. When auth checks fail for the current request, the component is hidden from list responses—it simply doesn't appear.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import require_auth, require_scopes
mcp = FastMCP("Component Auth Server")
@mcp.tool(auth=require_auth)
def authenticated_tool() -> str:
"""Only visible to authenticated users."""
return "Authenticated"
@mcp.resource("secret://data", auth=require_scopes("read"))
def secret_resource() -> str:
"""Only visible to users with 'read' scope."""
return "Secret data"
@mcp.prompt(auth=require_scopes("admin"))
def admin_prompt() -> str:
"""Only visible to users with 'admin' scope."""
return "Admin prompt content"
```
<Warning>
Component-level `auth` only controls visibility in list operations. It does not block direct access. Use `AuthMiddleware` to enforce authorization on execution.
</Warning>
## Server-Level Authorization
For server-wide authorization enforcement, use `AuthMiddleware`. This middleware applies auth checks globally to all components—filtering list responses and blocking unauthorized execution.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import require_auth
from fastmcp.server.middleware import AuthMiddleware
mcp = FastMCP(
"Enforced Auth Server",
middleware=[AuthMiddleware(auth=require_auth)]
)
@mcp.tool
def any_tool() -> str:
"""Requires authentication to see AND call."""
return "Protected"
```
### Filtering vs Enforcement
| Behavior | Component-level `auth` | `AuthMiddleware` |
|----------|------------------------|------------------|
| Filters list responses | Yes | Yes |
| Blocks execution | No | Yes (raises `AuthorizationError`) |
Component-level auth is useful for hiding components from unauthorized users while still allowing advanced clients to access them directly. `AuthMiddleware` provides complete enforcement by raising `AuthorizationError` when unauthorized requests attempt execution.
### Tag-Based Global Authorization
A common pattern uses `restrict_tag` with `AuthMiddleware` to apply scope requirements based on component tags.
```python
from fastmcp import FastMCP
from fastmcp.server.auth import restrict_tag
from fastmcp.server.middleware import AuthMiddleware
mcp = FastMCP(
"Tag-Based Auth Server",
middleware=[
AuthMiddleware(auth=restrict_tag("admin", scopes=["admin"])),
AuthMiddleware(auth=restrict_tag("write", scopes=["write"])),
]
)
@mcp.tool(tags={"admin"})
def delete_all_data() -> str:
"""Requires 'admin' scope."""
return "Deleted"
@mcp.tool(tags={"write"})
def update_record(id: str, data: str) -> str:
"""Requires 'write' scope."""
return f"Updated {id}"
@mcp.tool
def read_record(id: str) -> str:
"""No tag restrictions, accessible to all."""
return f"Record {id}"
```
## Accessing Tokens in Tools
Tools can access the current authentication token using `get_access_token()` from `fastmcp.server.dependencies`. This enables tools to make decisions based on user identity or permissions beyond simple authorization checks.
```python
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_access_token
mcp = FastMCP("Token Access Server")
@mcp.tool
def personalized_greeting() -> str:
"""Greet the user based on their token claims."""
token = get_access_token()
if token is None:
return "Hello, guest!"
name = token.claims.get("name", "user")
return f"Hello, {name}!"
@mcp.tool
def user_dashboard() -> dict:
"""Return user-specific data based on token."""
token = get_access_token()
if token is None:
return {"error": "Not authenticated"}
return {
"client_id": token.client_id,
"scopes": token.scopes,
"claims": token.claims,
}
```
## Reference
### AccessToken
The `AccessToken` object contains information extracted from the OAuth token.
| Property | Type | Description |
|----------|------|-------------|
| `token` | `str` | The raw token string |
| `client_id` | `str \| None` | OAuth client identifier |
| `scopes` | `list[str]` | Granted OAuth scopes |
| `expires_at` | `datetime \| None` | Token expiration time |
| `claims` | `dict[str, Any]` | All JWT claims or custom token data |
### AuthContext
The `AuthContext` dataclass is passed to all auth check functions.
| Property | Type | Description |
|----------|------|-------------|
| `token` | `AccessToken \| None` | Current access token, or `None` if unauthenticated |
| `component` | `Tool \| Resource \| Prompt` | The component being accessed |
Access to the component object enables authorization decisions based on metadata like tags, name, or custom properties.
```python
from fastmcp.server.auth import AuthContext
def require_matching_tag(ctx: AuthContext) -> bool:
"""Require a scope matching each of the component's tags."""
if ctx.token is None:
return False
user_scopes = set(ctx.token.scopes)
return ctx.component.tags.issubset(user_scopes)
```
### Imports
```python
from fastmcp.server.auth import (
AccessToken, # Token with .token, .client_id, .scopes, .expires_at, .claims
AuthContext, # Context with .token, .component
AuthCheck, # Type alias: Callable[[AuthContext], bool]
require_auth, # Built-in: requires any valid token
require_scopes, # Built-in: requires specific scopes
restrict_tag, # Built-in: tag-based scope requirements
run_auth_checks, # Utility: run checks with AND logic
)
from fastmcp.server.middleware import AuthMiddleware
```