OAuth 2.1 Authentication
FLTR uses OAuth 2.1 with PKCE (Proof Key for Code Exchange) to provide secure authentication for MCP clients like Claude Desktop, VS Code, Cursor, and web applications.
Overview
OAuth 2.1 provides:
15,000 requests/hour rate limit (15x more than API keys)
Scope-based permissions for fine-grained access control
PKCE security to prevent authorization code interception
Automatic token refresh for seamless user experience
Native MCP support for AI development tools
Quick Start for MCP Clients
Configure Claude Desktop
Add FLTR to your Claude Desktop MCP configuration: {
"mcpServers" : {
"fltr" : {
"command" : "npx" ,
"args" : [ "-y" , "@fltr/mcp-server" ],
"env" : {
"FLTR_OAUTH_CLIENT_ID" : "your_client_id" ,
"FLTR_AUTH_URL" : "https://www.tryfltr.com/oauth/authorize"
}
}
}
}
Authorize Access
Restart Claude Desktop
Click “Connect to FLTR” when prompted
Log in and authorize the requested scopes
Return to Claude Desktop - you’re connected!
Start Using FLTR
Query your datasets directly from Claude: Search my "Product Docs" dataset for information about authentication
OAuth Flow
FLTR implements the Authorization Code flow with PKCE:
Implementation Guide
1. Register Your Application
Log in to www.tryfltr.com
Navigate to Settings → OAuth Applications
Click Create OAuth App
Fill in the details:
Name : Your application name
Redirect URI : Where users return after authorization
Scopes : Permissions your app needs
Save your Client ID and Client Secret
Store your Client Secret securely. Never commit it to version control or expose it in client-side code.
2. Generate PKCE Parameters
Before starting the OAuth flow, generate PKCE parameters:
import secrets
import hashlib
import base64
# Generate code_verifier
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes( 32 )).decode( 'utf-8' )
code_verifier = code_verifier.rstrip( '=' )
# Generate code_challenge
code_challenge = hashlib.sha256(code_verifier.encode( 'utf-8' )).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode( 'utf-8' )
code_challenge = code_challenge.rstrip( '=' )
print ( f "code_verifier: { code_verifier } " )
print ( f "code_challenge: { code_challenge } " )
3. Authorization Request
Redirect the user to FLTR’s authorization endpoint:
https://www.tryfltr.com/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
scope=datasets:read datasets:write mcp:query&
state=RANDOM_STATE&
code_challenge=CODE_CHALLENGE&
code_challenge_method=S256
Parameters:
Parameter Required Description response_typeYes Must be code client_idYes Your OAuth client ID redirect_uriYes Where to redirect after authorization scopeYes Space-separated list of scopes stateRecommended Random string to prevent CSRF code_challengeYes SHA256 hash of code_verifier code_challenge_methodYes Must be S256
Available Scopes:
datasets:read Read datasets and documents
datasets:write Create and modify datasets
datasets:delete Delete datasets and documents
mcp:query Use MCP query endpoints
mcp:batch_query Use batch query endpoints
webhooks:manage Create and manage webhooks
account:read Read account information
from urllib.parse import urlencode
params = {
'response_type' : 'code' ,
'client_id' : 'YOUR_CLIENT_ID' ,
'redirect_uri' : 'https://yourapp.com/callback' ,
'scope' : 'datasets:read datasets:write mcp:query' ,
'state' : secrets.token_urlsafe( 32 ),
'code_challenge' : code_challenge,
'code_challenge_method' : 'S256'
}
auth_url = f "https://www.tryfltr.com/oauth/authorize? { urlencode(params) } "
print ( f "Redirect user to: { auth_url } " )
4. Handle Callback
After the user authorizes, FLTR redirects back to your redirect_uri with an authorization code:
https://yourapp.com/callback?
code=AUTH_CODE&
state=RANDOM_STATE
Verify the state parameter matches what you sent to prevent CSRF attacks.
5. Exchange Code for Token
Exchange the authorization code for an access token:
POST https://www.tryfltr.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type = authorization_code &
code = AUTH_CODE &
client_id = YOUR_CLIENT_ID &
client_secret = YOUR_CLIENT_SECRET &
redirect_uri = YOUR_REDIRECT_URI &
code_verifier = CODE_VERIFIER
import requests
token_response = requests.post(
'https://www.tryfltr.com/oauth/token' ,
data = {
'grant_type' : 'authorization_code' ,
'code' : auth_code,
'client_id' : 'YOUR_CLIENT_ID' ,
'client_secret' : 'YOUR_CLIENT_SECRET' ,
'redirect_uri' : 'https://yourapp.com/callback' ,
'code_verifier' : code_verifier
}
)
tokens = token_response.json()
access_token = tokens[ 'access_token' ]
refresh_token = tokens[ 'refresh_token' ]
expires_in = tokens[ 'expires_in' ] # Seconds until expiration
Response:
{
"access_token" : "fltr_at_abc123..." ,
"refresh_token" : "fltr_rt_def456..." ,
"token_type" : "Bearer" ,
"expires_in" : 3600 ,
"scope" : "datasets:read datasets:write mcp:query"
}
6. Make API Requests
Use the access token to make authenticated API requests:
headers = {
'Authorization' : f 'Bearer { access_token } ' ,
'Content-Type' : 'application/json'
}
response = requests.get(
'https://api.fltr.com/v1/datasets' ,
headers = headers
)
7. Refresh Tokens
Access tokens expire after 1 hour. Use the refresh token to get a new access token:
POST https://www.tryfltr.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type = refresh_token &
refresh_token = REFRESH_TOKEN &
client_id = YOUR_CLIENT_ID &
client_secret = YOUR_CLIENT_SECRET
def refresh_access_token ( refresh_token ):
response = requests.post(
'https://www.tryfltr.com/oauth/token' ,
data = {
'grant_type' : 'refresh_token' ,
'refresh_token' : refresh_token,
'client_id' : 'YOUR_CLIENT_ID' ,
'client_secret' : 'YOUR_CLIENT_SECRET'
}
)
tokens = response.json()
return tokens[ 'access_token' ], tokens[ 'refresh_token' ]
# Automatically refresh when token expires
if time.time() >= token_expires_at:
access_token, refresh_token = refresh_access_token(refresh_token)
token_expires_at = time.time() + 3600
MCP Server Configuration
For MCP clients (Claude Desktop, VS Code, Cursor), use the FLTR MCP server:
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
%APPDATA%\Claude\claude_desktop_config.json (Windows)
{
"mcpServers" : {
"fltr" : {
"command" : "npx" ,
"args" : [ "-y" , "@fltr/mcp-server" ],
"env" : {
"FLTR_OAUTH_CLIENT_ID" : "your_client_id" ,
"FLTR_AUTH_URL" : "https://www.tryfltr.com/oauth/authorize"
}
}
}
}
VS Code / Cursor
.vscode/settings.json or Cursor settings:
{
"mcp.servers" : {
"fltr" : {
"command" : "npx" ,
"args" : [ "-y" , "@fltr/mcp-server" ],
"env" : {
"FLTR_OAUTH_CLIENT_ID" : "your_client_id" ,
"FLTR_AUTH_URL" : "https://www.tryfltr.com/oauth/authorize"
}
}
}
}
Security Best Practices
PKCE
Always use PKCE (code_challenge + code_verifier):
✅ Do:
Generate a cryptographically random code_verifier (43-128 characters)
Use SHA256 for code_challenge_method
Store code_verifier securely until token exchange
❌ Don’t:
Skip PKCE (it’s required)
Reuse code_verifier across sessions
Use MD5 or other weak hashing
State Parameter
Use the state parameter to prevent CSRF:
# Generate state
state = secrets.token_urlsafe( 32 )
session[ 'oauth_state' ] = state
# Verify state in callback
if request.args.get( 'state' ) != session.get( 'oauth_state' ):
raise SecurityError( "Invalid state parameter" )
Token Storage
Server-side applications:
Store tokens in encrypted database
Use HTTP-only cookies for refresh tokens
Never expose tokens to client-side JavaScript
Client-side applications:
Use secure storage (Keychain on macOS, Credential Manager on Windows)
Never store tokens in localStorage
Consider using httpOnly cookies with a token proxy
Redirect URI Validation
Register exact redirect URIs (no wildcards)
Use HTTPS in production (required)
Validate redirect_uri in both authorization and token requests
Error Handling
Authorization Errors
{
"error" : "invalid_scope" ,
"error_description" : "The requested scope is invalid or unknown"
}
Common errors:
access_denied - User denied authorization
invalid_scope - Unknown or unauthorized scope
invalid_request - Missing required parameters
server_error - Temporary server issue
Token Errors
{
"error" : "invalid_grant" ,
"error_description" : "The provided authorization code is invalid or expired"
}
Common errors:
invalid_grant - Expired or invalid authorization code
invalid_client - Invalid client credentials
unauthorized_client - Client not authorized for this grant type
Handle token refresh failures:
try :
access_token, refresh_token = refresh_access_token(refresh_token)
except Exception as e:
# Refresh failed - re-authenticate user
redirect_to_authorization()
Complete Example
Here’s a complete Flask application with OAuth:
from flask import Flask, redirect, request, session, url_for
import requests
import secrets
import hashlib
import base64
app = Flask( __name__ )
app.secret_key = 'your-secret-key'
CLIENT_ID = 'your_client_id'
CLIENT_SECRET = 'your_client_secret'
REDIRECT_URI = 'http://localhost:5000/callback'
AUTH_URL = 'https://www.tryfltr.com/oauth/authorize'
TOKEN_URL = 'https://www.tryfltr.com/oauth/token'
@app.route ( '/' )
def index ():
if 'access_token' in session:
return 'Logged in! <a href="/datasets">View datasets</a>'
return '<a href="/login">Login with FLTR</a>'
@app.route ( '/login' )
def login ():
# Generate PKCE parameters
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes( 32 )).decode( 'utf-8' ).rstrip( '=' )
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode( 'utf-8' )).digest()
).decode( 'utf-8' ).rstrip( '=' )
# Store for later use
session[ 'code_verifier' ] = code_verifier
session[ 'oauth_state' ] = secrets.token_urlsafe( 32 )
# Build authorization URL
params = {
'response_type' : 'code' ,
'client_id' : CLIENT_ID ,
'redirect_uri' : REDIRECT_URI ,
'scope' : 'datasets:read mcp:query' ,
'state' : session[ 'oauth_state' ],
'code_challenge' : code_challenge,
'code_challenge_method' : 'S256'
}
from urllib.parse import urlencode
auth_url = f " { AUTH_URL } ? { urlencode(params) } "
return redirect(auth_url)
@app.route ( '/callback' )
def callback ():
# Verify state
if request.args.get( 'state' ) != session.get( 'oauth_state' ):
return 'Invalid state' , 400
# Exchange code for token
code = request.args.get( 'code' )
code_verifier = session.get( 'code_verifier' )
token_response = requests.post( TOKEN_URL , data = {
'grant_type' : 'authorization_code' ,
'code' : code,
'client_id' : CLIENT_ID ,
'client_secret' : CLIENT_SECRET ,
'redirect_uri' : REDIRECT_URI ,
'code_verifier' : code_verifier
})
tokens = token_response.json()
session[ 'access_token' ] = tokens[ 'access_token' ]
session[ 'refresh_token' ] = tokens[ 'refresh_token' ]
return redirect(url_for( 'index' ))
@app.route ( '/datasets' )
def datasets ():
headers = {
'Authorization' : f "Bearer { session[ 'access_token' ] } "
}
response = requests.get( 'https://api.fltr.com/v1/datasets' , headers = headers)
return response.json()
if __name__ == '__main__' :
app.run( debug = True )
Next Steps
MCP Query Endpoint Use OAuth tokens to query datasets
Scopes Reference Understand OAuth scope permissions
Rate Limits OAuth rate limit details
Security Guide OAuth security best practices