mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
Adds oauth providers, fixes bottom page navigation with mobile suppor… (#726)
<!--
Fixes generation script, adds new oauth docs pages, fixes bottom
navigation, adds mobile support, sidebar changes.
-->
<!-- ELLIPSIS_HIDDEN -->
----
> [!IMPORTANT]
> This PR adds OAuth provider documentation, enhances mobile navigation,
and updates Python-specific documentation for Stack Auth.
>
> - **OAuth Providers**:
> - Adds documentation for GitHub, Google, Facebook, Microsoft, Spotify,
Discord, GitLab, Apple, Bitbucket, LinkedIn, and X (Twitter) in
`docs/templates/concepts/auth-providers/`.
> - Updates `docs/docs-platform.yml` to include new OAuth provider
pages.
> - **Mobile Support**:
> - Enhances bottom navigation for mobile devices in
`docs/src/app/(home)/layout.tsx` and `docs/src/app/api/layout.tsx`.
> - Introduces `AIChatDrawer` and `AuthPanel` components for
mobile-friendly interactions.
> - **Documentation Enhancements**:
> - Adds Python-specific documentation for user authentication and team
management in `docs/templates-python/concepts/`.
> - Updates `docs/templates-python/meta.json` to include new Python
documentation pages.
> - Refines search functionality and UI components for better user
experience.
>
> <sup>This description was created by </sup>[<img alt="Ellipsis"
src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup>
for bf759151d8. You can
[customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this
summary. It will automatically update as commits are pushed.</sup>
<!-- ELLIPSIS_HIDDEN -->
---------
Co-authored-by: Konsti Wohlwend <[email protected]>
This commit is contained in:
co-authored by
Konsti Wohlwend
parent
a00dcb1cb1
commit
a5734defba
@@ -0,0 +1,599 @@
|
||||
---
|
||||
title: "Teams Management"
|
||||
description: "Learn how to implement team functionality in your Python application using Stack Auth's REST API"
|
||||
---
|
||||
|
||||
After setting up your [Stack Auth helper function](../getting-started/setup.mdx), you can implement comprehensive team functionality in your Python application.
|
||||
|
||||
## Team Management
|
||||
|
||||
Stack Auth provides full team management capabilities including:
|
||||
- **Team Creation & Management** - Create and update teams with metadata
|
||||
- **Team Memberships** - Add and remove users from teams
|
||||
- **Team Invitations** - Send email invitations to join teams
|
||||
- **Team Permissions** - Control what team members can do
|
||||
- **Team Profiles** - Manage user profiles within team context
|
||||
|
||||
### Creating a Team
|
||||
|
||||
To create a new team:
|
||||
|
||||
```python
|
||||
def create_team(access_token, display_name, creator_user_id=None):
|
||||
"""
|
||||
Create a new team
|
||||
Returns the created team data
|
||||
"""
|
||||
body = {
|
||||
'display_name': display_name
|
||||
}
|
||||
|
||||
# Optionally specify a creator (only on server)
|
||||
if creator_user_id:
|
||||
body['creator_user_id'] = creator_user_id
|
||||
|
||||
response = stack_auth_request('POST', 'api/v1/teams',
|
||||
headers={'x-stack-access-token': access_token},
|
||||
json=body
|
||||
)
|
||||
|
||||
return {
|
||||
'id': response['id'],
|
||||
'display_name': response['display_name'],
|
||||
'profile_image_url': response['profile_image_url'],
|
||||
'client_metadata': response['client_metadata'],
|
||||
'client_read_only_metadata': response['client_read_only_metadata'],
|
||||
'created_at_millis': response.get('created_at_millis') # Server only
|
||||
}
|
||||
|
||||
# Example usage
|
||||
team_data = create_team(
|
||||
access_token=access_token,
|
||||
display_name="Engineering Team"
|
||||
)
|
||||
team_id = team_data['id']
|
||||
print(f"Created team: {team_data['display_name']}")
|
||||
```
|
||||
|
||||
### Listing Teams
|
||||
|
||||
Get all teams for the current user:
|
||||
|
||||
```python
|
||||
def list_user_teams(access_token):
|
||||
"""
|
||||
List all teams that the current user is a member of
|
||||
"""
|
||||
response = stack_auth_request('GET', 'api/v1/teams?user_id=me',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
|
||||
return response['items']
|
||||
|
||||
def list_all_teams():
|
||||
"""
|
||||
List all teams in the project (server access only)
|
||||
"""
|
||||
response = stack_auth_request('GET', 'api/v1/teams')
|
||||
return response['items']
|
||||
|
||||
# Example usage
|
||||
user_teams = list_user_teams(access_token)
|
||||
print(f"User is member of {len(user_teams)} teams")
|
||||
|
||||
# Server-side: list all teams
|
||||
all_teams = list_all_teams()
|
||||
print(f"Total teams in project: {len(all_teams)}")
|
||||
```
|
||||
|
||||
### Getting Team Information
|
||||
|
||||
Retrieve details about a specific team:
|
||||
|
||||
```python
|
||||
def get_team(access_token, team_id):
|
||||
"""
|
||||
Get information about a specific team
|
||||
"""
|
||||
response = stack_auth_request('GET', f'api/v1/teams/{team_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
|
||||
return {
|
||||
'id': response['id'],
|
||||
'display_name': response['display_name'],
|
||||
'profile_image_url': response['profile_image_url'],
|
||||
'client_metadata': response['client_metadata'],
|
||||
'client_read_only_metadata': response['client_read_only_metadata']
|
||||
}
|
||||
|
||||
# Example usage
|
||||
team_info = get_team(access_token, team_id)
|
||||
print(f"Team: {team_info['display_name']}")
|
||||
```
|
||||
|
||||
### Updating Team Information
|
||||
|
||||
Update team details (requires `$update_team` permission):
|
||||
|
||||
```python
|
||||
def update_team(access_token, team_id, **updates):
|
||||
"""
|
||||
Update team information
|
||||
Requires $update_team permission
|
||||
"""
|
||||
# Filter out None values
|
||||
body = {k: v for k, v in updates.items() if v is not None}
|
||||
|
||||
response = stack_auth_request('PATCH', f'api/v1/teams/{team_id}',
|
||||
headers={'x-stack-access-token': access_token},
|
||||
json=body
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# Example usage
|
||||
updated_team = update_team(
|
||||
access_token=access_token,
|
||||
team_id=team_id,
|
||||
display_name="Updated Engineering Team",
|
||||
profile_image_url="https://example.com/team-logo.png",
|
||||
client_metadata={
|
||||
"department": "Engineering",
|
||||
"location": "San Francisco"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Team Membership Management
|
||||
|
||||
### Adding Members to a Team
|
||||
|
||||
Add users to a team (server access required):
|
||||
|
||||
```python
|
||||
def add_team_member(team_id, user_id):
|
||||
"""
|
||||
Add a user to a team (server access only)
|
||||
"""
|
||||
response = stack_auth_request('POST', f'api/v1/team-memberships/{team_id}/{user_id}',
|
||||
json={}
|
||||
)
|
||||
return response
|
||||
|
||||
# Example usage
|
||||
add_team_member(team_id, user_id)
|
||||
print(f"Added user {user_id} to team {team_id}")
|
||||
```
|
||||
|
||||
### Removing Members from a Team
|
||||
|
||||
Remove users from a team (requires `$remove_members` permission):
|
||||
|
||||
```python
|
||||
def remove_team_member(access_token, team_id, user_id):
|
||||
"""
|
||||
Remove a user from a team
|
||||
Requires $remove_members permission
|
||||
"""
|
||||
stack_auth_request('DELETE', f'api/v1/team-memberships/{team_id}/{user_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
|
||||
# Example usage
|
||||
remove_team_member(access_token, team_id, user_id)
|
||||
print(f"Removed user {user_id} from team {team_id}")
|
||||
```
|
||||
|
||||
### Getting Team Members
|
||||
|
||||
List all members of a team:
|
||||
|
||||
```python
|
||||
def get_team_members(access_token, team_id):
|
||||
"""
|
||||
Get all members of a team with their profiles
|
||||
Requires $read_members permission
|
||||
"""
|
||||
response = stack_auth_request('GET', f'api/v1/team-member-profiles?team_id={team_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
|
||||
return response['items']
|
||||
|
||||
# Example usage
|
||||
members = get_team_members(access_token, team_id)
|
||||
for member in members:
|
||||
print(f"Member: {member['display_name']} ({member['user_id']})")
|
||||
```
|
||||
|
||||
## Team Invitations
|
||||
|
||||
### Sending Team Invitations
|
||||
|
||||
Invite users to join a team via email:
|
||||
|
||||
```python
|
||||
def send_team_invitation(access_token, team_id, email, callback_url):
|
||||
"""
|
||||
Send an invitation to join a team
|
||||
Requires $invite_members permission
|
||||
"""
|
||||
response = stack_auth_request('POST', 'api/v1/team-invitations/send-code',
|
||||
headers={'x-stack-access-token': access_token},
|
||||
json={
|
||||
'email': email,
|
||||
'team_id': team_id,
|
||||
'callback_url': callback_url
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
'success': response['success'],
|
||||
'invitation_id': response['id']
|
||||
}
|
||||
|
||||
# Example usage
|
||||
invitation_result = send_team_invitation(
|
||||
access_token=access_token,
|
||||
team_id=team_id,
|
||||
email="[email protected]",
|
||||
callback_url="https://yourapp.com/join-team"
|
||||
)
|
||||
print(f"Invitation sent: {invitation_result['invitation_id']}")
|
||||
```
|
||||
|
||||
### Accepting Team Invitations
|
||||
|
||||
Complete the invitation process when a user clicks the invitation link:
|
||||
|
||||
```python
|
||||
def accept_team_invitation(code):
|
||||
"""
|
||||
Accept a team invitation using the code from the invitation email
|
||||
"""
|
||||
response = stack_auth_request('POST', 'api/v1/team-invitations/accept',
|
||||
json={'code': code}
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# Example usage (when user clicks invitation link)
|
||||
accept_team_invitation(invitation_code)
|
||||
print("User successfully joined the team!")
|
||||
```
|
||||
|
||||
### Listing Team Invitations
|
||||
|
||||
Get pending invitations for a team:
|
||||
|
||||
```python
|
||||
def list_team_invitations(access_token, team_id):
|
||||
"""
|
||||
List pending invitations for a team
|
||||
Requires $invite_members permission
|
||||
"""
|
||||
response = stack_auth_request('GET', f'api/v1/team-invitations?team_id={team_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
|
||||
return response['items']
|
||||
|
||||
# Example usage
|
||||
invitations = list_team_invitations(access_token, team_id)
|
||||
for invitation in invitations:
|
||||
print(f"Pending invitation for: {invitation['recipient_email']}")
|
||||
```
|
||||
|
||||
## Team Permissions Management
|
||||
|
||||
### Granting Team Permissions
|
||||
|
||||
Give specific permissions to team members:
|
||||
|
||||
```python
|
||||
def grant_team_permission(team_id, user_id, permission_id):
|
||||
"""
|
||||
Grant a permission to a user in a team (server access only)
|
||||
"""
|
||||
response = stack_auth_request('POST', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}',
|
||||
json={}
|
||||
)
|
||||
return response
|
||||
|
||||
# Example usage
|
||||
grant_team_permission(team_id, user_id, "$update_team")
|
||||
grant_team_permission(team_id, user_id, "$invite_members")
|
||||
print(f"Granted permissions to user {user_id}")
|
||||
```
|
||||
|
||||
### Revoking Team Permissions
|
||||
|
||||
Remove permissions from team members:
|
||||
|
||||
```python
|
||||
def revoke_team_permission(team_id, user_id, permission_id):
|
||||
"""
|
||||
Revoke a permission from a user in a team (server access only)
|
||||
"""
|
||||
stack_auth_request('DELETE', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}')
|
||||
|
||||
# Example usage
|
||||
revoke_team_permission(team_id, user_id, "$update_team")
|
||||
print(f"Revoked permission from user {user_id}")
|
||||
```
|
||||
|
||||
### Checking Team Permissions
|
||||
|
||||
Check if a user has specific permissions in a team:
|
||||
|
||||
```python
|
||||
def check_team_permission(access_token, team_id, user_id, permission_id):
|
||||
"""
|
||||
Check if a user has a specific permission in a team
|
||||
"""
|
||||
try:
|
||||
response = stack_auth_request('GET', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
if "TEAM_PERMISSION_NOT_FOUND" in str(e):
|
||||
return False
|
||||
raise e
|
||||
|
||||
# Example usage
|
||||
can_update = check_team_permission(access_token, team_id, user_id, "$update_team")
|
||||
if can_update:
|
||||
print("User can update the team")
|
||||
else:
|
||||
print("User cannot update the team")
|
||||
```
|
||||
|
||||
### Listing User Permissions
|
||||
|
||||
Get all permissions a user has in a team:
|
||||
|
||||
```python
|
||||
def list_user_team_permissions(access_token, team_id, user_id="me"):
|
||||
"""
|
||||
List all permissions a user has in a team
|
||||
"""
|
||||
response = stack_auth_request('GET', f'api/v1/team-permissions/{team_id}/{user_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
|
||||
return response['items']
|
||||
|
||||
# Example usage
|
||||
permissions = list_user_team_permissions(access_token, team_id)
|
||||
permission_ids = [p['id'] for p in permissions]
|
||||
print(f"User permissions: {permission_ids}")
|
||||
```
|
||||
|
||||
## Team Member Profiles
|
||||
|
||||
### Managing Team Member Profiles
|
||||
|
||||
Users can have different display names and profile information within each team:
|
||||
|
||||
```python
|
||||
def update_team_member_profile(access_token, team_id, user_id="me", **profile_data):
|
||||
"""
|
||||
Update a user's profile within a team context
|
||||
"""
|
||||
response = stack_auth_request('PATCH', f'api/v1/team-member-profiles/{team_id}/{user_id}',
|
||||
headers={'x-stack-access-token': access_token},
|
||||
json=profile_data
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def get_team_member_profile(access_token, team_id, user_id="me"):
|
||||
"""
|
||||
Get a user's profile within a team context
|
||||
"""
|
||||
response = stack_auth_request('GET', f'api/v1/team-member-profiles/{team_id}/{user_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# Example usage
|
||||
# Update current user's profile in the team
|
||||
updated_profile = update_team_member_profile(
|
||||
access_token=access_token,
|
||||
team_id=team_id,
|
||||
display_name="John Doe (Engineering Lead)",
|
||||
profile_image_url="https://example.com/john-avatar.png"
|
||||
)
|
||||
|
||||
# Get the updated profile
|
||||
profile = get_team_member_profile(access_token, team_id)
|
||||
print(f"Team profile: {profile['display_name']}")
|
||||
```
|
||||
|
||||
### Deleting Teams
|
||||
|
||||
Remove a team entirely (requires `$delete_team` permission):
|
||||
|
||||
```python
|
||||
def delete_team(access_token, team_id):
|
||||
"""
|
||||
Delete a team (requires $delete_team permission)
|
||||
"""
|
||||
response = stack_auth_request('DELETE', f'api/v1/teams/{team_id}',
|
||||
headers={'x-stack-access-token': access_token}
|
||||
)
|
||||
return response
|
||||
|
||||
# Example usage
|
||||
delete_team(access_token, team_id)
|
||||
print(f"Team {team_id} deleted successfully")
|
||||
```
|
||||
|
||||
## Complete Team Management Example
|
||||
|
||||
Here's a comprehensive example that demonstrates a full team management workflow:
|
||||
|
||||
```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 TeamManager:
|
||||
def __init__(self, access_token):
|
||||
self.access_token = access_token
|
||||
|
||||
def create_and_setup_team(self, name, member_emails):
|
||||
"""Create a team and invite members"""
|
||||
# Create the team
|
||||
team = create_team(self.access_token, name)
|
||||
team_id = team['id']
|
||||
|
||||
print(f"Created team: {name} (ID: {team_id})")
|
||||
|
||||
# Send invitations to members
|
||||
invitation_results = []
|
||||
for email in member_emails:
|
||||
try:
|
||||
result = send_team_invitation(
|
||||
self.access_token,
|
||||
team_id,
|
||||
email,
|
||||
"https://yourapp.com/join-team"
|
||||
)
|
||||
invitation_results.append((email, result['invitation_id']))
|
||||
print(f"Invited {email}")
|
||||
except Exception as e:
|
||||
print(f"Failed to invite {email}: {e}")
|
||||
|
||||
return team, invitation_results
|
||||
|
||||
def manage_team_permissions(self, team_id, admin_user_ids):
|
||||
"""Grant admin permissions to specific users"""
|
||||
admin_permissions = ["$update_team", "$invite_members", "$remove_members"]
|
||||
|
||||
for user_id in admin_user_ids:
|
||||
for permission in admin_permissions:
|
||||
try:
|
||||
grant_team_permission(team_id, user_id, permission)
|
||||
print(f"Granted {permission} to {user_id}")
|
||||
except Exception as e:
|
||||
print(f"Failed to grant {permission} to {user_id}: {e}")
|
||||
|
||||
def get_team_overview(self, team_id):
|
||||
"""Get complete team information"""
|
||||
# Get team details
|
||||
team_info = get_team(self.access_token, team_id)
|
||||
|
||||
# Get team members
|
||||
try:
|
||||
members = get_team_members(self.access_token, team_id)
|
||||
except Exception:
|
||||
members = [] # User might not have read_members permission
|
||||
|
||||
# Get pending invitations
|
||||
try:
|
||||
invitations = list_team_invitations(self.access_token, team_id)
|
||||
except Exception:
|
||||
invitations = [] # User might not have invite_members permission
|
||||
|
||||
return {
|
||||
'team': team_info,
|
||||
'members': members,
|
||||
'pending_invitations': invitations
|
||||
}
|
||||
|
||||
# Example usage
|
||||
team_manager = TeamManager(access_token)
|
||||
|
||||
# Create a new team with initial members
|
||||
team, invitations = team_manager.create_and_setup_team(
|
||||
name="Product Team",
|
||||
member_emails=["[email protected]", "[email protected]", "[email protected]"]
|
||||
)
|
||||
|
||||
# Make some users team admins (server-side operation)
|
||||
admin_users = ["user-id-1", "user-id-2"]
|
||||
team_manager.manage_team_permissions(team['id'], admin_users)
|
||||
|
||||
# Get team overview
|
||||
overview = team_manager.get_team_overview(team['id'])
|
||||
print(f"\nTeam Overview:")
|
||||
print(f"Name: {overview['team']['display_name']}")
|
||||
print(f"Members: {len(overview['members'])}")
|
||||
print(f"Pending Invitations: {len(overview['pending_invitations'])}")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Common team-related errors you might encounter:
|
||||
|
||||
```python
|
||||
def handle_team_errors(func):
|
||||
"""Decorator to handle common team operation errors"""
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
error_message = str(e)
|
||||
|
||||
if "TEAM_PERMISSION_REQUIRED" in error_message:
|
||||
print("Insufficient permissions for this team operation")
|
||||
elif "TEAM_MEMBERSHIP_NOT_FOUND" in error_message:
|
||||
print("User is not a member of this team")
|
||||
elif "TEAM_NOT_FOUND" in error_message:
|
||||
print("Team not found")
|
||||
elif "TEAM_MEMBERSHIP_ALREADY_EXISTS" in error_message:
|
||||
print("User is already a member of this team")
|
||||
elif "USER_NOT_FOUND" in error_message:
|
||||
print("User not found")
|
||||
else:
|
||||
print(f"Team operation error: {error_message}")
|
||||
|
||||
raise e
|
||||
return wrapper
|
||||
|
||||
@handle_team_errors
|
||||
def safe_add_member(team_id, user_id):
|
||||
return add_team_member(team_id, user_id)
|
||||
|
||||
@handle_team_errors
|
||||
def safe_send_invitation(access_token, team_id, email, callback_url):
|
||||
return send_team_invitation(access_token, team_id, email, callback_url)
|
||||
```
|
||||
|
||||
## Common Team Permission IDs
|
||||
|
||||
Stack Auth includes several built-in team permissions:
|
||||
|
||||
- **`$update_team`** - Edit team information and metadata
|
||||
- **`$delete_team`** - Delete the entire team
|
||||
- **`$invite_members`** - Send invitations to new members
|
||||
- **`$remove_members`** - Remove members from the team
|
||||
- **`$read_members`** - View team member list
|
||||
- **`team_member`** - Basic team membership (automatically granted)
|
||||
|
||||
For more advanced team features, check out the [REST API documentation](../rest-api/overview.mdx).
|
||||
@@ -0,0 +1,370 @@
|
||||
---
|
||||
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="[email protected]",
|
||||
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("[email protected]", "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("[email protected]", "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="[email protected]",
|
||||
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)
|
||||
```
|
||||
|
||||
|
||||
For more advanced authentication features, check out the [REST API documentation](../rest-api/overview.mdx).
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: Setup
|
||||
---
|
||||
|
||||
<Info>
|
||||
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).
|
||||
</Info>
|
||||
|
||||
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, and Django.
|
||||
|
||||
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`.
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
### 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")
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Make a request
|
||||
|
||||
Next, create a helper function to make requests to the Stack Auth API:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
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()
|
||||
|
||||
print(stack_auth_request('GET', '/api/v1/projects/current'))
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Handle user access tokens
|
||||
|
||||
If you're building a backend server, most likely you'll want to use the currently signed in user's access token. Most normally, you would send this with all your requests to the backend in an HTTP header.
|
||||
|
||||
In Stack Auth's JavaScript SDK, you can retrieve the access token [from the `stackClientApp` object](/sdk/types/user#currentusergetauthjson). Then, you can use said access token to make requests to Stack Auth:
|
||||
|
||||
```python
|
||||
access_token = # access token retrieved from the JavaScript SDK
|
||||
|
||||
print(stack_auth_request('GET', '/api/v1/users/me', headers={
|
||||
'x-stack-access-token': access_token,
|
||||
}))
|
||||
```
|
||||
|
||||
Here's how to extract and use the access token in your Python backend:
|
||||
|
||||
**Extract access token from request headers:**
|
||||
|
||||
```python
|
||||
def get_access_token_from_request(request):
|
||||
"""Extract access token from x-stack-access-token header"""
|
||||
return request.headers.get('x-stack-access-token')
|
||||
```
|
||||
|
||||
**Use the access token with Stack Auth:**
|
||||
|
||||
```python
|
||||
def get_current_user_from_token(access_token):
|
||||
"""Get current user information using their access token"""
|
||||
return stack_auth_request('GET', '/api/v1/users/me', headers={
|
||||
'x-stack-access-token': access_token,
|
||||
})
|
||||
```
|
||||
|
||||
**Example usage in a Flask route:**
|
||||
|
||||
```python
|
||||
@app.route('/api/user-profile')
|
||||
def user_profile():
|
||||
access_token = get_access_token_from_request(request)
|
||||
if not access_token:
|
||||
return {'error': 'No access token provided'}, 401
|
||||
|
||||
try:
|
||||
user_info = get_current_user_from_token(access_token)
|
||||
return {'user': user_info}
|
||||
except Exception as e:
|
||||
return {'error': 'Invalid access token'}, 401
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Done!
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you have Stack Auth set up in your Python application, you can:
|
||||
|
||||
1. **[User Authentication](../concepts/user-authentication.mdx)** - Learn how to sign up and sign in users
|
||||
2. **[Teams Management](../concepts/teams-management.mdx)** - Implement team functionality
|
||||
|
||||
Check out the [REST API documentation](../rest-api/overview.mdx) to learn more about the available endpoints and how to use them in your Python application.
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"title": "Python Documentation",
|
||||
"description": "Stack Auth for Python applications using the REST API",
|
||||
"pages": [
|
||||
"overview",
|
||||
"faq",
|
||||
"---Getting Started---",
|
||||
"getting-started/setup",
|
||||
"---Concepts---",
|
||||
"concepts/user-authentication",
|
||||
"concepts/teams-management",
|
||||
"---Integration---",
|
||||
"---Other---",
|
||||
"others/self-host",
|
||||
"others/cli-authentication"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user