OAuth API Reference
Complete reference for Flowsta OAuth 2.0 endpoints.
Base URL
https://auth-api.flowsta.comAll endpoints use this base URL.
Authentication
OAuth endpoints use different authentication methods:
| Endpoint | Authentication Method |
|---|---|
/oauth/authorize | None (public) |
/oauth/token | PKCE (client_id + code_verifier) |
/oauth/userinfo | Bearer token (Authorization header) |
/oauth/revoke | Client ID header (X-Client-Id) |
No Client Secret Required
Flowsta uses OAuth 2.0 with PKCE, which means you don't need a client secret. PKCE provides security for browser and mobile apps without exposing secrets in frontend code.
Authorization Endpoint
GET /oauth/authorize
Initiates the OAuth flow by redirecting the user to the Flowsta login page.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
response_type | string | ✅ | Must be code |
client_id | string | ✅ | Your application's client ID |
redirect_uri | string | ✅ | One of your configured redirect URIs |
scope | string | ✅ | Space-separated scopes (see Available Scopes) |
state | string | ⚠️ | CSRF protection token (highly recommended) |
code_challenge | string | ⚠️ | PKCE code challenge (highly recommended) |
code_challenge_method | string | ⚠️ | Must be S256 (if using PKCE) |
Example Request
GET https://auth-api.flowsta.com/oauth/authorize?
response_type=code&
client_id=abc123...&
redirect_uri=https://yourapp.com/auth/callback&
scope=profile%20email&
state=random_state_string&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256Response
Redirect to login page:
If the user is not logged in:
https://login.flowsta.com/login?
client_id=abc123...&
redirect_uri=https://yourapp.com/auth/callback&
scope=profile%20email&
state=random_state_string&
code_challenge=E9Melhoa...&
code_challenge_method=S256Redirect to callback:
After successful authentication and consent:
https://yourapp.com/auth/callback?
code=def456...&
state=random_state_stringError Responses
If an error occurs, the user is redirected to redirect_uri with error parameters:
https://yourapp.com/auth/callback?
error=invalid_request&
error_description=Missing+required+parameter+client_id&
state=random_state_stringError Codes:
| Error | Description |
|---|---|
invalid_request | Missing or invalid required parameters |
unauthorized_client | Client ID not found or app disabled |
access_denied | User denied authorization |
unsupported_response_type | response_type is not code |
invalid_scope | One or more requested scopes are invalid |
server_error | Internal server error |
Token Endpoint
POST /oauth/token
Exchange authorization code for access token and refresh token.
Request Headers
Content-Type: application/jsonRequest Body
Grant Type: authorization_code
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | ✅ | Must be authorization_code |
code | string | ✅ | Authorization code from callback |
redirect_uri | string | ✅ | Must match the original redirect URI |
client_id | string | ✅ | Your application's client ID |
code_verifier | string | ✅ | PKCE code verifier |
PKCE Security
PKCE replaces client secrets for all clients. The code_verifier proves you initiated the authorization request without needing to store secrets.
Grant Type: refresh_token
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | ✅ | Must be refresh_token |
refresh_token | string | ✅ | The refresh token |
client_id | string | ✅ | Your application's client ID |
Example Request (Authorization Code)
POST /oauth/token HTTP/1.1
Host: auth-api.flowsta.com
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "def456...",
"redirect_uri": "https://yourapp.com/auth/callback",
"client_id": "flowsta_app_abc123...",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}Example Request (Refresh Token)
POST /oauth/token HTTP/1.1
Host: auth-api.flowsta.com
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "ghi012...",
"client_id": "flowsta_app_abc123..."
}Success Response
Status: 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "ghi012...",
"scope": "profile email"
}| Field | Type | Description |
|---|---|---|
access_token | string | JWT access token (valid for 1 hour) |
token_type | string | Always Bearer |
expires_in | number | Seconds until token expires (3600 = 1 hour) |
refresh_token | string | Refresh token (valid for 30 days) |
scope | string | Granted scopes (may differ from requested) |
Error Response
Status: 400 Bad Request
{
"error": "invalid_grant",
"error_description": "Authorization code has expired or is invalid"
}Error Codes:
| Error | Description |
|---|---|
invalid_request | Missing required parameters |
invalid_client | Invalid client credentials |
invalid_grant | Invalid or expired authorization code |
unauthorized_client | Client not authorized for this grant type |
unsupported_grant_type | Grant type not supported |
User Info Endpoint
GET /oauth/userinfo
Retrieve user profile information using an access token.
Request Headers
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Example Request
GET /oauth/userinfo HTTP/1.1
Host: auth-api.flowsta.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Success Response
Status: 200 OK
With openid display_name scopes only:
{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe"
}With all scopes (and user has granted permission):
{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"preferred_username": "johndoe",
"did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"agent_pub_key": "uhCAk7JpEWfkiV_RdAFfCnRZcJ9PwJR4yTLN-E3EcVU7KYCnRRZc",
"profile_picture": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0...",
"has_custom_picture": true,
"email": "john@example.com",
"email_verified": true
}Email Requires Permission
The email field is only included if:
- The access token has
emailscope - The user has granted email permission for your app
If permission is not granted, email and emailVerified will not be present.
| Field | Type | Scope | Description |
|---|---|---|---|
sub | string (UUID) | openid | Unique user identifier (always included) |
name | string | display_name | User's display name |
preferred_username | string | username | Username (if set by user) |
did | string | did | W3C Decentralized Identifier |
agent_pub_key | string | public_key | Holochain agent public key |
profile_picture | string | profile_picture | Profile picture (base64 data URI or URL) |
has_custom_picture | boolean | profile_picture | Whether user uploaded a custom picture |
email | string | email | Email address (if permitted) |
email_verified | boolean | email | Email verification status |
Error Response
Status: 401 Unauthorized
{
"error": "invalid_token",
"error_description": "Access token is invalid or expired"
}Error Codes:
| Error | Description |
|---|---|
invalid_token | Token is invalid, expired, or revoked |
insufficient_scope | Token doesn't have required scopes |
Token Revocation Endpoint
POST /oauth/revoke
Revoke a refresh token to logout the user.
Request Headers
Content-Type: application/json
X-Client-Id: abc123...Request Body
| Field | Type | Required | Description |
|---|---|---|---|
token | string | ✅ | The refresh token to revoke |
token_type_hint | string | ❌ | Must be refresh_token |
Example Request
POST /oauth/revoke HTTP/1.1
Host: auth-api.flowsta.com
Content-Type: application/json
X-Client-Id: abc123...
{
"token": "ghi012...",
"token_type_hint": "refresh_token"
}Success Response
Status: 200 OK
{
"message": "Token revoked successfully"
}Error Response
Status: 400 Bad Request
{
"error": "invalid_request",
"error_description": "Token parameter is required"
}PKCE (Proof Key for Code Exchange)
What is PKCE?
PKCE enhances OAuth security by preventing authorization code interception attacks. It's especially important for public clients (SPAs, mobile apps).
How PKCE Works
- Generate code verifier (random 43-128 character string)
- Generate code challenge (SHA256 hash of verifier, base64url encoded)
- Send code challenge with authorization request
- Send code verifier with token exchange request
- Server verifies that challenge matches verifier
Implementation
import crypto from 'crypto';
// 1. Generate code verifier
function generateCodeVerifier() {
return crypto.randomBytes(32).toString('base64url');
}
// 2. Generate code challenge
function generateCodeChallenge(verifier) {
return crypto
.createHash('sha256')
.update(verifier)
.digest('base64url');
}
// Usage
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);
// Store verifier in session for later use
sessionStorage.setItem('code_verifier', codeVerifier);
// Use challenge in authorization request
const authUrl = `https://auth-api.flowsta.com/oauth/authorize?
response_type=code&
client_id=${clientId}&
redirect_uri=${redirectUri}&
scope=openid%20display_name%20email%20profile_picture&
code_challenge=${codeChallenge}&
code_challenge_method=S256`;Button Widget Handles PKCE
The @flowsta/login-button package automatically handles PKCE for you. You don't need to implement it manually.
Rate Limiting
OAuth endpoints are rate-limited to prevent abuse:
| Endpoint | Rate Limit |
|---|---|
/oauth/authorize | 100 requests per minute per IP |
/oauth/token | 50 requests per minute per client |
/oauth/userinfo | 500 requests per minute per token |
/oauth/revoke | 50 requests per minute per client |
Rate Limit Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1699564800429 Too Many Requests Response:
{
"error": "rate_limit_exceeded",
"error_description": "Too many requests. Please try again later.",
"retry_after": 60
}Token Lifetimes
| Token Type | Lifetime | Renewable? |
|---|---|---|
| Authorization Code | 10 minutes | ❌ No |
| Access Token | 1 hour | ❌ No (use refresh token) |
| Refresh Token | 30 days | ✅ Yes (sliding window) |
Refresh Token Sliding Window
When you use a refresh token to get a new access token, the refresh token's expiration extends by 30 days from the time of use.
Example:
- Day 1: Issue refresh token (expires Day 31)
- Day 20: Use refresh token → expires Day 50
- Day 45: Use refresh token → expires Day 75
Scopes
Available Scopes
Flowsta uses granular scopes so users only consent to the specific data your app needs:
| Scope | Data Included | Description |
|---|---|---|
openid | sub | User ID (UUID) - auto-included |
display_name | name | User's display name |
username | preferred_username | User's @username |
email | email, email_verified | Email address |
profile_picture | profile_picture, has_custom_picture | Profile picture |
did | did | W3C Decentralized Identifier |
public_key | agent_pub_key | Holochain agent public key |
Request Only What You Need
Instead of requesting all scopes, choose only the ones your app actually needs. This builds user trust and simplifies the consent screen.
Requesting Multiple Scopes
Separate scopes with spaces:
scope=openid display_name email profile_pictureURL encoded:
scope=openid%20display_name%20email%20profile_pictureScope Downgrading
If a user hasn't granted permission for a requested scope, the token will be issued with reduced scopes.
Request: scope=openid display_name email
User hasn't granted email permission
Token issued with: scope=openid display_name
Always check the scope field in the token response to see what was actually granted.
Security Best Practices
1. Always Use PKCE
// ✅ Good: With PKCE
const authUrl = buildAuthUrl({
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
// ❌ Bad: Without PKCE
const authUrl = buildAuthUrl({});2. Always Use State Parameter
// ✅ Good: With state for CSRF protection
const state = crypto.randomBytes(16).toString('hex');
sessionStorage.setItem('oauth_state', state);
// ❌ Bad: Without state
const authUrl = buildAuthUrl({ /* no state */ });3. Verify State in Callback
// ✅ Good: Verify state
const callbackState = new URLSearchParams(window.location.search).get('state');
const storedState = sessionStorage.getItem('oauth_state');
if (callbackState !== storedState) {
throw new Error('Possible CSRF attack');
}4. Use HTTPS
All redirect URIs must use HTTPS in production:
// ✅ Good
redirect_uri: 'https://yourapp.com/callback'
// ❌ Bad (except localhost)
redirect_uri: 'http://yourapp.com/callback'Testing
Test Credentials
Use these test values during development:
// Development
const clientId = 'test_client_id';
const redirectUri = 'http://localhost:3000/callback';Test Users
Create test accounts at dev.flowsta.com for testing.
Test OAuth Flow Button
In your app dashboard, use the "Test OAuth Flow" button to verify your configuration.
Need Help?
- 💬 Discord: Join our community
- 🆘 Support: Find out about Flowsta support options
- 🐙 GitHub: github.com/WeAreFlowsta