mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
<!--
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 <n2d4xc@gmail.com>
371 lines
11 KiB
Plaintext
371 lines
11 KiB
Plaintext
---
|
|
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)
|
|
```
|
|
|
|
|
|
For more advanced authentication features, check out the [REST API documentation](../rest-api/overview.mdx).
|
|
|
|
|
|
|
|
|