mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
- Fix incorrect docstring method names in get_item (set_quantity -> increase_quantity, decrease_quantity, try_decrease_quantity) - Add asyncio.Lock to AsyncJWKSFetcher._fetch_jwks to deduplicate concurrent JWKS fetches on cold/expired cache - Add threading.Lock to SyncJWKSFetcher._fetch_jwks for same reason - Add threading.Lock to _get_or_create_memory_store to prevent race condition in token store registry - Fix README clone URL to point to upstream repo - Fix E2E test SyncJWKSFetcher constructor to use correct signature (jwks_url + http_client instead of project_id + base_url)
219 lines
5.8 KiB
Markdown
219 lines
5.8 KiB
Markdown
# Stack Auth Python SDK
|
|
|
|
Python SDK for [Stack Auth](https://stack-auth.com) — the open-source authentication platform. Provides `StackServerApp` for server-side user management, team operations, JWT verification, and more.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pip install stack-auth
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```python
|
|
from stack_auth import StackServerApp
|
|
|
|
app = StackServerApp(
|
|
project_id="your-project-id",
|
|
secret_server_key="your-secret-server-key",
|
|
)
|
|
|
|
# List users
|
|
users = app.list_users(limit=10)
|
|
for user in users.items:
|
|
print(f"{user.display_name} ({user.primary_email})")
|
|
|
|
# Create a user
|
|
user = app.create_user(
|
|
primary_email="[email protected]",
|
|
password="securepassword",
|
|
display_name="Jane Doe",
|
|
)
|
|
|
|
# Get a user
|
|
user = app.get_user("user-id")
|
|
|
|
# Update a user
|
|
updated = app.update_user("user-id", display_name="New Name")
|
|
|
|
# Delete a user
|
|
app.delete_user("user-id")
|
|
```
|
|
|
|
## Async Support
|
|
|
|
Every method has an async equivalent via `AsyncStackServerApp`:
|
|
|
|
```python
|
|
from stack_auth import AsyncStackServerApp
|
|
|
|
app = AsyncStackServerApp(
|
|
project_id="your-project-id",
|
|
secret_server_key="your-secret-server-key",
|
|
)
|
|
|
|
user = await app.get_user("user-id")
|
|
users = await app.list_users(limit=10)
|
|
team = await app.create_team(display_name="Engineering")
|
|
```
|
|
|
|
## Authentication
|
|
|
|
Verify incoming request tokens without framework-specific middleware:
|
|
|
|
```python
|
|
from stack_auth._auth import sync_authenticate_request
|
|
from stack_auth._jwt import SyncJWKSFetcher
|
|
|
|
fetcher = SyncJWKSFetcher(
|
|
project_id="your-project-id",
|
|
base_url="https://api.stack-auth.com",
|
|
)
|
|
|
|
# Works with any object that has a .headers mapping
|
|
result = sync_authenticate_request(request, fetcher=fetcher)
|
|
|
|
if result.status == "authenticated":
|
|
print(f"User: {result.user_id}")
|
|
else:
|
|
print("Not authenticated")
|
|
```
|
|
|
|
### FastAPI Example
|
|
|
|
```python
|
|
from fastapi import Depends, HTTPException, Request
|
|
from stack_auth._auth import sync_authenticate_request
|
|
from stack_auth._jwt import SyncJWKSFetcher
|
|
|
|
fetcher = SyncJWKSFetcher(project_id="...", base_url="https://api.stack-auth.com")
|
|
|
|
def get_user_id(request: Request) -> str:
|
|
result = sync_authenticate_request(request, fetcher=fetcher)
|
|
if result.status != "authenticated":
|
|
raise HTTPException(status_code=401)
|
|
return result.user_id
|
|
|
|
@app.get("/protected")
|
|
def protected_route(user_id: str = Depends(get_user_id)):
|
|
return {"user_id": user_id}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
All API errors raise typed exceptions:
|
|
|
|
```python
|
|
from stack_auth.errors import (
|
|
StackAuthError,
|
|
AuthenticationError,
|
|
NotFoundError,
|
|
ValidationError,
|
|
)
|
|
|
|
try:
|
|
user = app.get_user("invalid-id")
|
|
except NotFoundError as e:
|
|
print(f"Not found: {e.message}")
|
|
except AuthenticationError as e:
|
|
print(f"Auth failed: {e.code}")
|
|
except StackAuthError as e:
|
|
print(f"API error: {e.code} - {e.message}")
|
|
```
|
|
|
|
## Teams
|
|
|
|
```python
|
|
# Create a team
|
|
team = app.create_team(
|
|
display_name="Engineering",
|
|
creator_user_id="user-id",
|
|
)
|
|
|
|
# List teams
|
|
teams = app.list_teams(user_id="user-id")
|
|
|
|
# Manage members
|
|
app.add_team_member(team_id=team.id, user_id="another-user-id")
|
|
profiles = app.list_team_member_profiles(team_id=team.id)
|
|
|
|
# Permissions
|
|
app.grant_team_permission(
|
|
team_id=team.id,
|
|
user_id="user-id",
|
|
permission_id="admin",
|
|
)
|
|
```
|
|
|
|
## Self-Hosted
|
|
|
|
Point to your self-hosted Stack Auth instance:
|
|
|
|
```python
|
|
app = StackServerApp(
|
|
project_id="your-project-id",
|
|
secret_server_key="your-secret-key",
|
|
base_url="https://your-stack-auth.example.com",
|
|
)
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### StackServerApp / AsyncStackServerApp
|
|
|
|
**Constructor:**
|
|
- `project_id` (str) — Your Stack Auth project ID
|
|
- `secret_server_key` (str) — Server secret key from the dashboard
|
|
- `base_url` (str, optional) — API base URL (default: `https://api.stack-auth.com`)
|
|
- `publishable_client_key` (str, optional) — Required for projects with `requirePublishableClientKey`
|
|
- `token_store` (optional) — Token storage strategy
|
|
|
|
**User methods:** `get_user`, `list_users`, `create_user`, `update_user`, `delete_user`, `get_user_by_api_key`, `get_partial_user`
|
|
|
|
**Team methods:** `get_team`, `list_teams`, `create_team`, `update_team`, `delete_team`, `get_team_by_api_key`, `add_team_member`, `remove_team_member`, `list_team_member_profiles`, `get_team_member_profile`, `send_team_invitation`, `list_team_invitations`, `revoke_team_invitation`
|
|
|
|
**Permission methods:** `grant_team_permission`, `revoke_team_permission`, `list_team_permissions`, `grant_user_permission`, `revoke_user_permission`, `list_user_permissions`
|
|
|
|
**Session methods:** `list_sessions`, `get_session`, `revoke_session`
|
|
|
|
**Contact channel methods:** `send_contact_channel_verification`, `verify_contact_channel`, `check_contact_channel_verification`
|
|
|
|
**API key methods:** `create_user_api_key`, `list_user_api_keys`, `revoke_user_api_key`, `create_team_api_key`, `list_team_api_keys`, `revoke_team_api_key`, `check_api_key`
|
|
|
|
**OAuth methods:** `list_oauth_providers`, `create_oauth_provider`, `get_oauth_provider`, `list_connected_accounts`
|
|
|
|
**Payment methods:** `list_products`, `get_item`, `grant_product`, `cancel_subscription`
|
|
|
|
**Email methods:** `send_email`, `get_email_delivery_stats`
|
|
|
|
**Data vault:** `get_data_vault_store` → returns `DataVaultStore` with `get`, `set`, `delete`, `list_keys`
|
|
|
|
## Development
|
|
|
|
```bash
|
|
# Clone the repo
|
|
git clone https://github.com/stack-auth/stack-auth.git
|
|
cd stack-auth/sdks/implementations/python
|
|
|
|
# Install in dev mode
|
|
pip install -e ".[dev]"
|
|
|
|
# Run unit tests (no server needed)
|
|
python3 -m pytest tests/ -v
|
|
|
|
# Run E2E tests (requires running Stack Auth)
|
|
# First: pnpm start-deps && pnpm dev (from monorepo root)
|
|
STACK_E2E=1 python3 -m pytest tests/test_e2e.py -v -s
|
|
```
|
|
|
|
## Requirements
|
|
|
|
- Python >= 3.10
|
|
- httpx
|
|
- PyJWT[crypto]
|
|
- pydantic >= 2.7
|
|
|
|
## License
|
|
|
|
MIT — same as [Stack Auth](https://github.com/stack-auth/stack-auth).
|