Use when adding login, logout, and user profile to a Flask web application using session-based authentication - integrates auth0-server-python for server-ren...
---
name: auth0-flask
description: Use when adding login, logout, and user profile to a Flask web application using session-based authentication - integrates auth0-server-python for server-rendered apps with login/callback/profile/logout flows.
license: Apache-2.0
metadata:
author: Auth0 <support@auth0.com>
version: '1.0.1'
openclaw:
emoji: "\U0001F510"
homepage: https://github.com/auth0/agent-skills
---
# Auth0 Flask Web App Integration
Add login, logout, and user profile to a Flask web application using `auth0-server-python`.
---
## Prerequisites
- Flask application
- Auth0 Regular Web Application configured (not an API — must be an Application)
- If you don't have Auth0 set up yet, use the `auth0-quickstart` skill first
## When NOT to Use
- **Python APIs with JWT Bearer validation** — Use `auth0-fastapi-api` for FastAPI, or see the [Django REST Framework quickstart](https://auth0.com/docs/quickstart/backend/django)
- **FastAPI web app with login/logout UI** — No dedicated skill yet; see the [FastAPI quickstart](https://auth0.com/docs/quickstart/webapp/python)
- **Single Page Applications** — Use `auth0-react`, `auth0-vue`, or `auth0-angular` for client-side auth
- **Next.js applications** — Use `auth0-nextjs` which handles both client and server
- **Node.js web apps** — Use `auth0-express` or `auth0-fastify` for session-based auth
---
## Quick Start Workflow
### 1. Install SDK
```bash
pip install auth0-server-python "flask[async]" python-dotenv
```
**Critical:** You must install `flask[async]` (not just `flask`). The `[async]` extra installs `asgiref` which is required for Flask 2.0+ to support `async def` route handlers. Without it, async routes will not work. In `requirements.txt`, use `flask[async]>=2.0.0`.
### 2. Configure Environment
Create `.env`:
```bash
AUTH0_DOMAIN=your-tenant.us.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
AUTH0_SECRET=your_generated_app_secret
AUTH0_REDIRECT_URI=http://localhost:5000/callback
```
`AUTH0_DOMAIN` is your Auth0 tenant domain (without `https://`). `AUTH0_CLIENT_ID` and `AUTH0_CLIENT_SECRET` come from your Auth0 Application settings. `AUTH0_SECRET` is used for encrypting session data — generate with `openssl rand -hex 64`.
### 3. Configure Auth0 Dashboard
In your Auth0 Application settings:
- **Allowed Callback URLs**: `http://localhost:5000/callback`
- **Allowed Logout URLs**: `http://localhost:5000`
### 4. Create Auth Module
Create `auth.py` to initialize the `ServerClient` with Flask session-based stores. The stores use Flask's built-in `session` (cookie-based by default) for a **stateless** setup — no external database needed:
```python
import os
from flask import session as flask_session
from auth0_server_python.auth_server.server_client import ServerClient
from auth0_server_python.auth_types import StateData, TransactionData
from auth0_server_python.store import StateStore, TransactionStore
from dotenv import load_dotenv
load_dotenv() # Uses .env by default; pass load_dotenv(".env.local") if credentials are in .env.local
class FlaskSessionStateStore(StateStore):
"""State store that uses Flask's session for persistence."""
def __init__(self, secret: str):
super().__init__({"secret": secret})
async def set(self, identifier, state, remove_if_expires=False, options=None):
data = state.dict() if hasattr(state, "dict") else state
flask_session[identifier] = self.encrypt(identifier, data)
async def get(self, identifier, options=None):
data = flask_session.get(identifier)
if data is None:
return None
decrypted = self.decrypt(identifier, data)
# Ensure to not return a dict, as the underlying SDK expects a StateData instance, not a dict
return StateData(**decrypted) if isinstance(decrypted, dict) else decrypted
async def delete(self, identifier, options=None):
flask_session.pop(identifier, None)
async def delete_by_logout_token(self, claims, options=None):
pass
class FlaskSessionTransactionStore(TransactionStore):
"""Transaction store that uses Flask's session for persistence."""
def __init__(self, secret: str):
super().__init__({"secret": secret})
async def set(self, identifier, state, remove_if_expires=False, options=None):
data = state.dict() if hasattr(state, "dict") else state
flask_session[identifier] = self.encrypt(identifier, data)
async def get(self, identifier, options=None):
data = flask_session.get(identifier)
if data is None:
return None
decrypted = self.decrypt(identifier, data)
# Ensure to not return a dict, as the underlying SDK expects a TransactionData instance, not a dict
return TransactionData(**decrypted) if isinstance(decrypted, dict) else decrypted
async def delete(self, identifier, options=None):
flask_session.pop(identifier, None)
secret = os.getenv("AUTH0_SECRET")
auth0 = ServerClient(
domain=os.getenv("AUTH0_DOMAIN"),
client_id=os.getenv("AUTH0_CLIENT_ID"),
client_secret=os.getenv("AUTH0_CLIENT_SECRET"),
secret=secret,
redirect_uri=os.getenv("AUTH0_REDIRECT_URI"),
state_store=FlaskSessionStateStore(secret=secret),
transaction_store=FlaskSessionTransactionStore(secret=secret),
authorization_params={"scope": "openid profile email"},
)
```
Create one `ServerClient` instance and reuse it. Never hardcode credentials — always use environment variables.
**How this works:** Flask's default session is cookie-based (stateless). The SDK encrypts session data (tokens, user profile) with JWE before storing it in the session, so data is both signed and encrypted in the cookie. No server-side database is required.
**No `store_options` or `before_request` needed:** The SDK supports passing `store_options` (e.g. request/response objects) to store methods. Since these stores use `flask.session` — which is globally available during a request — they don't need anything from `store_options`, so you can call SDK methods without passing it. If you implement a custom store that manages cookies directly (instead of using `flask.session`), you would need to reintroduce `store_options` with `{"request": request, "response": response}`.
**Cookie size note:** Stateless sessions store all data in a cookie (~4KB limit). For most apps this is sufficient. If you store large amounts of session data or hit cookie size limits, switch to [stateful setup](#stateful-setup-with-redis).
### 5. Configure Flask App
In `app.py`, set up Flask with the secret key and session configuration:
```python
import os
from flask import Flask, redirect, request
from auth import auth0
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv("AUTH0_SECRET")
app.config.update(
SESSION_COOKIE_SECURE=False, # Set to True in production (requires HTTPS)
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
)
```
**Critical:** `app.secret_key` must be set for Flask session management. Without it, sessions won't work.
**For production:** Set `SESSION_COOKIE_SECURE=True` when deploying with HTTPS. Leaving it as `False` in production allows session cookies to be sent over unencrypted connections.
### 6. Add Home Route
```python
@app.route("/")
async def home():
user = await auth0.get_user()
if user:
return f"Hello, {user['name']}! <a href='/profile'>Profile</a> | <a href='/logout'>Logout</a>"
return "Welcome! <a href='/login'>Login</a>"
```
### 7. Add Login Route
```python
@app.route("/login")
async def login():
authorization_url = await auth0.start_interactive_login()
return redirect(authorization_url)
```
`start_interactive_login()` returns a URL string pointing to Auth0's Universal Login page. You must wrap it in `redirect()`. Authorization params (scope, redirect_uri) are already configured on the `ServerClient`.
### 8. Add Callback Route
```python
@app.route("/callback")
async def callback():
try:
await auth0.complete_interactive_login(str(request.url))
return redirect("/")
except Exception as e:
return f"Authentication error: {str(e)}", 400
```
Pass `str(request.url)` as the first argument — this is the full callback URL including the authorization code query parameters. Always wrap in try/except since the token exchange can fail (e.g. expired code, CSRF mismatch).
### 9. Add Profile Route (Protected)
```python
@app.route("/profile")
async def profile():
user = await auth0.get_user()
if user is None:
return redirect("/login")
return (
f"<h1>{user['name']}</h1>"
f"<p>Email: {user['email']}</p>"
f"<img src='{user['picture']}' alt='{user['name']}' width='100' />"
f"<p><a href='/logout'>Logout</a></p>"
)
```
`get_user()` returns the user's profile from the session, or `None` if not logged in.
### 10. Add Logout Route
```python
@app.route("/logout")
async def logout():
url = await auth0.logout()
return redirect(url)
```
`logout()` returns the Auth0 logout URL. Redirect the user to it.
### 11. Test the App
```bash
flask run
```
Visit `http://localhost:5000/login` to start the login flow.
---
## Stateful Setup with Redis
For production apps or when session data exceeds cookie size limits, use **Flask-Session** with Redis to store sessions server-side. Only a session ID is stored in the cookie.
### 1. Install Dependencies
```bash
pip install flask-session redis
```
### 2. Configure Flask-Session
Update `app.py` to use Redis-backed sessions:
```python
import os
from flask import Flask, redirect, request
from flask_session import Session
from auth import auth0
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv("AUTH0_SECRET")
app.config.update(
SESSION_TYPE="redis",
SESSION_PERMANENT=True,
SESSION_KEY_PREFIX="auth0:",
SESSION_COOKIE_SECURE=False,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
)
Session(app)
```
### 3. No Store Changes Needed
The same `FlaskSessionStateStore` and `FlaskSessionTransactionStore` from `auth.py` work without modification. Flask-Session transparently switches the `flask.session` backend from cookies to Redis — the stores continue to use `flask.session` as before.
**Routes are identical** to the stateless setup — no code changes needed.
---
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Hardcoding `domain`, `client_id`, or `client_secret` in source | Always read from environment variables — never embed credentials in code |
| Using `Authlib` or `python-jose` directly | Not needed; `auth0-server-python` handles all OAuth/OIDC flows |
| Using `Flask-Login` or `Flask-Dance` | Not needed; the SDK manages sessions and authentication |
| Manually parsing JWTs with `jwt.decode()` | The SDK handles token validation internally |
| Installing `flask` without `[async]` extra | Must use `flask[async]>=2.0.0` in requirements.txt — without it, async route handlers silently fail |
| Using synchronous route handlers | All routes calling SDK methods must be `async def` and use `await` |
| Forgetting `app.secret_key` | Required for Flask session management — without it, sessions silently fail |
| Using `auth0-fastapi-api` in Flask | That package is for FastAPI APIs — use `auth0-server-python` for Flask |
| Passing `domain` as full URL with `https://` | `domain` should be the bare domain, e.g. `my-tenant.us.auth0.com`, not `https://my-tenant.us.auth0.com` |
| Not configuring callback URL in Auth0 Dashboard | Must add `http://localhost:5000/callback` to Allowed Callback URLs |
| Returning `start_interactive_login()` directly | It returns a URL string, not a response — must wrap in `redirect()` |
| Not handling errors in `/callback` | `complete_interactive_login()` can fail — always wrap in try/except |
| Calling SDK methods without `await` | All SDK methods are async — forgetting `await` returns a coroutine instead of the result |
| Passing options positionally to `logout()` | Use `logout(store_options=...)` — the first positional parameter is `LogoutOptions`, not store options |
| Expecting backchannel logout to work | Not supported with cookie-based sessions — `delete_by_logout_token` is a no-op. Use standard `/logout` route |
| Deploying with `SESSION_COOKIE_SECURE=False` | Must set to `True` in production — cookies are sent over HTTP otherwise |
---
## Key SDK Methods
All methods are async:
| Method | Signature | Purpose |
|--------|-----------|---------|
| `start_interactive_login` | `await auth0.start_interactive_login()` | Returns authorization URL string — wrap in `redirect()` |
| `complete_interactive_login` | `await auth0.complete_interactive_login(str(request.url))` | Processes the callback URL, exchanges code for tokens |
| `get_user` | `await auth0.get_user()` | Returns current session user dict or `None` |
| `get_access_token` | `await auth0.get_access_token()` | Returns the access token for calling external APIs |
| `logout` | `await auth0.logout()` | Returns Auth0 logout URL string |
---
## Related Skills
- `auth0-express` — For server-rendered Express web apps with login/logout sessions
- `auth0-fastify` — For Fastify web applications with session-based auth
- `auth0-cli` — Manage Auth0 resources from the terminal
---
## Quick Reference
**ServerClient configuration:**
```python
auth0 = ServerClient(
domain=os.getenv("AUTH0_DOMAIN"), # required
client_id=os.getenv("AUTH0_CLIENT_ID"), # required
client_secret=os.getenv("AUTH0_CLIENT_SECRET"), # required
secret=os.getenv("AUTH0_SECRET"), # required (encryption secret)
redirect_uri=os.getenv("AUTH0_REDIRECT_URI"), # required
state_store=FlaskSessionStateStore(secret=secret), # required
transaction_store=FlaskSessionTransactionStore(secret=secret), # required
authorization_params={"scope": "openid profile email"}, # recommended
)
```
**Route protection pattern:**
```python
user = await auth0.get_user()
if user is None:
return redirect("/login")
```
**Environment variables:**
- `AUTH0_DOMAIN` — your Auth0 tenant domain (e.g. `tenant.us.auth0.com`)
- `AUTH0_CLIENT_ID` — your Application's client ID
- `AUTH0_CLIENT_SECRET` — your Application's client secret
- `AUTH0_SECRET` — encryption and session secret key
- `AUTH0_REDIRECT_URI` — callback URL (e.g. `http://localhost:5000/callback`)
---
## Detailed Documentation
- **[Setup Guide](references/setup.md)** - Automated setup scripts, environment configuration, Auth0 CLI usage
- **[Integration Guide](references/integration.md)** - Protected routes, calling APIs, session management, error handling
- **[API Reference](references/api.md)** - Complete ServerClient API, configuration options, store implementation, security
---
## References
- [auth0-server-python on PyPI](https://pypi.org/project/auth0-server-python/)
- [Auth0 Flask Quickstart](https://auth0.com/docs/quickstart/webapp/python)
- [Flask Documentation](https://flask.palletsprojects.com/)
don't have the plugin yet? install it then click "run inline in claude" again.
add login, logout, and user profile endpoints to a Flask web application using session-based authentication via auth0-server-python. use this skill when building server-rendered Flask apps (not SPAs, not FastAPI, not Next.js) that need stateless cookie-based or Redis-backed session management. covers the full OAuth2/OIDC flow: login redirect to Auth0 Universal Login, callback handling, protected route checks, and logout to Auth0 logout URL.
environment variables (required):
AUTH0_DOMAIN (string): your Auth0 tenant domain without https://, e.g. my-tenant.us.auth0.comAUTH0_CLIENT_ID (string): Application client ID from Auth0 dashboardAUTH0_CLIENT_SECRET (string): Application client secret from Auth0 dashboardAUTH0_SECRET (string): 64-byte hex encryption key for session data and state, generate with openssl rand -hex 64AUTH0_REDIRECT_URI (string): full callback URL, e.g. http://localhost:5000/callback for local or https://yourdomain.com/callback for productionAuth0 application setup:
AUTH0_REDIRECT_URIhttp://localhost:5000 or https://yourdomain.comPython dependencies:
auth0-server-python (>=0.x.x): OAuth2/OIDC SDK for server-side authflask[async] (>=2.0.0): Flask with async route support via asgirefpython-dotenv: load .env file for credential managementflask-session and redis (optional): for Redis-backed stateful sessions instead of cookie-basedexternal connections:
edge cases to handle:
app.secret_key (sessions silently fail)flask[async] extra (async routes silently fail without error)step 1: install dependencies and initialize environment
inputs: none
action: run pip install auth0-server-python "flask[async]" python-dotenv. if using Redis-backed sessions, also run pip install flask-session redis. create .env file in project root with all five required environment variables. verify Auth0 application exists in Auth0 dashboard and copy client ID, client secret, and tenant domain.
outputs: .env file with credentials, confirmed Auth0 application configured
step 2: create auth0 session stores
inputs: AUTH0_SECRET environment variable
action: create auth.py module. implement two custom store classes that inherit from StateStore and TransactionStore (from auth0_server_python.store). both stores use Flask's global flask.session dict for persistence and handle encryption/decryption via parent class methods. FlaskSessionStateStore manages OAuth state (nonces, redirect URLs). FlaskSessionTransactionStore manages login transaction data. both require async methods: set(), get(), and delete(). decrypt data before returning and convert dicts to StateData or TransactionData instances. do not implement delete_by_logout_token() for stateless cookies (no backchannel logout support).
outputs: auth.py with two store classes
step 3: initialize ServerClient
inputs: AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_SECRET, AUTH0_REDIRECT_URI environment variables; both store classes from step 2
action: in auth.py, instantiate a single module-level ServerClient with all five config parameters plus both store instances. set authorization_params to {"scope": "openid profile email"} (or add additional scopes if calling external APIs). do not create multiple ServerClient instances. load environment variables with load_dotenv() before reading them.
outputs: auth0 ServerClient instance ready for use across all routes
step 4: configure Flask app with session settings
inputs: AUTH0_SECRET environment variable
action: in app.py, set app.secret_key = os.getenv("AUTH0_SECRET"). update app.config with session cookie flags: SESSION_COOKIE_SECURE=False for development (must be True in production with HTTPS), SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax". these flags prevent JavaScript access to session cookies and enforce secure transport in production.
outputs: Flask app configured with session security
step 5: add home route (optional user check)
inputs: ServerClient instance from step 3
action: create async GET route at /. call await auth0.get_user() which returns user dict from session or None if not authenticated. render different HTML based on result: if user exists, show welcome message with user's name, Profile link, and Logout link. if user is None, show login prompt with Login link.
outputs: home page that reflects auth status
step 6: add login route (start OAuth flow)
inputs: ServerClient instance
action: create async GET route at /login. call await auth0.start_interactive_login() which returns a string URL to Auth0 Universal Login. wrap this string in Flask's redirect() function and return it. never return the URL string directly or render it in HTML.
outputs: HTTP redirect to Auth0 login page with authorization params
step 7: add callback route (exchange code for tokens)
inputs: ServerClient instance, Flask request object
action: create async GET route at /callback. extract the full callback URL as str(request.url) (includes query params like code and state). pass this string to await auth0.complete_interactive_login(str(request.url)). this method exchanges the authorization code for tokens, validates state, and stores user profile and tokens in Flask session. wrap this call in try/except because token exchange can fail (expired code, state mismatch, network error, etc.). on success, redirect to home page or originally requested page. on failure, return 400 error with exception message.
outputs: session populated with tokens and user profile on success; error response on failure
step 8: add profile route (protected example)
inputs: ServerClient instance
action: create async GET route at /profile. call await auth0.get_user() at the start. if it returns None, redirect to /login. if user exists, render HTML with user's name, email, and picture URL (all present in user dict). add a Logout link. this is a minimal protection pattern.
outputs: protected HTML page or redirect to login
step 9: add logout route (clear session and redirect)
inputs: ServerClient instance
action: create async GET route at /logout. call await auth0.logout() which returns the Auth0 logout URL (clears Auth0 session server-side). wrap this URL in redirect() and return it. this routes the user to Auth0, which clears its session and redirects to the Allowed Logout URL configured in Auth0 dashboard.
outputs: HTTP redirect to Auth0 logout endpoint
step 10: test locally
inputs: Flask app with all routes, .env with valid credentials, Auth0 app configured with http://localhost:5000/callback and http://localhost:5000 as allowed URLs
action: run flask run from project directory. open browser to http://localhost:5000. click Login, authenticate with Auth0 (email/password, social, etc.), and verify callback completes and user profile appears. click Logout and verify redirect to Auth0 logout, then post-logout redirect.
outputs: verified working login, callback, user profile access, and logout flows
step 11 (optional): switch to Redis-backed sessions for production
inputs: Redis instance running, flask-session and redis packages installed
action: update app.py to set SESSION_TYPE="redis", SESSION_KEY_PREFIX="auth0:", and other session config. call Session(app) to bind Flask-Session extension. no changes needed to auth.py or routes, flask.session continues to work transparently but now reads/writes to Redis instead of cookies. only session ID is stored in cookie. configure Redis URL via SESSION_REDIS or connection pool if not using default localhost:6379.
outputs: app now persists sessions in Redis with stateful security model
if building a Flask API (not a web app with login UI): do not use this skill. Auth0 Flask is for server-rendered HTML with user-facing login pages. for Python APIs requiring JWT Bearer tokens, use auth0-fastapi-api for FastAPI or the Django REST Framework quickstart. this skill assumes sessions and cookies, not Bearer auth.
if building a single-page application (React, Vue, Angular): do not use this skill. SPAs require client-side SDKs (auth0-react, auth0-vue, auth0-angular) that manage auth on the browser side. this skill is for server-rendered Flask apps only.
if using FastAPI instead of Flask: do not use this skill. FastAPI requires a different approach. check the Auth0 FastAPI quickstart for options (or wait for a dedicated auth0-fastapi-web skill if it exists).
if using Next.js: do not use this skill. use auth0-nextjs which is purpose-built for Next.js and handles both client and server auth.
if session data exceeds cookie size limit (>4KB): switch to Redis-backed sessions (step 11 in procedure). stateless cookie-based sessions encode everything in the cookie; large token payloads or extra claims will overflow. Redis moves data server-side and only stores session ID in cookie.
if user is not logged in and tries to access /profile: redirect to /login. always check await auth0.get_user() at the start of protected routes and return early if result is None.
if token exchange fails in /callback (expired code, CSRF mismatch, network timeout): catch the exception and return HTTP 400 with error details. do not redirect to home; user must restart login from /login.
if app.secret_key is not set: Flask session management silently fails, sessions are created but not persisted. always set app.secret_key = os.getenv("AUTH0_SECRET") in Flask app config.
if flask[async] extra is not installed: async route handlers (all routes using SDK methods) silently fail, they return a coroutine instead of a response. always use pip install "flask[async]>=2.0.0" or declare it in requirements.txt.
if deploying to production with HTTPS: set SESSION_COOKIE_SECURE=True to ensure session cookies are only sent over encrypted connections. leaving it as False in production leaks sessions over HTTP.
if Auth0 callback URL is not configured in Auth0 dashboard: token exchange will fail with "redirect_uri mismatch" error in /callback. must add AUTH0_REDIRECT_URI value to Allowed Callback URLs in Auth0 application settings.
if backchannel logout (OIDC RP-initiated logout) is required: this skill does not support it via cookie-based sessions. the delete_by_logout_token() store method is a no-op. use standard /logout route instead, which performs full logout via Auth0 logout endpoint.
on successful completion of the procedure, the deliverable consists of:
file: .env
AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_SECRET, AUTH0_REDIRECT_URI.gitignorefile: auth.py
FlaskSessionStateStore class, FlaskSessionTransactionStore class, module-level auth0 ServerClient instanceapp/auth.pyfile: app.py
secret_key and session config, routes: /, /login, /callback, /profile, /logoutasync def and use await for SDK callssession data format (in Flask session dict):
state_<random> (managed by SDK, not your code)auth_<random> (managed by SDK)await auth0.get_user() (dict with keys: sub, name, email, picture, updated_at, custom claims if configured)await auth0.get_access_token() (string, JWT)HTTP response status codes:
/: 200 OK (HTML)/login: 302 Found (redirect to Auth0)/callback on success: 302 Found (redirect to home)/callback on error: 400 Bad Request (error message in body)/profile if not authenticated: 302 Found (redirect to /login)/profile if authenticated: 200 OK (HTML with user profile)/logout: 302 Found (redirect to Auth0 logout URL)session persistence:
the skill has worked when:
visiting http://localhost:5000 shows "Welcome! [Login]" if not authenticated or "Hello, [username]! [Profile] | [Logout]" if authenticated
clicking Login redirects to Auth0 Universal Login page (domain matches your Auth0 tenant, form shows email/password or social login options)
authenticating with valid credentials redirects back to /callback, then to home page, and user is now logged in (name appears on home page)
visiting /profile while logged in displays the user's full name, email, and profile picture, with a Logout link
clicking Logout redirects to Auth0 logout endpoint, clears the Auth0 session, and redirects back to home page showing "Welcome! [Login]" again (user is logged out)
visiting /profile while logged out redirects to /login
viewing Flask server logs shows no errors or warnings related to session or authentication (no "coroutine was never awaited" messages, no "no secret key" warnings)
inspecting browser cookies shows a single session cookie (name typically session) with secure flags set (HttpOnly, SameSite=Lax, Secure in production)
token exchange completes in under 1 second (verify in network tab or Flask logs, slow callback indicates network issue or Redis misconfiguration in stateful setup)
in production with Redis, session data is stored in Redis with expiry matching token lifetime (verify with redis-cli or monitoring tool)
auth0-express for server-rendered Express.js web apps with session-based authauth0-fastify for Fastify web applicationsauth0-react for React SPAsauth0-vue for Vue SPAsauth0-angular for Angular SPAsauth0-nextjs for Next.js full-stack appsauth0-cli for managing Auth0 resources from the terminalAUTH0_DOMAIN=my-tenant.us.auth0.com
AUTH0_CLIENT_ID=aBcDeFg1H2iJkLmNoPqRsTu
AUTH0_CLIENT_SECRET=xyz_very_long_secret_string_abc123
AUTH0_SECRET=1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7a8b9c0d1e
AUTH0_REDIRECT_URI=http://localhost:5000/callback
from auth0_server_python.auth_server.server_client import ServerClient
auth0 = ServerClient(
domain=os.getenv("AUTH0_DOMAIN"),
client_id=os.getenv("AUTH0_CLIENT_ID"),
client_secret=os.getenv("AUTH0_CLIENT_SECRET"),
secret=os.getenv("AUTH0_SECRET"),
redirect_uri=os.getenv("AUTH0_REDIRECT_URI"),
state_store=FlaskSessionStateStore(secret=os.getenv("AUTH0_SECRET")),
transaction_store=FlaskSessionTransactionStore(secret=os.getenv("AUTH0_SECRET")),
authorization_params={"scope": "openid profile email"},
)
@app.route("/protected")
async def protected():
user = await auth0.get_user()
if user is None:
return redirect("/login")
# user is authenticated, proceed
return f"Hello, {user['name']}"
| mistake | fix |
|---|---|
| hardcoding credentials in source code | always load from environment variables via os.getenv(), never embed in code |
using flask without [async] extra |
must install flask[async]>=2.0.0, not just flask |
forgetting app.secret_key |
set it |