diff --git a/docs/package.json b/docs/package.json
index 43e75c03a..36f0b30a3 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -18,6 +18,9 @@
"clear-docs": "node scripts/clear-docs.js"
},
"dependencies": {
+ "@ai-sdk/google": "^1.2.21",
+ "@ai-sdk/openai": "^1.3.22",
+ "@ai-sdk/react": "^1.2.12",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-presence": "^1.1.4",
@@ -26,6 +29,7 @@
"@radix-ui/react-tabs": "^1.1.12",
"@stackframe/stack": "workspace:^",
"@xyflow/react": "^12.6.4",
+ "ai": "^4.3.16",
"class-variance-authority": "^0.7.1",
"fumadocs-core": "15.3.3",
"fumadocs-mdx": "11.6.4",
@@ -45,7 +49,8 @@
"remark-gfm": "^4.0.1",
"remark-mdx": "^3.1.0",
"shiki": "^3.4.2",
- "tailwind-merge": "^3.3.0"
+ "tailwind-merge": "^3.3.0",
+ "zod": "^3.23.8"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.7",
diff --git a/docs/src/app/(home)/page.tsx b/docs/src/app/(home)/page.tsx
index e0af3b40c..7e5b832c6 100644
--- a/docs/src/app/(home)/page.tsx
+++ b/docs/src/app/(home)/page.tsx
@@ -1,3 +1,4 @@
+import AIChat from '@/components/chat/ai-chat';
import DocsSelector from '@/components/homepage/iconHover';
export default function HomePage() {
@@ -34,6 +35,11 @@ export default function HomePage() {
+
+ {/* AI Chat Assistant */}
+
diff --git a/docs/src/app/api/chat/route.ts b/docs/src/app/api/chat/route.ts
new file mode 100644
index 000000000..842c9a66e
--- /dev/null
+++ b/docs/src/app/api/chat/route.ts
@@ -0,0 +1,119 @@
+import { createGoogleGenerativeAI } from '@ai-sdk/google';
+import { streamText } from 'ai';
+
+// Allow streaming responses up to 30 seconds
+export const maxDuration = 30;
+
+// Configure Google Gemini with custom API key variable
+const google = createGoogleGenerativeAI({
+ apiKey: process.env.GOOGLE_AI_API_KEY,
+});
+
+export function errorHandler(error: unknown) {
+ if (error == null) {
+ return 'unknown error';
+ }
+
+ if (typeof error === 'string') {
+ return error;
+ }
+
+ if (error instanceof Error) {
+ return error.message;
+ }
+
+ return JSON.stringify(error);
+}
+
+async function getStackAuthDocs() {
+ try {
+ // Get the base URL from the request or use localhost for development
+ const baseUrl = process.env.VERCEL_URL
+ ? `https://${process.env.VERCEL_URL}`
+ : 'http://localhost:8104';
+
+ console.log('Fetching docs from:', `${baseUrl}/llms.txt`);
+
+ const response = await fetch(`${baseUrl}/llms.txt`);
+ console.log('Docs fetch response status:', response.status);
+
+ if (!response.ok) {
+ console.error('Failed to fetch Stack Auth docs:', response.status, response.statusText);
+ return null;
+ }
+
+ const docsContent = await response.text();
+ console.log('Docs content length:', docsContent?.length || 0);
+ console.log('Docs content preview:', docsContent?.substring(0, 200) + '...');
+
+ return docsContent;
+ } catch (error) {
+ console.error('Error fetching Stack Auth docs:', error);
+ return null;
+ }
+}
+
+export async function POST(req: Request) {
+ try {
+ const { messages } = await req.json();
+
+ console.log('Received messages:', messages);
+ console.log('Google AI API Key configured:', !!process.env.GOOGLE_AI_API_KEY);
+
+ // Fetch Stack Auth documentation
+ const stackAuthDocs = await getStackAuthDocs();
+
+ // Create system message with documentation context
+ const systemMessage = {
+ role: 'system' as const,
+ content: `You are a technical AI assistant specializing in Stack Auth, a complete authentication solution. You are helping developers who want detailed, technical guidance.
+
+IMPORTANT INSTRUCTIONS:
+- You can ONLY answer questions about Stack Auth and authentication topics
+- If someone asks about anything else, politely redirect them to ask about Stack Auth
+- Your audience is DEVELOPERS who need in-depth technical information
+- Provide comprehensive, detailed answers with code examples when available
+- Include specific implementation details, configuration options, and best practices
+- Reference exact function names, parameters, and code snippets from the documentation
+- Don't oversimplify - developers want the full technical depth
+- When explaining concepts, include relevant code examples and implementation details
+- If there are multiple approaches, explain the different options and their trade-offs
+
+${stackAuthDocs ? `
+Here is the complete Stack Auth documentation with detailed examples and technical information:
+
+${stackAuthDocs}
+
+Use this documentation to provide comprehensive, technical answers. Include code examples, configuration details, and implementation specifics. Developers are looking for actionable, detailed guidance, not basic overviews.
+` : 'Stack Auth documentation could not be loaded. Please answer based on general Stack Auth knowledge, but provide detailed technical information for developers.'}
+
+Remember: Your responses should match the technical depth and detail level of the Stack Auth documentation. Provide code examples, configuration snippets, and comprehensive implementation guidance.`
+ };
+
+ // Prepend system message to the conversation
+ const messagesWithContext = [systemMessage, ...messages];
+
+ const result = streamText({
+ model: google('gemini-1.5-flash'),
+ messages: messagesWithContext,
+ });
+
+ return result.toDataStreamResponse({
+ getErrorMessage: errorHandler,
+ });
+ } catch (error) {
+ console.error('Error in chat API:', error);
+ return new Response(
+ JSON.stringify({
+ error: 'Internal server error',
+ details: error instanceof Error ? error.message : 'Unknown error'
+ }),
+ {
+ status: 500,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ }
+ );
+ }
+}
diff --git a/docs/src/components/chat/ai-chat.tsx b/docs/src/components/chat/ai-chat.tsx
new file mode 100644
index 000000000..b7d85ff79
--- /dev/null
+++ b/docs/src/components/chat/ai-chat.tsx
@@ -0,0 +1,120 @@
+'use client';
+
+import { useChat } from '@ai-sdk/react';
+import { Bot, Send, User } from 'lucide-react';
+import { useState } from 'react';
+
+export default function AIChat() {
+ const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
+ const [isOpen, setIsOpen] = useState(false);
+
+ // Debug logging
+ console.log('Messages:', messages);
+
+ return (
+
+ {/* Toggle Button */}
+
+
+
+
+ {/* Chat Interface */}
+ {isOpen && (
+
+
+
+
Stack Auth AI Assistant
+
+ Ask questions about Stack Auth documentation
+
+
+
+ {/* Messages Container */}
+
+ {messages.length === 0 && (
+
+
+
Welcome to Stack Auth!
+
+ Ask me anything about authentication, documentation, or how to get started.
+
+
+ )}
+
+ {messages.map(message => (
+
+ {message.role === 'assistant' && (
+
+
+
+ )}
+
+
+
+ {message.content}
+
+
+
+ {message.role === 'user' && (
+
+
+
+ )}
+
+ ))}
+
+ {isLoading && (
+
+ )}
+
+
+ {/* Input Form */}
+
+
+ )}
+
+ );
+}
diff --git a/docs/src/components/homepage/iconHover.tsx b/docs/src/components/homepage/iconHover.tsx
index 7f5470905..c9dc4e4b8 100644
--- a/docs/src/components/homepage/iconHover.tsx
+++ b/docs/src/components/homepage/iconHover.tsx
@@ -227,259 +227,74 @@ const DocsIcon3D: React.FC = ({
}
};
- // Add custom CSS for the floating animation
- const floatingDotsStyle = `
- @keyframes float-up {
- 0% {
- transform: translateY(0px) scale(1);
- opacity: 0.8;
- filter: blur(0px);
- }
- 50% {
- opacity: 0.6;
- filter: blur(0.5px);
- }
- 100% {
- transform: translateY(-200px) scale(0.3);
- opacity: 0;
- filter: blur(1px);
- }
- }
- .animate-float-up {
- animation: float-up 3s ease-out infinite;
- }
-`;
-
return (
- <>
-
-
-
- {platformSections.map((section) => (
+
+
+ {platformSections.map((section) => (
+
setHoveredSection(section.id)}
+ onMouseLeave={() => setHoveredSection(null)}
+ onClick={() => handleSectionClick(section)}
+ >
setHoveredSection(section.id)}
- onMouseLeave={() => setHoveredSection(null)}
- onClick={() => handleSectionClick(section)}
- >
-
- {/* Animated background gradient */}
+ style={{
+ borderColor: hoveredSection === section.id ? section.color : undefined,
+ }}
+ >
+ {/* Icon Container */}
+
-
- {/* Continuous upward floating dots effect */}
-
- {hoveredSection === section.id && (
-
- {[...Array(12)].map((_, i) => (
-
- ))}
-
- )}
-
-
- {/* 3D Icon Container */}
-
- {/* Enhanced shadow layers */}
-
-
- {/* Main icon with enhanced styling */}
-
-
- {React.cloneElement(section.icon as React.ReactElement, {
- size: 24,
- strokeWidth: hoveredSection === section.id ? 2.5 : 2,
- })}
-
-
-
-
- {/* Enhanced Title */}
-
- {section.title}
-
-
- {/* Enhanced Description */}
-
- {section.description}
-
-
- {/* Enhanced hover indicator */}
-
-
- {/* Enhanced corner accents with glow */}
-
-
-
-
- {/* New: Diagonal accent line */}
-
-
+ {React.cloneElement(section.icon as React.ReactElement, {
+ size: 20,
+ strokeWidth: 2,
+ })}
+
+ {/* Title */}
+
+ {section.title}
+
+
+ {/* Description */}
+
+ {section.description}
+
- ))}
-
+
+ ))}
- >
+
);
};
diff --git a/docs/src/components/mdx/card.tsx b/docs/src/components/mdx/card.tsx
index 2b828aa02..7014057b6 100644
--- a/docs/src/components/mdx/card.tsx
+++ b/docs/src/components/mdx/card.tsx
@@ -1,6 +1,6 @@
'use client';
-import { Code, FileText, Link as LinkIcon, Play, Puzzle, Settings, Shield, UserCheck } from 'lucide-react';
+import { Code, FileText, Link as LinkIcon, Play, Puzzle, Settings, Shield, User, UserCheck } from 'lucide-react';
import Link from 'next/link';
import { type ReactNode } from 'react';
import { cn } from '../../lib/cn';
@@ -17,6 +17,7 @@ const iconMap: Record
> = {
'user-check': UserCheck,
'link': LinkIcon,
'shield-check': Shield,
+ 'user': User,
};
export type CardProps = {
diff --git a/docs/templates-python/authentication/api-setup.mdx b/docs/templates-python/authentication/api-setup.mdx
deleted file mode 100644
index 43bbfa14a..000000000
--- a/docs/templates-python/authentication/api-setup.mdx
+++ /dev/null
@@ -1,165 +0,0 @@
----
-title: API Setup & Configuration
----
-
-This guide covers the essential setup for using Stack Auth's REST API in your Python application. Stack Auth provides a REST API for managing users, sessions, and authentication flows.
-
-## Prerequisites
-
-Before you begin, make sure you have:
-- A Stack Auth [project](https://app.stack-auth.com/projects) created
-- Python 3.7+ installed
-- `requests` library (`pip install requests`)
-
-## Environment Configuration
-
-First, set up your API credentials. Get these from your Stack Auth dashboard:
-
-```bash
-# .env file
-STACK_PROJECT_ID=your_project_id
-STACK_PUBLISHABLE_KEY=your_publishable_key
-STACK_SECRET_KEY=your_secret_key
-STACK_API_URL=https://api.stack-auth.com
-```
-
-## Basic API Client Setup
-
-Create a basic API client to handle Stack Auth requests:
-
-```python
-import os
-import requests
-from typing import Dict, Any, Optional
-
-class StackAuthClient:
- def __init__(self):
- self.project_id = os.getenv('STACK_PROJECT_ID')
- self.secret_key = os.getenv('STACK_SECRET_KEY')
- self.publishable_key = os.getenv('STACK_PUBLISHABLE_KEY')
- self.api_url = os.getenv('STACK_API_URL', 'https://api.stack-auth.com')
-
- if not all([self.project_id, self.secret_key]):
- raise ValueError("Missing required Stack Auth credentials")
-
- def _make_request(
- self,
- method: str,
- endpoint: str,
- data: Optional[Dict[str, Any]] = None,
- headers: Optional[Dict[str, str]] = None
- ) -> requests.Response:
- """Make authenticated request to Stack Auth API"""
- url = f"{self.api_url}/api/v1{endpoint}"
-
- # Add authentication headers
- auth_headers = {
- 'X-Stack-Project-Id': self.project_id,
- 'X-Stack-Secret-Key': self.secret_key,
- 'Content-Type': 'application/json'
- }
-
- if headers:
- auth_headers.update(headers)
-
- response = requests.request(
- method=method,
- url=url,
- json=data,
- headers=auth_headers
- )
-
- # Handle common error cases
- if response.status_code == 401:
- raise Exception("Authentication failed - check your API credentials")
- elif response.status_code == 403:
- raise Exception("Forbidden - insufficient permissions")
- elif not response.ok:
- raise Exception(f"API request failed: {response.status_code} - {response.text}")
-
- return response
-
- def get(self, endpoint: str, **kwargs) -> requests.Response:
- return self._make_request('GET', endpoint, **kwargs)
-
- def post(self, endpoint: str, data: Dict[str, Any] = None, **kwargs) -> requests.Response:
- return self._make_request('POST', endpoint, data, **kwargs)
-
- def put(self, endpoint: str, data: Dict[str, Any] = None, **kwargs) -> requests.Response:
- return self._make_request('PUT', endpoint, data, **kwargs)
-
- def delete(self, endpoint: str, **kwargs) -> requests.Response:
- return self._make_request('DELETE', endpoint, **kwargs)
-
-# Initialize the client
-stack_client = StackAuthClient()
-```
-
-## Testing Your Setup
-
-Test your API connection with a simple request:
-
-```python
-def test_connection():
- try:
- # Test with a simple API call
- response = stack_client.get('/users')
- print("✅ Connection successful!")
- print(f"Found {len(response.json().get('users', []))} users")
- return True
- except Exception as e:
- print(f"❌ Connection failed: {e}")
- return False
-
-# Run the test
-if __name__ == "__main__":
- test_connection()
-```
-
-## Error Handling
-
-Implement proper error handling for production use:
-
-```python
-import logging
-from typing import Optional
-
-logger = logging.getLogger(__name__)
-
-class StackAuthError(Exception):
- """Base exception for Stack Auth errors"""
- pass
-
-class StackAuthClient:
- # ... previous code ...
-
- def safe_request(
- self,
- method: str,
- endpoint: str,
- data: Optional[Dict[str, Any]] = None
- ) -> Optional[Dict[str, Any]]:
- """Make a safe request with comprehensive error handling"""
- try:
- response = self._make_request(method, endpoint, data)
- return response.json()
- except requests.exceptions.ConnectionError:
- logger.error("Failed to connect to Stack Auth API")
- raise StackAuthError("Network connection failed")
- except requests.exceptions.Timeout:
- logger.error("Request to Stack Auth API timed out")
- raise StackAuthError("Request timed out")
- except Exception as e:
- logger.error(f"Stack Auth API error: {e}")
- raise StackAuthError(f"API request failed: {e}")
-```
-
-## Next Steps
-
-With your API client set up, you can now:
-
-- [Authenticate users](./user-sessions) with your application
-- [Handle OAuth flows](./oauth-flows) for social login
-- [Validate server-side sessions](./server-validation)
-
-For detailed API reference, see the [REST API documentation](/api/overview).
diff --git a/docs/templates-python/authentication/index.mdx b/docs/templates-python/authentication/index.mdx
index 77699fd94..d038a8dfc 100644
--- a/docs/templates-python/authentication/index.mdx
+++ b/docs/templates-python/authentication/index.mdx
@@ -1,56 +1,252 @@
---
-title: "Authentication Flows"
-description: "Learn how to implement authentication flows in your Python application using Stack Auth's REST API"
+title: Authentication
---
-This section covers the core authentication patterns and flows you'll need to implement secure authentication in your Python application using Stack Auth's REST API.
-## Core Authentication
+This guide covers how to implement authentication in your Python application using Stack Auth. We'll walk through all the available authentication methods and show you how to handle user sessions.
-
-
- Set up your Python client and configure API credentials for Stack Auth integration.
-
-
-
- Handle user session management, validation, and lifecycle in Python applications.
-
-
-
- Implement OAuth authentication flows for social login providers via the REST API.
-
-
-
- Validate user tokens and authenticate requests on your Python backend.
-
-
+## Overview
-## Authentication Patterns
+Stack Auth supports multiple authentication methods that you can enable or disable based on your needs:
-Each authentication flow addresses specific use cases:
+- **[Email/Password](./password.mdx)** - Traditional email and password authentication
+- **[Magic Links (OTP)](./magic-links.mdx)** - Passwordless authentication via email
+- **[OAuth Providers](./oauth.mdx)** - Sign in with Google, GitHub, Facebook, and more
+- **[Passkeys](./passkeys.mdx)** - Modern biometric authentication
+- **[Anonymous Users](./anonymous.mdx)** - Guest users that can be upgraded later
-- **API Setup**: Foundation for all authentication operations
-- **User Sessions**: Managing authenticated user state and session lifecycle
-- **OAuth Flows**: Social login integration (Google, GitHub, etc.)
-- **Server Validation**: Securing your API endpoints and validating requests
+## Basic Authentication Flow
-## Getting Started
+All authentication methods in Stack Auth follow a similar pattern:
-Start with [API Setup & Configuration](./api-setup) to establish your Python client, then proceed to the specific authentication flow that matches your application's needs.
+1. **Initiate authentication** - Start the sign-in/sign-up process
+2. **User verification** - User completes authentication (password, email link, OAuth, etc.)
+3. **Receive tokens** - Get access and refresh tokens
+4. **Manage session** - Use tokens to authenticate API requests
-For framework-specific integration examples, see the [Framework Integration](../integration) section.
+Here's the basic structure for handling authentication:
+
+```python
+from typing import Optional
+import requests
+
+class AuthManager:
+ def __init__(self, stack_auth_client):
+ self.client = stack_auth_client
+ self.current_user_token: Optional[str] = None
+
+ def authenticate_request(self, access_token: str) -> dict:
+ """Verify and get user info from access token"""
+ return self.client._make_request(
+ 'GET',
+ '/api/v1/users/me',
+ access_type="client",
+ access_token=access_token
+ )
+
+ def refresh_token(self, refresh_token: str) -> dict:
+ """Refresh an expired access token"""
+ return self.client._make_request(
+ 'POST',
+ '/api/v1/auth/sessions/current/refresh',
+ access_type="client",
+ json={'refresh_token': refresh_token}
+ )
+
+ def sign_out(self, access_token: str) -> bool:
+ """Sign out the current user"""
+ try:
+ self.client._make_request(
+ 'DELETE',
+ '/api/v1/auth/sessions/current',
+ access_type="client",
+ access_token=access_token
+ )
+ return True
+ except Exception:
+ return False
+
+# Initialize auth manager
+auth_manager = AuthManager(stack_auth)
+```
+
+## Token Management
+
+Stack Auth uses two types of tokens:
+
+- **Access Token** - Short-lived token for API requests (typically 1 hour)
+- **Refresh Token** - Long-lived token to get new access tokens (typically 30 days)
+
+### Storing Tokens Securely
+
+For web applications, store tokens securely:
+
+```python
+from flask import session
+import jwt
+from datetime import datetime
+
+def store_tokens_securely(access_token: str, refresh_token: str):
+ """Store tokens in secure HTTP-only cookies or session"""
+ # For session storage
+ session['access_token'] = access_token
+ session['refresh_token'] = refresh_token
+
+ # For cookie storage (recommended for web apps)
+ # Set secure, HTTP-only cookies
+ response.set_cookie(
+ 'access_token',
+ access_token,
+ httponly=True,
+ secure=True,
+ samesite='Strict'
+ )
+
+def get_current_user_from_token(access_token: str) -> Optional[dict]:
+ """Extract user info from access token"""
+ try:
+ # Verify token and get user
+ user_info = auth_manager.authenticate_request(access_token)
+ return user_info
+ except Exception:
+ return None
+```
+
+## Middleware for Authentication
+
+Here's how to create middleware that automatically handles authentication:
+
+```python
+from functools import wraps
+from flask import request, jsonify, g
+
+def require_auth(f):
+ """Decorator that requires authentication"""
+ @wraps(f)
+ def decorated_function(*args, **kwargs):
+ # Get token from header or cookie
+ access_token = request.headers.get('Authorization')
+ if access_token and access_token.startswith('Bearer '):
+ access_token = access_token[7:]
+ elif 'access_token' in request.cookies:
+ access_token = request.cookies['access_token']
+ else:
+ return jsonify({'error': 'Authentication required'}), 401
+
+ # Verify token and get user
+ try:
+ user_info = auth_manager.authenticate_request(access_token)
+ g.current_user = user_info
+ g.access_token = access_token
+ return f(*args, **kwargs)
+ except Exception as e:
+ return jsonify({'error': 'Invalid token'}), 401
+
+ return decorated_function
+
+def optional_auth(f):
+ """Decorator for optional authentication"""
+ @wraps(f)
+ def decorated_function(*args, **kwargs):
+ access_token = request.headers.get('Authorization')
+ if access_token and access_token.startswith('Bearer '):
+ access_token = access_token[7:]
+ try:
+ user_info = auth_manager.authenticate_request(access_token)
+ g.current_user = user_info
+ g.access_token = access_token
+ except Exception:
+ g.current_user = None
+ else:
+ g.current_user = None
+
+ return f(*args, **kwargs)
+
+ return decorated_function
+```
+
+## Usage Examples
+
+### Protected Route
+
+```python
+from flask import Flask, g, jsonify
+
+app = Flask(__name__)
+
+@app.route('/profile')
+@require_auth
+def get_profile():
+ """Get current user's profile - requires authentication"""
+ return jsonify({
+ 'user': g.current_user,
+ 'message': 'This is your profile'
+ })
+
+@app.route('/public')
+@optional_auth
+def public_endpoint():
+ """Public endpoint with optional user context"""
+ if g.current_user:
+ return jsonify({
+ 'message': f'Hello {g.current_user.get("display_name", "User")}!'
+ })
+ else:
+ return jsonify({'message': 'Hello anonymous user!'})
+```
+
+### Manual Token Refresh
+
+```python
+def refresh_user_session(refresh_token: str) -> Optional[dict]:
+ """Refresh user session and return new tokens"""
+ try:
+ token_response = auth_manager.refresh_token(refresh_token)
+ return {
+ 'access_token': token_response['access_token'],
+ 'refresh_token': token_response['refresh_token']
+ }
+ except Exception as e:
+ print(f"Token refresh failed: {e}")
+ return None
+```
+
+## Error Handling
+
+Common authentication errors and how to handle them:
+
+```python
+class AuthError(Exception):
+ def __init__(self, message: str, status_code: int = 401):
+ self.message = message
+ self.status_code = status_code
+ super().__init__(self.message)
+
+def handle_auth_errors(func):
+ """Decorator to handle common authentication errors"""
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except requests.exceptions.HTTPError as e:
+ if e.response.status_code == 401:
+ raise AuthError("Invalid or expired token", 401)
+ elif e.response.status_code == 403:
+ raise AuthError("Insufficient permissions", 403)
+ else:
+ raise AuthError("Authentication error", e.response.status_code)
+ except Exception as e:
+ raise AuthError(f"Unexpected error: {str(e)}", 500)
+ return wrapper
+```
+
+## Next Steps
+
+Choose the authentication method that best fits your application:
+
+1. **[Email/Password Authentication](./password.mdx)** - Most common, familiar to users
+2. **[Magic Link Authentication](./magic-links.mdx)** - Modern passwordless approach
+3. **[OAuth Authentication](./oauth.mdx)** - Easy sign-in with existing accounts
+4. **[Passkey Authentication](./passkeys.mdx)** - Most secure, future-proof option
+5. **[Anonymous Authentication](./anonymous.mdx)** - Great for guest users
+
+You can also enable multiple methods simultaneously to give users options.
diff --git a/docs/templates-python/authentication/meta.json b/docs/templates-python/authentication/meta.json
index 2e17eee92..94a4af185 100644
--- a/docs/templates-python/authentication/meta.json
+++ b/docs/templates-python/authentication/meta.json
@@ -2,6 +2,7 @@
"title": "Authentication",
"description": "Authentication flows and patterns for Python applications",
"pages": [
+ "index",
"api-setup",
"user-sessions",
"oauth-flows",
diff --git a/docs/templates-python/authentication/user-authentication.mdx b/docs/templates-python/authentication/user-authentication.mdx
new file mode 100644
index 000000000..04812ba52
--- /dev/null
+++ b/docs/templates-python/authentication/user-authentication.mdx
@@ -0,0 +1,378 @@
+---
+title: "User Authentication"
+description: "Learn how to implement user authentication in your Python application using Stack Auth's REST API"
+---
+
+After creating a [helper function](../getting-started/setup.mdx) to make requests to the Stack Auth API, you can start using the API to authenticate users.
+
+## User Authentication
+
+Stack Auth supports multiple authentication methods:
+- **Password Authentication** - Email and password
+- **OTP Authentication** - Magic links and one-time passwords via email
+- **OAuth Authentication** - Social logins (GitHub, Google, etc.)
+- **Passkey Authentication** - WebAuthn/FIDO2 passkeys
+- **Multi-Factor Authentication** - TOTP-based MFA
+
+### Sign Up with Email and Password
+
+To create a new user account with email and password:
+
+```python
+def sign_up_with_password(email, password, verification_callback_url):
+ """
+ Sign up a new user with email and password
+ Returns access_token, refresh_token, and user_id
+ """
+ response = stack_auth_request('POST', 'api/v1/auth/password/sign-up', json={
+ 'email': email,
+ 'password': password,
+ 'verification_callback_url': verification_callback_url # URL where user will verify email
+ })
+
+ return {
+ 'access_token': response['access_token'],
+ 'refresh_token': response['refresh_token'],
+ 'user_id': response['user_id']
+ }
+
+# Example usage
+user_data = sign_up_with_password(
+ email="user@example.com",
+ password="secure_password_123",
+ verification_callback_url="https://yourapp.com/verify-email"
+)
+```
+
+### Sign In with Email and Password
+
+To authenticate an existing user:
+
+```python
+def sign_in_with_password(email, password):
+ """
+ Sign in an existing user with email and password
+ Returns access_token, refresh_token, and user_id
+ """
+ response = stack_auth_request('POST', 'api/v1/auth/password/sign-in', json={
+ 'email': email,
+ 'password': password
+ })
+
+ return {
+ 'access_token': response['access_token'],
+ 'refresh_token': response['refresh_token'],
+ 'user_id': response['user_id']
+ }
+
+# Example usage
+user_data = sign_in_with_password("user@example.com", "secure_password_123")
+access_token = user_data['access_token']
+refresh_token = user_data['refresh_token']
+```
+
+### Sign In with OTP (Magic Link)
+
+For passwordless authentication using one-time passwords:
+
+```python
+def send_otp_code(email, callback_url):
+ """
+ Send an OTP code to the user's email
+ Returns a nonce that must be stored for verification
+ """
+ response = stack_auth_request('POST', 'api/v1/auth/otp/send-sign-in-code', json={
+ 'email': email,
+ 'callback_url': callback_url # URL where user will complete sign-in
+ })
+
+ return response['nonce']
+
+def verify_otp_code(nonce, six_digit_code):
+ """
+ Verify the OTP code and complete sign-in
+ The code parameter should be the 6-digit code + nonce concatenated
+ Returns access_token, refresh_token, and user_id
+ """
+ # The verification code is the 6-digit code followed by the nonce
+ verification_code = six_digit_code + nonce
+
+ response = stack_auth_request('POST', 'api/v1/auth/otp/sign-in', json={
+ 'code': verification_code
+ })
+
+ return {
+ 'access_token': response['access_token'],
+ 'refresh_token': response['refresh_token'],
+ 'user_id': response['user_id'],
+ 'is_new_user': response['is_new_user'] # True if this was a sign-up
+ }
+
+# Example usage
+nonce = send_otp_code("user@example.com", "https://yourapp.com/verify-otp")
+# Store the nonce temporarily, user receives email with 6-digit code
+# When user enters the code:
+user_data = verify_otp_code(nonce, "123456")
+```
+
+### Get Current User Information
+
+To retrieve information about the currently authenticated user:
+
+```python
+def get_current_user(access_token):
+ """
+ Get the current user's information using their access token
+ """
+ response = stack_auth_request('GET', 'api/v1/users/me', headers={
+ 'x-stack-access-token': access_token
+ })
+
+ return {
+ 'id': response['id'],
+ 'display_name': response['display_name'],
+ 'primary_email': response['primary_email'],
+ 'primary_email_verified': response['primary_email_verified'],
+ 'profile_image_url': response['profile_image_url'],
+ 'signed_up_at_millis': response['signed_up_at_millis'],
+ 'last_active_at_millis': response['last_active_at_millis'],
+ 'oauth_providers': response['oauth_providers'],
+ 'has_password': response['has_password'],
+ 'auth_with_email': response['auth_with_email']
+ }
+
+# Example usage
+user_info = get_current_user(access_token)
+print(f"Welcome, {user_info['display_name']}!")
+```
+
+### Refresh Access Token
+
+Access tokens expire after a short time (typically 10 minutes). Use the refresh token to get a new access token:
+
+```python
+def refresh_access_token(refresh_token):
+ """
+ Get a new access token using the refresh token
+ """
+ response = stack_auth_request('POST', 'api/v1/auth/sessions/current/refresh', headers={
+ 'x-stack-refresh-token': refresh_token
+ })
+
+ return response['access_token']
+
+# Example usage
+new_access_token = refresh_access_token(refresh_token)
+```
+
+### Sign Out (Revoke Session)
+
+To sign out a user by revoking their session:
+
+```python
+def get_user_sessions(access_token):
+ """
+ Get all active sessions for the current user
+ """
+ response = stack_auth_request('GET', 'api/v1/auth/sessions', headers={
+ 'x-stack-access-token': access_token
+ })
+
+ return response['items']
+
+def sign_out_session(access_token, session_id):
+ """
+ Sign out by deleting a specific session
+ """
+ stack_auth_request('DELETE', f'api/v1/auth/sessions/{session_id}', headers={
+ 'x-stack-access-token': access_token
+ })
+
+def sign_out_current_user(access_token):
+ """
+ Sign out the current user by finding and deleting their current session
+ """
+ sessions = get_user_sessions(access_token)
+ current_session = next((s for s in sessions if s['is_current_session']), None)
+
+ if current_session:
+ # Note: This will fail with "CannotDeleteCurrentSession" error
+ # Instead, you should invalidate the tokens on your client side
+ pass
+
+ # In practice, you would typically just discard the tokens client-side
+ print("User signed out (tokens should be discarded client-side)")
+
+# Example usage
+sign_out_current_user(access_token)
+```
+
+## Complete Authentication Flow Example
+
+Here's a complete example that demonstrates a full authentication flow:
+
+```python
+import os
+import requests
+
+# Setup (from setup guide)
+stack_project_id = os.getenv("STACK_PROJECT_ID")
+stack_publishable_client_key = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
+stack_secret_server_key = os.getenv("STACK_SECRET_SERVER_KEY")
+
+def stack_auth_request(method, endpoint, **kwargs):
+ res = requests.request(
+ method,
+ f'https://api.stack-auth.com/{endpoint}',
+ headers={
+ 'x-stack-access-type': 'server', # or 'client' if you're only accessing the client API
+ 'x-stack-project-id': stack_project_id,
+ 'x-stack-publishable-client-key': stack_publishable_client_key,
+ 'x-stack-secret-server-key': stack_secret_server_key, # not necessary if access type is 'client'
+ **kwargs.pop('headers', {}),
+ },
+ **kwargs,
+ )
+ if res.status_code >= 400:
+ raise Exception(f"Stack Auth API request failed with {res.status_code}: {res.text}")
+ return res.json()
+
+class StackAuthClient:
+ def __init__(self):
+ self.access_token = None
+ self.refresh_token = None
+ self.user_id = None
+
+ def sign_up(self, email, password, verification_callback_url):
+ """Sign up a new user"""
+ response = stack_auth_request('POST', 'api/v1/auth/password/sign-up', json={
+ 'email': email,
+ 'password': password,
+ 'verification_callback_url': verification_callback_url
+ })
+
+ self.access_token = response['access_token']
+ self.refresh_token = response['refresh_token']
+ self.user_id = response['user_id']
+
+ return response
+
+ def sign_in(self, email, password):
+ """Sign in an existing user"""
+ response = stack_auth_request('POST', 'api/v1/auth/password/sign-in', json={
+ 'email': email,
+ 'password': password
+ })
+
+ self.access_token = response['access_token']
+ self.refresh_token = response['refresh_token']
+ self.user_id = response['user_id']
+
+ return response
+
+ def get_current_user(self):
+ """Get current user information"""
+ if not self.access_token:
+ raise Exception("No access token available")
+
+ return stack_auth_request('GET', 'api/v1/users/me', headers={
+ 'x-stack-access-token': self.access_token
+ })
+
+ def refresh_token_if_needed(self):
+ """Refresh the access token"""
+ if not self.refresh_token:
+ raise Exception("No refresh token available")
+
+ response = stack_auth_request('POST', 'api/v1/auth/sessions/current/refresh', headers={
+ 'x-stack-refresh-token': self.refresh_token
+ })
+
+ self.access_token = response['access_token']
+ return response
+
+ def sign_out(self):
+ """Sign out by clearing tokens"""
+ self.access_token = None
+ self.refresh_token = None
+ self.user_id = None
+
+# Example usage
+auth_client = StackAuthClient()
+
+# Sign up a new user
+try:
+ auth_client.sign_up(
+ email="newuser@example.com",
+ password="secure_password_123",
+ verification_callback_url="https://yourapp.com/verify"
+ )
+ print("User signed up successfully!")
+except Exception as e:
+ print(f"Sign up failed: {e}")
+
+# Get user information
+try:
+ user_info = auth_client.get_current_user()
+ print(f"Logged in as: {user_info['primary_email']}")
+except Exception as e:
+ print(f"Failed to get user info: {e}")
+
+# Refresh token when needed
+try:
+ auth_client.refresh_token_if_needed()
+ print("Token refreshed successfully!")
+except Exception as e:
+ print(f"Token refresh failed: {e}")
+
+# Sign out
+auth_client.sign_out()
+print("User signed out!")
+```
+
+## Error Handling
+
+Common errors you might encounter:
+
+```python
+def handle_auth_errors(func):
+ """Decorator to handle common authentication errors"""
+ def wrapper(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except Exception as e:
+ error_message = str(e)
+
+ if "EmailPasswordMismatch" in error_message:
+ print("Invalid email or password")
+ elif "AccessTokenExpired" in error_message:
+ print("Access token expired, please refresh")
+ elif "UserWithEmailAlreadyExists" in error_message:
+ print("User with this email already exists")
+ elif "PasswordAuthenticationNotEnabled" in error_message:
+ print("Password authentication is not enabled for this project")
+ else:
+ print(f"Authentication error: {error_message}")
+
+ raise e
+ return wrapper
+
+@handle_auth_errors
+def safe_sign_in(email, password):
+ return sign_in_with_password(email, password)
+```
+
+## Next Steps
+
+Now that you have user authentication working, you can:
+
+1. **[Manage Users](../user-management/index.mdx)** - Update user profiles, manage user data
+2. **[Handle Teams](../team-management/index.mdx)** - Implement team functionality
+3. **[Set up OAuth](../oauth/index.mdx)** - Add social login providers
+4. **[Framework Integration](../integration/index.mdx)** - See examples for Flask, Django, and FastAPI
+
+For more advanced authentication features, check out the [REST API documentation](../rest-api/overview.mdx).
+
+
+
+
diff --git a/docs/templates-python/getting-started/setup.mdx b/docs/templates-python/getting-started/setup.mdx
new file mode 100644
index 000000000..2d5e44c5b
--- /dev/null
+++ b/docs/templates-python/getting-started/setup.mdx
@@ -0,0 +1,127 @@
+---
+title: Setup
+---
+
+
+Welcome to the Python setup guide. If you're looking for guides for other frameworks, check out the [Next.js SDK Setup](/next/getting-started/setup), [React SDK Setup](/react/getting-started/setup), or the [JavaScript SDK Setup](/js/getting-started/setup).
+
+
+Our recommended way to use Stack Auth with Python is with the [REST API](../rest-api/overview.mdx). It provides a fully documented way to interact with Stack Auth from any Python framework, including Flask, FastAPI, Django, and others.
+
+For the purpose of this guide, we will use the `requests` library to make HTTP requests to the Stack Auth API. If you haven't already, you can install it in your environment with `pip install requests`.
+
+
+
+ ### Create API keys
+
+
+ First, create an account on [the Stack Auth dashboard](https://app.stack-auth.com/projects), and copy your project ID, publishable client key, and secret server key into a safe place (eg. environment variables).
+
+ From there, you can access them in your Python code. You can then read them like this:
+
+ ```python
+ import os
+
+ stack_project_id = os.getenv("STACK_PROJECT_ID")
+ stack_publishable_client_key = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
+ stack_secret_server_key = os.getenv("STACK_SECRET_SERVER_KEY")
+ ```
+
+
+ ### Create a Stack Auth client
+
+
+ Next, create a helper class to make requests to the Stack Auth API. This will handle authentication headers and error handling:
+
+ ```python
+ import requests
+ import os
+ from typing import Optional, Dict, Any
+
+ class StackAuthClient:
+ def __init__(self):
+ self.base_url = "https://api.stack-auth.com"
+ self.project_id = os.getenv("STACK_PROJECT_ID")
+ self.publishable_client_key = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
+ self.secret_server_key = os.getenv("STACK_SECRET_SERVER_KEY")
+
+ if not all([self.project_id, self.publishable_client_key]):
+ raise ValueError("Missing required Stack Auth environment variables")
+
+ def _make_request(
+ self,
+ method: str,
+ endpoint: str,
+ access_type: str = "server",
+ access_token: Optional[str] = None,
+ **kwargs
+ ) -> Dict[str, Any]:
+ """Make a request to the Stack Auth API"""
+ headers = {
+ 'x-stack-access-type': access_type,
+ 'x-stack-project-id': self.project_id,
+ 'x-stack-publishable-client-key': self.publishable_client_key,
+ 'Content-Type': 'application/json',
+ **kwargs.pop('headers', {}),
+ }
+
+ # Add server key for server access
+ if access_type == "server" and self.secret_server_key:
+ headers['x-stack-secret-server-key'] = self.secret_server_key
+
+ # Add access token for authenticated requests
+ if access_token:
+ headers['x-stack-access-token'] = access_token
+
+ url = f"{self.base_url}/{endpoint.lstrip('/')}"
+
+ response = requests.request(method, url, headers=headers, **kwargs)
+
+ if response.status_code >= 400:
+ raise Exception(f"Stack Auth API request failed with {response.status_code}: {response.text}")
+
+ return response.json() if response.content else {}
+
+ # Initialize the client
+ stack_auth = StackAuthClient()
+ ```
+
+
+ ### Test the connection
+
+
+ Test that your API keys work correctly by fetching the current project:
+
+ ```python
+ # Test the connection
+ try:
+ project_info = stack_auth._make_request('GET', '/api/v1/projects/current')
+ print("✅ Successfully connected to Stack Auth!")
+ print(f"Project: {project_info.get('display_name', 'Unknown')}")
+ except Exception as e:
+ print(f"❌ Failed to connect: {e}")
+ ```
+
+
+ ### Done!
+
+
+
+## What's Next?
+
+Now that you have Stack Auth set up in your Python application, you can:
+
+1. **[Implement Authentication](../authentication/index.mdx)** - Learn how to sign up and sign in users
+2. **[Manage Users](../user-management/index.mdx)** - Create, update, and retrieve user information
+3. **[Handle Teams](../team-management/index.mdx)** - Implement team functionality
+4. **[Framework Integration](../integration/index.mdx)** - See specific examples for Flask, Django, and FastAPI
+
+## Framework-Specific Setup
+
+While the REST API approach works with any Python framework, we also provide specific integration guides:
+
+- **[Flask Integration](../integration/flask.mdx)** - Complete Flask setup with middleware
+- **[Django Integration](../integration/django.mdx)** - Django setup with custom authentication backend
+- **[FastAPI Integration](../integration/fastapi.mdx)** - FastAPI setup with dependency injection
+
+Choose the guide that matches your framework, or continue with the general REST API approach if you're using a different framework.