Python SDK
The Kinde Python SDK allows developers to quickly and securely integrate a new or an existing Python application into the Kinde platform. The SDK supports both Flask and FastAPI frameworks through a single unified interface.
Before you begin
Link to this section- Kinde Python SDK supports Python 3.9+
- If you haven’t already got a Kinde account, register for free here (no credit card required). Registering gives you a Kinde domain, which you need to get started, e.g.
yourapp.kinde.com
.
If you are using a previous version of Python, you may need to refer to the previous v1 SDK.
If you’re migrating from an older version of the SDK, see our migration guide for detailed instructions.
For new projects, you can find our Starter Kit on GitHub.
Install
Link to this sectionInstall PIP and then execute the following command:
pip install kinde-python-sdk
Environment variables
Link to this sectionThe Kinde Python SDK uses environment variables for configuration. Here are all the supported variables:
Required variables
Link to this sectionKINDE_CLIENT_ID
- Your application’s client ID from KindeKINDE_CLIENT_SECRET
- Your application’s client secret from KindeKINDE_REDIRECT_URI
- The callback URL where Kinde will redirect after authenticationKINDE_HOST
- Your Kinde domain (e.g.,https://yourdomain.kinde.com
)KINDE_ISSUER_URL
- Your Kinde issuer URL (typically same as KINDE_HOST)GRANT_TYPE
- The OAuth grant type to use (e.g.,AUTHORIZATION_CODE_WITH_PKCE
)
Optional variables
Link to this sectionKINDE_AUDIENCE
- The intended recipient of the access token (for API access)KINDE_CALLBACK_URL
- Alternative name for KINDE_REDIRECT_URILOGOUT_REDIRECT_URL
- Where users are redirected after logoutSITE_HOST
- Your application’s host (default:127.0.0.1
)SITE_PORT
- Your application’s port (default:5000
)SITE_URL
- Your application’s base URLCODE_VERIFIER
- Required for PKCE flow (auto-generated if not provided)
Session management variables (core SDK features):
SECRET_KEY
- Used for session management and token securitySESSION_TYPE
- Session storage type (e.g.,filesystem
)SESSION_PERMANENT
- Whether sessions are permanent (default:False
)
Application configuration:
TEMPLATES_AUTO_RELOAD
- Whether to auto-reload templates (default:True
)
Management API variables (only needed if using Management API features):
MGMT_API_CLIENT_ID
- Management API client IDMGMT_API_CLIENT_SECRET
- Management API client secret
Example .env
file:
KINDE_CLIENT_ID=your_client_idKINDE_CLIENT_SECRET=your_client_secretKINDE_REDIRECT_URI=http://localhost:5000/api/auth/kinde_callbackKINDE_HOST=https://yourdomain.kinde.comKINDE_ISSUER_URL=https://yourdomain.kinde.comGRANT_TYPE=AUTHORIZATION_CODE_WITH_PKCESITE_HOST=localhostSITE_PORT=5000SITE_URL=http://localhost:5000LOGOUT_REDIRECT_URL=http://localhost:8000SECRET_KEY=your_secret_keySESSION_TYPE=filesystemSESSION_PERMANENT=FalseTEMPLATES_AUTO_RELOAD=True
Set callback URLs
Link to this section- In Kinde, go to Settings > Applications > [Your app] > View details.
- Add your callback URLs in the relevant fields. For example:
- Allowed callback URLs (also known as redirect URIs) - for example,
http://localhost:8000/callback
- Allowed logout redirect URLs - for example,
http://localhost:8000
- Allowed callback URLs (also known as redirect URIs) - for example,
- Select Save.
Add environments
Link to this sectionKinde comes with a production environment, but you can set up other environments if you want to. Note that each environment needs to be set up independently, so you need to use the Environment subdomain in the code block above for those new environments.
Configure your app
Link to this sectionThe OAuth client is now automatically configured based on the framework you’re using. Simply import the OAuth class from the auth module and create an instance:
from kinde_sdk.auth.oauth import OAuth
# For Flask applicationsfrom flask import Flaskapp = Flask(__name__)oauth = OAuth( framework="flask", app=app # optional: pass your Flask app instance)
# For FastAPI applicationsfrom fastapi import FastAPIapp = FastAPI()oauth = OAuth( framework="fastapi", app=app # optional: pass your FastAPI app instance)
The SDK will automatically detect and configure the appropriate framework implementation based on the framework parameter and app instance you provide.
Sign in and sign up
Link to this sectionThe Kinde client provides methods for easy sign in and sign up. You can add buttons in your HTML as follows:
<div class="navigation"> <a href="{{ url_for('login') }}" type="button">Sign in</a> <a href="{{ url_for('register') }}" type="button">Sign up</a></div>
Automatic Route Registration
Link to this sectionThe framework wrapper can automatically register all necessary routes. For Flask:
from kinde_sdk.auth.oauth import OAuthfrom flask import Flask
app = Flask(__name__)oauth = OAuth( framework="flask", app=app)
For FastAPI:
from kinde_sdk.auth.oauth import OAuthfrom fastapi import FastAPI
app = FastAPI()oauth = OAuth( framework="fastapi", app=app)
Manual route implementation
Link to this sectionIf you prefer to implement the routes manually, here’s how you can do it:
For Flask:
import asynciofrom flask import Flask, request, session, redirectfrom kinde_sdk.auth.oauth import OAuth
app = Flask(__name__)oauth = OAuth( framework="flask", app=app)
@app.route('/login')def login(): """Redirect to Kinde login page.""" loop = asyncio.get_event_loop() login_url = loop.run_until_complete(oauth.login()) return redirect(login_url)
@app.route('/register')def register(): """Redirect to Kinde registration page.""" loop = asyncio.get_event_loop() register_url = loop.run_until_complete(oauth.register()) return redirect(register_url)
@app.route('/callback')def callback(): """Handle the OAuth callback from Kinde.""" try: code = request.args.get('code') state = request.args.get('state')
if not code: return "Authentication failed: No code provided", 400
# Generate a unique user ID for the session user_id = session.get('user_id') or str(uuid.uuid4())
# Use OAuth's handle_redirect method to process the callback loop = asyncio.get_event_loop() result = loop.run_until_complete(oauth.handle_redirect(code, user_id, state))
# Store user ID in session session['user_id'] = user_id
return redirect('/') except Exception as e: return f"Authentication failed: {str(e)}", 400
@app.route('/logout')def logout(): """Logout the user and redirect to Kinde logout page.""" user_id = session.get('user_id') session.clear() loop = asyncio.get_event_loop() logout_url = loop.run_until_complete(oauth.logout(user_id)) return redirect(logout_url)
@app.route('/user')def get_user(): """Get the current user's information.""" try: if not oauth.is_authenticated(request): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: login_url = loop.run_until_complete(oauth.login()) return redirect(login_url) finally: loop.close()
return oauth.get_user_info(request) except Exception as e: return f"Failed to get user info: {str(e)}", 400
For FastAPI:
from fastapi import FastAPI, Requestfrom fastapi.responses import RedirectResponse
@app.get("/login")async def login(request: Request): url = await oauth.login() return RedirectResponse(url=url)
@app.get("/register")async def register(request: Request): url = await oauth.register() return RedirectResponse(url=url)
@app.get("/callback")async def callback(request: Request, code: str, state: Optional[str] = None): try: result = await oauth.handle_redirect(code, state) return RedirectResponse(url="/") except Exception as e: return HTMLResponse(f"Authentication failed: {str(e)}")
@app.get("/logout")async def logout(request: Request): request.session.clear() return RedirectResponse(url=await oauth.logout())
@app.get("/user")async def get_user(request: Request): if not oauth.is_authenticated(request): return RedirectResponse(url=await oauth.login()) return oauth.get_user_info(request)
The manual implementation gives you more control over the authentication flow and allows you to add custom logic like session management, error handling, and logging. Note that Flask requires special handling of async methods using asyncio
since it doesn’t natively support async/await like FastAPI does.
User permissions
Link to this sectionThe Kinde Python SDK provides a simple way to check user permissions in your application. First, import the permissions module:
from kinde_sdk.auth import permissions
Checking permissions
Link to this sectionTo check if a user has a specific permission:
# Check a single permissionpermission = await permissions.get_permission("create:todos")if permission["isGranted"]: # User has permission print(f"User has permission in organization: {permission['orgCode']}")
To get all permissions for the current user:
# Get all permissionsall_permissions = await permissions.get_permissions()print(f"User belongs to organization: {all_permissions['orgCode']}")print("User permissions:", all_permissions["permissions"])
Practical examples
Link to this sectionHere’s how to use permissions in your application:
# Example 1: Conditional Feature Accessasync def create_todo_button(): permission = await permissions.get_permission("create:todos") if permission["isGranted"]: return "<button>Create Todo</button>" return None
# Example 2: Permission-Based API Endpoint@router.post("/todos")async def create_todo(todo_data: dict): permission = await permissions.get_permission("create:todos") if not permission["isGranted"]: raise HTTPException(status_code=403, detail="Permission denied") # Create todo logic here...
Common permission patterns
Link to this sectionHere are some common permission patterns you might use:
# Resource-based permissions"create:todos""read:todos""update:todos""delete:todos"
# Feature-based permissions"can:export_data""can:manage_users""can:view_analytics"
# Organization-based permissions"org:manage_members""org:view_billing""org:update_settings"
For more information about setting up permissions in Kinde, see User permissions.
Feature flags
Link to this sectionThe Kinde Python SDK provides a simple way to access feature flags from your application. First, import the feature flags module:
from kinde_sdk.auth import feature_flags
Getting feature flags
Link to this sectionTo get a specific feature flag value:
# Get a string feature flagtheme_flag = await feature_flags.get_flag("theme")print(f"Current theme: {theme_flag.value}")
# Get a boolean feature flag with default valuedark_mode = await feature_flags.get_flag("is_dark_mode", default_value=False)if dark_mode.value: print("Dark mode is enabled")
# Get a numeric feature flagcompetitions_limit = await feature_flags.get_flag("competitions_limit")print(f"User can create up to {competitions_limit.value} competitions")
To get all feature flags for the current user:
# Get all feature flagsall_flags = await feature_flags.get_all_flags()for code, flag in all_flags.items(): print(f"- {code}: {flag.value} ({flag.type})")
Practical examples
Link to this sectionHere’s how to use feature flags in your application:
# Example 1: Conditional Feature Renderingasync def render_create_competition_button(): can_create = await feature_flags.get_flag("create_competition", default_value=False) if can_create.value: return "<button>Create Competition</button>" return None
# Example 2: Theme Configurationasync def get_user_theme(): theme = await feature_flags.get_flag("theme", default_value="light") dark_mode = await feature_flags.get_flag("is_dark_mode", default_value=False) return { "theme": theme.value, "is_dark_mode": dark_mode.value }
# Example 3: Feature Limits@router.post("/competitions")async def create_competition(competition_data: dict): limit_flag = await feature_flags.get_flag("competitions_limit", default_value=3) current_count = await get_user_competition_count()
if current_count >= limit_flag.value: raise HTTPException( status_code=403, detail=f"Competition limit reached (max: {limit_flag.value})" ) # Create competition logic here...
Feature flag types
Link to this sectionThe SDK supports the following feature flag types:
# String flags{ "t": "s", "v": "pink"}
# Boolean flags{ "t": "b", "v": true}
# Integer flags{ "t": "i", "v": 5}
Common use cases
Link to this section# Feature Togglescan_use_feature = await feature_flags.get_flag("enable_new_feature", default_value=False)
# User Preferencestheme = await feature_flags.get_flag("theme", default_value="light")dark_mode = await feature_flags.get_flag("is_dark_mode", default_value=False)
# Usage Limitsmax_uploads = await feature_flags.get_flag("max_uploads", default_value=10)
# A/B Testingtest_group = await feature_flags.get_flag("ab_test_group", default_value="control")
Claims
Link to this sectionThe Kinde Python SDK provides a simple way to access user claims from your application. First, import the claims module:
from kinde_sdk.auth import claims
Getting claims
Link to this sectionTo get a specific claim from the user’s tokens:
# Get the audience claim from the access tokenclaim = await claims.get_claim("aud")print(f"Token audience: {claim['value']}")
# Get the given_name claim from the ID tokenclaim = await claims.get_claim("given_name", token_type="id_token")print(f"User's given name: {claim['value']}")
To get all claims from the user’s tokens:
# Get all claims from the access tokenall_claims = await claims.get_all_claims()for claim_name, claim_value in all_claims.items(): print(f"- {claim_name}: {claim_value}")
# Get all claims from the ID tokenid_token_claims = await claims.get_all_claims(token_type="id_token")
Practical examples
Link to this sectionHere’s how to use claims in your application:
# Example 1: Accessing User Informationasync def get_user_profile(): given_name = await claims.get_claim("given_name", token_type="id_token") family_name = await claims.get_claim("family_name", token_type="id_token")
if given_name["value"] and family_name["value"]: return { "name": f"{given_name['value']} {family_name['value']}", "email": (await claims.get_claim("email", token_type="id_token"))["value"] } return None
# Example 2: Token Validation@router.get("/api/protected")async def protected_endpoint(): aud_claim = await claims.get_claim("aud") if not aud_claim["value"] or "api.yourapp.com" not in aud_claim["value"]: raise HTTPException(status_code=401, detail="Invalid token audience") return {"message": "Access granted"}
Common claims
Link to this sectionHere are some common claims you might want to access:
# User Information (ID Token)"given_name""family_name""email""picture"
# Token Information (Access Token)"aud" # Audience"iss" # Issuer"exp" # Expiration time"iat" # Issued at time
# Organization Information"org_code""org_name""org_id"
Organizations
Link to this sectionCreate an organization
Link to this sectionTo create a new organization within your application, you will need to run a similar function to below:
return app.redirect(oauth.create_org())
Sign up and sign in to organizations
Link to this sectionKinde has a unique code for every organization. You’ll have to pass this code through when you register a new user or sign in to a particular organization. Example function below:
oauth.get_claim("org_code")
@app.route("/login")def login(): return app.redirect(oauth.get_login_url())
@app.route("/register")def register(): return app.redirect(oauth.get_register_url())
Following authentication, Kinde provides a json web token (jwt) to your application. Along with the standard information we also include the org_code
and the permissions for that organization (this is important as a user can belong to multiple organizations and have different permissions for each).
Example of a returned token:
{ "aud": [], "exp": 1658475930, "iat": 1658472329, "iss": "https://your_subdomain.kinde.com", "jti": "123457890", "org_code": "org_1234", "permissions": ["read:todos", "create:todos"], "scp": [ "openid", "profile", "email", "offline" ], "sub": "kp:123457890"}
The id_token
will also contain an array of organizations that a user belongs to - this is useful if you wanted to build out an organization switcher for example.
{ ... "org_codes": ["org_1234", "org_4567"], ...};
There are two helper functions you can use to extract information:
oauth.get_organization()
oauth.get_user_organizations()
For more information about how organizations work in Kinde, see Kinde organizations for developers.
Token and session management
Link to this sectionThe Kinde Python SDK automatically handles token and session management for your application. Once a user has successfully authenticated, the SDK manages:
- Token acquisition and storage: Automatically obtains and securely stores access tokens, ID tokens, and refresh tokens
- Token refresh: Automatically refreshes tokens when they expire
- Session management: Handles user sessions across requests
- Framework integration: Works seamlessly with Flask and FastAPI session systems
The SDK uses the session configuration from your environment variables (SECRET_KEY
, SESSION_TYPE
, SESSION_PERMANENT
) to manage sessions appropriately for your chosen framework.
Token types
Link to this sectionThe SDK supports two types of tokens:
-
Access Token (
token_type="access_token"
):- Contains authorization information
- Used for API access
- Contains permissions and organization context
- Default token type
-
ID Token (
token_type="id_token"
):- Contains user identity information
- Used for user profile data
- Contains name, email, and other user details
- Must be explicitly requested using
token_type="id_token"
Session handling
Link to this sectionThe SDK automatically integrates with your framework’s session system:
- Flask: Uses Flask’s built-in session management
- FastAPI: Integrates with FastAPI’s session handling
You don’t need to manually manage tokens or sessions - the SDK handles this automatically for you.
Management API
Link to this sectionThe Kinde Python SDK provides a Management API client for interacting with Kinde’s management endpoints. This allows you to programmatically manage users, organizations, and other resources.
Getting started
Link to this sectionTo use the Management API, you’ll need to initialize the client with your Kinde credentials:
from kinde_sdk.auth.oauth import OAuth
oauth = OAuth( framework="flask", app=app)
# Get the management clientmanagement = oauth.get_management()
Available endpoints
Link to this sectionThe Management API provides methods for common operations on resources. Here are some examples:
# List usersusers = await management.get_users()
# Get a specific useruser = await management.get_user(user_id="user_123")
# Create a new usernew_user = await management.create_user( email="user@example.com", given_name="John", family_name="Doe")
# Update a userupdated_user = await management.update_user( user_id="user_123", given_name="Johnny")
# Delete a userawait management.delete_user(user_id="user_123")
Organization management
Link to this section# List organizationsorgs = await management.get_organizations()
# Get a specific organizationorg = await management.get_organization(org_id="org_123")
# Create a new organizationnew_org = await management.create_organization( name="My Organization")
# Update an organizationupdated_org = await management.update_organization( org_id="org_123", name="Updated Name")
# Delete an organizationawait management.delete_organization(org_id="org_123")
Error handling
Link to this sectionThe Management API methods will raise exceptions for API errors. It’s recommended to handle these appropriately:
try: user = await management.get_user(user_id="user_123")except Exception as e: # Handle API-specific errors print(f"Error: {e}")
Token management
Link to this sectionThe Management API client has its own token management system for API authentication, which is separate from the core SDK’s user session token management. The Management API client automatically handles:
- accessing Kinde Management API endpoints: Obtains tokens for accessing Kinde’s management endpoints
- Token refresh: Automatically refreshes management API tokens when they expire
- Token storage: Securely stores management API tokens
- Thread safety: Ensures thread-safe token handling for concurrent requests
You don’t need to manually manage Management API tokens - the client handles this for you. This is different from the core SDK’s user session token management, which handles user authentication tokens automatically.
Best practices
Link to this section- Always use async/await when calling Management API methods
- Handle API errors appropriately
- Cache results when appropriate to reduce API calls
- Use appropriate error handling for production environments
- Keep your client credentials secure
For more information about the Management API endpoints and capabilities, see the Kinde Management API documentation.