OAuth Development Guide
Contents
OAuth Development Guide
This guide helps developers set up and test PostHog's OAuth apps locally.
Quick Start
1. Configure RSA Keys
OAuth uses RS256 for signing JWT tokens. Copy the RSA private key from the example file:
Or generate a new key pair:
2. Set Up Your Environment
First, generate demo data which includes a test OAuth application:
3. Access the Demo Application
After running generate_demo_data, a test OAuth application is created with these credentials:
- Client ID:
DC5uRLVbGI02YQ82grxgnK6Qn12SXWpCqdPb60oZ - Client Secret:
GQItUP4GqE6t5kjcWIRfWO9c0GXPCY8QDV4eszH4PnxXwCVxIMVSil4Agit7yay249jasnzHEkkVqHnFMxI1YTXSrh8Bj1sl1IDfNi1S95sv208NOc0eoUBP3TdA7vf0 - Redirect URIs:
http://localhost:3000/callback,https://example.com/callback,http://localhost:8237/callback,http://localhost:8239/callback
You can view and test the OAuth flow from Django admin:
- Navigate to
http://localhost:8010/admin/posthog/oauthapplication/ - Click "View on site" to see an example authorization URL with PKCE parameters
Creating an OAuth Application
Via Django Admin
- Navigate to
http://localhost:8010/admin/posthog/oauthapplication/ - Click "Add OAuth Application"
- Configure the application fields (see below)
Application Fields
Basic Information
Name (required)
- Display name for the application
- Shown to users during authorization
- Example: "PostHog Mobile App", "Analytics Dashboard"
Client ID (auto-generated)
- Unique identifier for your application
- Automatically generated but can be customized
- Used in authorization requests
- Example:
DC5uRLVbGI02YQ82grxgnK6Qn12SXWpCqdPb60oZ
Client Secret (auto-generated)
- Confidential credential for the application
- Only shown once during creation
- Gets hashed after saving
- Only used by confidential clients
- ⚠️ Copy this before you save the application - you cannot view it again after creation
Client Type (required)
Confidential: For server-side applications that can securely store secretsPublic: For client-side apps (mobile, SPA) that cannot securely store secrets
See Client Types section for detailed explanation.
Authorization Settings
Authorization Grant Type (required, fixed)
- Only
Authorization codeis supported
Redirect URIs (required)
Whitespace-separated list of valid redirect URIs
PostHog will only redirect to these URIs after authorization
HTTPS required for non-localhost URIs
HTTP allowed only for localhost/loopback addresses (127.0.0.1)
No fragments (#) allowed
Examples:
text
Algorithm (required, fixed)
- Only
RS256(RSA with SHA-256) is supported - Used for signing ID tokens
- More secure than symmetric algorithms (HS256)
- Cannot be changed
Ownership
User
- The PostHog user who created the application
- Not used for access control
- Helps track who created the app
Organization
- The organization that owns this application
- In the future, we will allow organizations to manage their apps in their settings
- If organization is deleted, app becomes orphaned but remains active.
Client Types
Confidential Clients
Use for: Server-side applications, backend services, traditional web apps
Characteristics:
- Can securely store the client secret
- Runs in a trusted environment (your servers)
- Must authenticate with both client_id and client_secret when exchanging authorization code for tokens
Examples:
- Django/Rails/Express web applications
- Backend services
- Server-to-server integrations
Security: Higher - the secret never leaves your secure server environment
Public Clients
Use for: Single-page apps (SPAs), mobile apps, desktop apps
Characteristics:
- Cannot securely store secrets (code is distributed to users)
- Relies on PKCE for security instead of client secret
- Only needs client_id for token exchange
Examples:
- React/Vue/Angular applications
- iOS/Android mobile apps
- Electron desktop applications
OAuth Flow
Standard Authorization Code Flow with PKCE
Generate PKCE parameters (client-side):
PythonRedirect user to authorization URL:
textUser authorizes the application and selects access level
Receive authorization code at redirect_uri:
textExchange code for tokens:
TerminalResponse includes:
JSON
Available Scopes
OAuth supports all the same scopes as Personal API Keys. Each scope has a read and/or write action (e.g., experiment:read, experiment:write).
For a complete list of available scopes, see frontend/src/lib/scopes.tsx.
OpenID Connect Scopes
Standard OpenID Connect scopes are also supported:
openid- Required for OpenID Connect (provides ID token with user identity claims)profile- Access to user profile information (name, username, etc.)email- Access to user email address
Access Levels
When authorizing an application, users can scope access to:
- All: Access to all organizations and teams the user is a member of
- Organization: Access limited to specific organizations
- Team: Access limited to specific teams/projects
This is configured during the authorization step, not in the application settings.
If you would like to force the user to pick a single team or an organization you can use the required_access_level=project or required_access_level=organization query parameter in the authorization url.
Testing Your OAuth Application
Using the Admin Interface
- Go to
http://localhost:8010/admin/posthog/oauthapplication/ - Click your application
- Click "View on site" - this generates a test authorization URL with:
- Proper PKCE code_challenge (using code_verifier="test")
- First configured redirect_uri
- Example scopes
Manual Testing
Use the demo application credentials to test the full flow:
Endpoints
- Authorization:
/oauth/authorize/ - Token Exchange:
/oauth/token/ - Token Introspection:
/oauth/introspect/ - User Info:
/oauth/userinfo/ - JWKS (Public Keys):
/oauth/.well-known/jwks.json - OpenID Configuration:
/oauth/.well-known/openid-configuration/
Token Introspection
The introspection endpoint (/oauth/introspect/) allows you to check if a token is active and retrieve metadata about it. This is useful for validating tokens, checking their scopes or their scoped_teams and scoped_organizations.
Authentication Methods
The introspection endpoint supports three authentication methods:
1. HTTP Basic Authentication (Recommended)
2. Client Credentials in Request Body
3. Bearer Token Authentication
Important: When using Bearer token authentication (method 3), the bearer token must have the introspection scope. Client authentication methods (1 and 2) do not require any scopes.
Scope Requirements
- Client Authentication (HTTP Basic or Credentials): No scope required
- Bearer Token Authentication: Requires
introspectionscope
This means you can introspect any token using your application's client credentials, regardless of what scopes the token being introspected has. However, if you want to use an access token to introspect other tokens, that access token must have been granted the introspection scope.
Response Format
Active Token Response:
Inactive/Invalid Token Response:
Token Types
- Access Tokens: Return
"active": trueif valid and not expired - Refresh Tokens: Always return
"active": false(refresh tokens cannot be introspected)
Example: Introspecting with Client Credentials
Example: Introspecting with Bearer Token
To introspect tokens using another access token, ensure the bearer token has the introspection scope:
Troubleshooting
"Invalid client_id"
- Check the client_id matches exactly
- Verify the application exists in
https://localhost:8010/admin/posthog/oauthapplication/
"Redirect URI mismatch"
- Ensure redirect_uri in request matches one configured in application, make sure you included the path and not just the base url
- Check for trailing slashes
- Verify HTTP vs HTTPS
"Invalid code_verifier"
- The code_verifier used in token exchange must match the one used to generate code_challenge
- Ensure code_challenge was generated correctly using SHA256, you should send the hashed version as the code_challenge in the authorize request, and the original as the code_verifier in the token request
"Invalid client_secret"
- For confidential clients, ensure you saved the secret during creation, after creation you will see a hashed version in Django admin which is not your client secret
- Secrets cannot be retrieved after creation - you'll need to create a new application
Additional Resources
- OAuth 2.0 RFC: https://tools.ietf.org/html/rfc6749
- OpenID Connect Core: https://openid.net/specs/openid-connect-core-1_0.html
- PKCE RFC: https://tools.ietf.org/html/rfc7636