A test app that exercises every part of the Kinde Python SDK the way a
real application would use it. The same pages run on Flask (kinde_flask) and FastAPI
(kinde_fastapi): one framework-neutral feature layer, two thin adapters.
Every page pairs a live result with a "How this works" panel showing the SDK code behind it.
The Flask app, signed in to a real Kinde business. Tokens stay masked, and the security probes run against the live app.
| Page | What it exercises |
|---|---|
| Dashboard | Configuration (secrets masked) and live setup checks: OIDC discovery, callback URL, M2M token |
| Sign in lab | login() / register() with every LoginOptions value, create-org, invitations, auth_params, URL preview |
| Profile | get_user_info() / get_user_info_async(), Account API profile and user properties |
| Tokens | tokens helper, claims, expiry countdown, refresh and revoke |
| Claims | claims.get_claim() / get_all_claims() for access and ID tokens |
| Permissions & roles | Token claims and the Account API (ApiOptions(force_api=True)) side by side |
| Feature flags | get_flag() with typed defaults, get_all_flags() |
| Billing & entitlements | Entitlements, plan selection portal |
| Self-serve portal | portals.generate_portal_url() for every PortalPage |
| Organizations | Memberships, switching org, creating an org at sign-up |
| Client modes | OAuth, AsyncOAuth and SmartOAuth (create_oauth_client) |
| Session & storage | What the SDK stores where, KindeSessionManagement, storage backends |
| Protected routes | Guards for sign-in, permission, role, flag and entitlement; the framework-native equivalent |
| Management API | M2M token, curated reads, a read-only console over every API, guarded write scenarios with cleanup |
| Security audit | Session and cookie checks, forged / mismatched / replayed callbacks, SDK log scan |
| Regression | One button runs the SDK suite, then this app's suite. A second button runs live Management and account API reads against your Kinde business |
- Back-end web application. In Kinde, go to Settings → Applications → Add application and
choose Back-end web. Under Details:
- Allowed callback URLs:
http://localhost:5050/callbackandhttp://localhost:8000/callback - Allowed logout redirect URLs:
http://localhost:5050andhttp://localhost:8000 - Copy the domain, client ID and client secret.
- Allowed callback URLs:
- Refresh tokens. The Sign in lab requests the
offlinescope by default. Nothing else to do. - Optional, for richer pages:
- Create the permission
read:reports, the roleadmin, the boolean feature flagbeta_dashboardand assign them to your user's organization. The protected-route examples use these keys; change them withPLAYGROUND_DEMO_*. - Enable the self-serve portal for the application to use portal links.
- Set up billing with a plan containing a
pro_reportsfeature for the entitlement examples. - The "token claims" results read the access token: make sure roles, permissions and feature flags are included in it under the application's Tokens settings. The Account API results don't depend on this.
- Create the permission
- Optional, Management API. Add a Machine to machine application, authorize it for the
Kinde Management API with the scopes you want to try (
read:users,read:organizations, …; addcreate:users,create:organizations,delete:users,delete:organizations,update:organizationsandcreate:meter_usagefor the write scenarios). Use a test business or environment for writes.
python3 -m venv .venv
.venv/bin/pip install -e "../kinde-python-sdk[flask,fastapi]" -e ".[dev]"
cp .env.example .env # then fill in your Kinde values.venv/bin/python -m apps.flask_app # http://localhost:5050
.venv/bin/python -m apps.fastapi_app # http://localhost:8000Both can run at the same time. Real environment variables take precedence over .env.
All settings live in .env.example. The ones you'll change most:
| Variable | Purpose |
|---|---|
KINDE_HOST, KINDE_CLIENT_ID, KINDE_CLIENT_SECRET |
Your back-end web application. Leave the secret empty to run as a public (PKCE-only) client. |
PLAYGROUND_SESSION_SECRET |
Session signing key. Without it a random key is generated and sessions end on restart. |
PLAYGROUND_CLIENT_MODE |
oauth, async or smart: which SDK client the app creates (one per process). |
PLAYGROUND_FORCE_API |
Create the client with force_api=True, so helpers always call the Account API. |
KINDE_MANAGEMENT_CLIENT_ID, KINDE_MANAGEMENT_CLIENT_SECRET |
Enable the Management API page. |
PLAYGROUND_ALLOW_MUTATIONS |
Enable the Management write scenarios. Off by default. |
PLAYGROUND_DEBUG |
Enable token reveal buttons. Never in production. |
.venv/bin/python -m pytestThe suite runs every test against both apps with Kinde mocked (no network): every page signed out and signed in, the guard matrix, CSRF, cookie flags, session ID rotation, the Management console and scenarios, the security probes, and an end-to-end scan that no secret, code or token reaches any page or log line.
playground/
config.py Settings from the environment, validated at startup; secrets masked
kinde.py The one place that talks to the SDK: one OAuth client, one ManagementClient
web.py Framework-neutral Request / Page / Redirect, routing, CSRF, flash, templates
guards.py require_auth / require_permission / require_role / require_flag / require_entitlement
redact.py Masking, redacting log formatter, SDK log capture for the audit
fastapi_session.py Server-side session store for FastAPI
app_core.py Startup and request dispatch shared by both adapters
features/ One module per page
templates/, static/
apps/
flask_app.py Flask adapter (kinde_flask)
fastapi_app.py FastAPI adapter (kinde_fastapi)
Patterns worth copying into your own app:
- One OAuth client per process, created at startup with explicit configuration. The SDK keeps process-wide state (framework, storage), so creating a second client would replace the first.
- Server-side sessions. The SDK keeps tokens in the session. Flask uses Flask-Session's
filesystem store (configured by
kinde_flaskfromSESSION_TYPE/SESSION_FILE_DIR); FastAPI usesplayground/fastapi_session.py. Starlette's defaultSessionMiddlewarewould put the tokens in the cookie. - Rotate the session ID at sign-in to prevent session fixation (both adapters do this).
- Run blocking SDK calls off the event loop (
asyncio.to_thread): token refresh, userinfo and every Management API call are blocking HTTP requests. - Guards fail closed: authenticate first, then authorize, and deny if the check errors.
- Never show raw tokens or API errors. Exceptions are reported by type and HTTP status only;
Management API responses are passed through
redact_data, because some (such asget_application) return client secrets. - CSRF-protect state-changing forms, and only accept same-site paths as post-login redirects.
The playground is a development tool. Before using any of it as a template:
- Serve over HTTPS; cookies are marked
Secureautomatically when the base URL ishttps://. - Replace the in-memory FastAPI session store with Redis or a database, and the Flask filesystem store with a shared backend if you run more than one instance.
- Keep
PLAYGROUND_DEBUGandPLAYGROUND_ALLOW_MUTATIONSoff. - Run Flask under a production WSGI server and FastAPI with multiple workers only once sessions are shared.



