{"skill":{"slug":"auth0-flask","displayName":"Auth0 Flask","summary":"Use when adding login, logout, and user profile to a Flask web application using session-based authentication - integrates auth0-server-python for server-ren...","description":"---\nname: auth0-flask\ndescription: 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.\nlicense: Apache-2.0\nmetadata:\n  author: Auth0 <support@auth0.com>\n  version: '1.0.1'\n  openclaw:\n    emoji: \"\\U0001F510\"\n    homepage: https://github.com/auth0/agent-skills\n---\n\n# Auth0 Flask Web App Integration\n\nAdd login, logout, and user profile to a Flask web application using `auth0-server-python`.\n\n---\n\n## Prerequisites\n\n- Flask application\n- Auth0 Regular Web Application configured (not an API — must be an Application)\n- If you don't have Auth0 set up yet, use the `auth0-quickstart` skill first\n\n## When NOT to Use\n\n- **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)\n- **FastAPI web app with login/logout UI** — No dedicated skill yet; see the [FastAPI quickstart](https://auth0.com/docs/quickstart/webapp/python)\n- **Single Page Applications** — Use `auth0-react`, `auth0-vue`, or `auth0-angular` for client-side auth\n- **Next.js applications** — Use `auth0-nextjs` which handles both client and server\n- **Node.js web apps** — Use `auth0-express` or `auth0-fastify` for session-based auth\n\n---\n\n## Quick Start Workflow\n\n### 1. Install SDK\n\n```bash\npip install auth0-server-python \"flask[async]\" python-dotenv\n```\n\n**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`.\n\n### 2. Configure Environment\n\nCreate `.env`:\n\n```bash\nAUTH0_DOMAIN=your-tenant.us.auth0.com\nAUTH0_CLIENT_ID=your_client_id\nAUTH0_CLIENT_SECRET=your_client_secret\nAUTH0_SECRET=your_generated_app_secret\nAUTH0_REDIRECT_URI=http://localhost:5000/callback\n```\n\n`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`.\n\n### 3. Configure Auth0 Dashboard\n\nIn your Auth0 Application settings:\n- **Allowed Callback URLs**: `http://localhost:5000/callback`\n- **Allowed Logout URLs**: `http://localhost:5000`\n\n### 4. Create Auth Module\n\nCreate `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:\n\n```python\nimport os\nfrom flask import session as flask_session\nfrom auth0_server_python.auth_server.server_client import ServerClient\nfrom auth0_server_python.auth_types import StateData, TransactionData\nfrom auth0_server_python.store import StateStore, TransactionStore\nfrom dotenv import load_dotenv\n\nload_dotenv()  # Uses .env by default; pass load_dotenv(\".env.local\") if credentials are in .env.local\n\n\nclass FlaskSessionStateStore(StateStore):\n    \"\"\"State store that uses Flask's session for persistence.\"\"\"\n\n    def __init__(self, secret: str):\n        super().__init__({\"secret\": secret})\n\n    async def set(self, identifier, state, remove_if_expires=False, options=None):\n        data = state.dict() if hasattr(state, \"dict\") else state\n        flask_session[identifier] = self.encrypt(identifier, data)\n\n    async def get(self, identifier, options=None):\n        data = flask_session.get(identifier)\n        if data is None:\n            return None\n        decrypted = self.decrypt(identifier, data)\n        # Ensure to not return a dict, as the underlying SDK expects a StateData instance, not a dict\n        return StateData(**decrypted) if isinstance(decrypted, dict) else decrypted\n\n    async def delete(self, identifier, options=None):\n        flask_session.pop(identifier, None)\n\n    async def delete_by_logout_token(self, claims, options=None):\n        pass\n\n\nclass FlaskSessionTransactionStore(TransactionStore):\n    \"\"\"Transaction store that uses Flask's session for persistence.\"\"\"\n\n    def __init__(self, secret: str):\n        super().__init__({\"secret\": secret})\n\n    async def set(self, identifier, state, remove_if_expires=False, options=None):\n        data = state.dict() if hasattr(state, \"dict\") else state\n        flask_session[identifier] = self.encrypt(identifier, data)\n\n    async def get(self, identifier, options=None):\n        data = flask_session.get(identifier)\n        if data is None:\n            return None\n        decrypted = self.decrypt(identifier, data)\n        # Ensure to not return a dict, as the underlying SDK expects a TransactionData instance, not a dict\n        return TransactionData(**decrypted) if isinstance(decrypted, dict) else decrypted\n\n    async def delete(self, identifier, options=None):\n        flask_session.pop(identifier, None)\n\n\nsecret = os.getenv(\"AUTH0_SECRET\")\n\nauth0 = ServerClient(\n    domain=os.getenv(\"AUTH0_DOMAIN\"),\n    client_id=os.getenv(\"AUTH0_CLIENT_ID\"),\n    client_secret=os.getenv(\"AUTH0_CLIENT_SECRET\"),\n    secret=secret,\n    redirect_uri=os.getenv(\"AUTH0_REDIRECT_URI\"),\n    state_store=FlaskSessionStateStore(secret=secret),\n    transaction_store=FlaskSessionTransactionStore(secret=secret),\n    authorization_params={\"scope\": \"openid profile email\"},\n)\n```\n\nCreate one `ServerClient` instance and reuse it. Never hardcode credentials — always use environment variables.\n\n**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.\n\n**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}`.\n\n**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).\n\n### 5. Configure Flask App\n\nIn `app.py`, set up Flask with the secret key and session configuration:\n\n```python\nimport os\nfrom flask import Flask, redirect, request\nfrom auth import auth0\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\napp = Flask(__name__)\napp.secret_key = os.getenv(\"AUTH0_SECRET\")\napp.config.update(\n    SESSION_COOKIE_SECURE=False,  # Set to True in production (requires HTTPS)\n    SESSION_COOKIE_HTTPONLY=True,\n    SESSION_COOKIE_SAMESITE=\"Lax\",\n)\n```\n\n**Critical:** `app.secret_key` must be set for Flask session management. Without it, sessions won't work.\n\n**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.\n\n### 6. Add Home Route\n\n```python\n@app.route(\"/\")\nasync def home():\n    user = await auth0.get_user()\n    if user:\n        return f\"Hello, {user['name']}! <a href='/profile'>Profile</a> | <a href='/logout'>Logout</a>\"\n    return \"Welcome! <a href='/login'>Login</a>\"\n```\n\n### 7. Add Login Route\n\n```python\n@app.route(\"/login\")\nasync def login():\n    authorization_url = await auth0.start_interactive_login()\n    return redirect(authorization_url)\n```\n\n`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`.\n\n### 8. Add Callback Route\n\n```python\n@app.route(\"/callback\")\nasync def callback():\n    try:\n        await auth0.complete_interactive_login(str(request.url))\n        return redirect(\"/\")\n    except Exception as e:\n        return f\"Authentication error: {str(e)}\", 400\n```\n\nPass `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).\n\n### 9. Add Profile Route (Protected)\n\n```python\n@app.route(\"/profile\")\nasync def profile():\n    user = await auth0.get_user()\n    if user is None:\n        return redirect(\"/login\")\n    return (\n        f\"<h1>{user['name']}</h1>\"\n        f\"<p>Email: {user['email']}</p>\"\n        f\"<img src='{user['picture']}' alt='{user['name']}' width='100' />\"\n        f\"<p><a href='/logout'>Logout</a></p>\"\n    )\n```\n\n`get_user()` returns the user's profile from the session, or `None` if not logged in.\n\n### 10. Add Logout Route\n\n```python\n@app.route(\"/logout\")\nasync def logout():\n    url = await auth0.logout()\n    return redirect(url)\n```\n\n`logout()` returns the Auth0 logout URL. Redirect the user to it.\n\n### 11. Test the App\n\n```bash\nflask run\n```\n\nVisit `http://localhost:5000/login` to start the login flow.\n\n---\n\n## Stateful Setup with Redis\n\nFor 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.\n\n### 1. Install Dependencies\n\n```bash\npip install flask-session redis\n```\n\n### 2. Configure Flask-Session\n\nUpdate `app.py` to use Redis-backed sessions:\n\n```python\nimport os\nfrom flask import Flask, redirect, request\nfrom flask_session import Session\nfrom auth import auth0\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\napp = Flask(__name__)\napp.secret_key = os.getenv(\"AUTH0_SECRET\")\napp.config.update(\n    SESSION_TYPE=\"redis\",\n    SESSION_PERMANENT=True,\n    SESSION_KEY_PREFIX=\"auth0:\",\n    SESSION_COOKIE_SECURE=False,\n    SESSION_COOKIE_HTTPONLY=True,\n    SESSION_COOKIE_SAMESITE=\"Lax\",\n)\nSession(app)\n```\n\n### 3. No Store Changes Needed\n\nThe 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.\n\n**Routes are identical** to the stateless setup — no code changes needed.\n\n---\n\n## Common Mistakes\n\n| Mistake | Fix |\n|---------|-----|\n| Hardcoding `domain`, `client_id`, or `client_secret` in source | Always read from environment variables — never embed credentials in code |\n| Using `Authlib` or `python-jose` directly | Not needed; `auth0-server-python` handles all OAuth/OIDC flows |\n| Using `Flask-Login` or `Flask-Dance` | Not needed; the SDK manages sessions and authentication |\n| Manually parsing JWTs with `jwt.decode()` | The SDK handles token validation internally |\n| Installing `flask` without `[async]` extra | Must use `flask[async]>=2.0.0` in requirements.txt — without it, async route handlers silently fail |\n| Using synchronous route handlers | All routes calling SDK methods must be `async def` and use `await` |\n| Forgetting `app.secret_key` | Required for Flask session management — without it, sessions silently fail |\n| Using `auth0-fastapi-api` in Flask | That package is for FastAPI APIs — use `auth0-server-python` for Flask |\n| 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` |\n| Not configuring callback URL in Auth0 Dashboard | Must add `http://localhost:5000/callback` to Allowed Callback URLs |\n| Returning `start_interactive_login()` directly | It returns a URL string, not a response — must wrap in `redirect()` |\n| Not handling errors in `/callback` | `complete_interactive_login()` can fail — always wrap in try/except |\n| Calling SDK methods without `await` | All SDK methods are async — forgetting `await` returns a coroutine instead of the result |\n| Passing options positionally to `logout()` | Use `logout(store_options=...)` — the first positional parameter is `LogoutOptions`, not store options |\n| Expecting backchannel logout to work | Not supported with cookie-based sessions — `delete_by_logout_token` is a no-op. Use standard `/logout` route |\n| Deploying with `SESSION_COOKIE_SECURE=False` | Must set to `True` in production — cookies are sent over HTTP otherwise |\n\n---\n\n## Key SDK Methods\n\nAll methods are async:\n\n| Method | Signature | Purpose |\n|--------|-----------|---------|\n| `start_interactive_login` | `await auth0.start_interactive_login()` | Returns authorization URL string — wrap in `redirect()` |\n| `complete_interactive_login` | `await auth0.complete_interactive_login(str(request.url))` | Processes the callback URL, exchanges code for tokens |\n| `get_user` | `await auth0.get_user()` | Returns current session user dict or `None` |\n| `get_access_token` | `await auth0.get_access_token()` | Returns the access token for calling external APIs |\n| `logout` | `await auth0.logout()` | Returns Auth0 logout URL string |\n\n---\n\n## Related Skills\n\n- `auth0-express` — For server-rendered Express web apps with login/logout sessions\n- `auth0-fastify` — For Fastify web applications with session-based auth\n- `auth0-cli` — Manage Auth0 resources from the terminal\n\n---\n\n## Quick Reference\n\n**ServerClient configuration:**\n```python\nauth0 = ServerClient(\n    domain=os.getenv(\"AUTH0_DOMAIN\"),                    # required\n    client_id=os.getenv(\"AUTH0_CLIENT_ID\"),              # required\n    client_secret=os.getenv(\"AUTH0_CLIENT_SECRET\"),      # required\n    secret=os.getenv(\"AUTH0_SECRET\"),                    # required (encryption secret)\n    redirect_uri=os.getenv(\"AUTH0_REDIRECT_URI\"),        # required\n    state_store=FlaskSessionStateStore(secret=secret),   # required\n    transaction_store=FlaskSessionTransactionStore(secret=secret),  # required\n    authorization_params={\"scope\": \"openid profile email\"},  # recommended\n)\n```\n\n**Route protection pattern:**\n```python\nuser = await auth0.get_user()\nif user is None:\n    return redirect(\"/login\")\n```\n\n**Environment variables:**\n- `AUTH0_DOMAIN` — your Auth0 tenant domain (e.g. `tenant.us.auth0.com`)\n- `AUTH0_CLIENT_ID` — your Application's client ID\n- `AUTH0_CLIENT_SECRET` — your Application's client secret\n- `AUTH0_SECRET` — encryption and session secret key\n- `AUTH0_REDIRECT_URI` — callback URL (e.g. `http://localhost:5000/callback`)\n\n---\n\n## Detailed Documentation\n\n- **[Setup Guide](references/setup.md)** - Automated setup scripts, environment configuration, Auth0 CLI usage\n- **[Integration Guide](references/integration.md)** - Protected routes, calling APIs, session management, error handling\n- **[API Reference](references/api.md)** - Complete ServerClient API, configuration options, store implementation, security\n\n---\n\n## References\n\n- [auth0-server-python on PyPI](https://pypi.org/project/auth0-server-python/)\n- [Auth0 Flask Quickstart](https://auth0.com/docs/quickstart/webapp/python)\n- [Flask Documentation](https://flask.palletsprojects.com/)\n","tags":{"latest":"1.0.1"},"stats":{"comments":0,"downloads":332,"installsAllTime":0,"installsCurrent":0,"stars":0,"versions":1},"createdAt":1778072345572,"updatedAt":1778492859420},"latestVersion":{"version":"1.0.1","createdAt":1778072345572,"changelog":"Initial publish","license":"MIT-0"},"metadata":{"setup":[],"os":null,"systems":null},"owner":{"handle":"auth0","userId":"s1705618g3dhya8px0x9gnyy4d84geyh","displayName":"Auth0","image":"https://avatars.githubusercontent.com/u/2824157"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1780090753402}}