OAuth 2.1 Clients
Create and manage OAuth clients for secure authentication
OAuth 2.1 Clients
OAuth 2.1 clients enable secure, industry-standard authentication for advanced integrations with n8n MCP Bridge.
What are OAuth Clients?
OAuth 2.1 clients are applications that can authenticate users and access the MCP server using the OAuth protocol. Unlike API keys (which are permanent), OAuth clients use temporary access tokens that expire and can be refreshed.
When to Use OAuth vs API Keys
Use API Keys When:
- Connecting personal AI clients (Claude Desktop, etc.)
- Simple, single-user scenarios
- No need for token expiration
- Direct access with minimal setup
Use OAuth When:
- Building third-party integrations
- Multi-user applications
- Need token expiration and refresh
- Compliance requires OAuth
- Want authorization scopes (future)
OAuth 2.1 Features
Our implementation follows RFC 9449 and includes:
- Authorization Code Flow with PKCE (Proof Key for Code Exchange)
- Refresh tokens for long-lived access
- Secure token storage with encryption
- Client credential rotation
- Session management
Creating an OAuth Client
Step 1: Navigate to OAuth Tab
- Go to http://localhost:3000/dashboard
- Click the OAuth Clients tab
- Click Create OAuth Client
Step 2: Fill Client Details
Required information:
- Client Name: Descriptive name (e.g., "My Mobile App")
- n8n Connection: Which n8n instance to connect
- Redirect URIs: Allowed callback URLs (one per line)
Example redirect URIs:
http://localhost:8080/callback
https://app.example.com/oauth/callback
Step 3: Save Credentials
After creation, you'll receive:
{
"clientId": "mcp_client_abc123",
"clientSecret": "mcp_secret_xyz789...",
"redirectUris": ["http://localhost:8080/callback"]
}
Important: Save the clientSecret immediately. You won't see it again!
OAuth Client Format
Client ID
mcp_client_{random_characters}
- Prefix:
mcp_client_ - Length: ~40 characters
- Public identifier (can be shared)
Client Secret
mcp_secret_{64_hex_characters}
- Prefix:
mcp_secret_ - Length: ~74 characters total
- Must be kept secret (like a password)
Authorization Flow
Step 1: Authorization Request
Direct users to the authorization endpoint:
GET /oauth/authorize?
client_id=mcp_client_abc123&
redirect_uri=http://localhost:8080/callback&
response_type=code&
code_challenge=ABC123...&
code_challenge_method=S256&
state=random_state_value
Parameters:
client_id: Your OAuth client IDredirect_uri: Must match registered URIresponse_type: Alwayscodecode_challenge: PKCE challenge (base64url-encoded)code_challenge_method: AlwaysS256(SHA-256)state: Random value for CSRF protection
Step 2: User Authorization
The user sees a consent screen:
[App Name] wants to access your n8n workflows
This will allow the app to:
- Execute workflows
- Read workflow results
- Access your n8n connection
[Authorize] [Deny]
Step 3: Authorization Code
After approval, user is redirected:
http://localhost:8080/callback?
code=AUTH_CODE_HERE&
state=random_state_value
Step 4: Token Exchange
Exchange the code for tokens:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=http://localhost:8080/callback&
client_id=mcp_client_abc123&
client_secret=mcp_secret_xyz789&
code_verifier=ORIGINAL_CODE_VERIFIER
Response:
{
"access_token": "mcp_access_abc123...",
"refresh_token": "mcp_refresh_xyz789...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "n8n:execute"
}
Step 5: Use Access Token
Make MCP requests with the access token:
POST /mcp
Authorization: Bearer mcp_access_abc123...
Content-Type: application/json
{
"method": "tools/call",
"params": {
"name": "ping_n8n"
}
}
Step 6: Refresh Token
When access token expires, use refresh token:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=mcp_refresh_xyz789...&
client_id=mcp_client_abc123&
client_secret=mcp_secret_xyz789
Response:
{
"access_token": "mcp_access_new123...",
"refresh_token": "mcp_refresh_new789...",
"token_type": "Bearer",
"expires_in": 3600
}
PKCE (Proof Key for Code Exchange)
Why PKCE?
PKCE prevents authorization code interception attacks. It's required for all OAuth clients.
How It Works
-
Generate Code Verifier
const codeVerifier = generateRandomString(128) // Example: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" -
Create Code Challenge
const codeChallenge = base64url(sha256(codeVerifier)) // Example: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" -
Send Challenge in Authorization
/oauth/authorize?code_challenge=E9Melhoa...&code_challenge_method=S256 -
Send Verifier in Token Exchange
POST /oauth/token ...&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk -
Server Validates
if (sha256(code_verifier) === stored_code_challenge) { // Valid - issue tokens }
Managing OAuth Clients
View All Clients
In the OAuth Clients tab, see:
- Client name
- Client ID
- n8n connection
- Redirect URIs
- Created date
- Active sessions count
Edit OAuth Client
You can update:
- Client name
- Redirect URIs (add/remove)
- n8n connection
Note: You cannot change the client ID or regenerate the secret without deleting and recreating.
Rotate Client Secret
To generate a new client secret:
- Create a new OAuth client
- Update your application with new credentials
- Test thoroughly
- Delete the old client
Delete OAuth Client
Deleting a client:
- Revokes all active sessions
- Invalidates all tokens
- Prevents new authorizations
- Removes from database
Warning: All apps using this client will lose access.
OAuth Sessions
What are Sessions?
Each time a user authorizes your OAuth client, a session is created. Sessions store:
- User ID
- OAuth client ID
- Access token (encrypted)
- Refresh token (encrypted)
- Expiration times
- Session metadata
View Active Sessions
Navigate to OAuth Sessions tab to see:
- Which clients have active sessions
- When sessions were created
- When they expire
- Last activity
Revoke Sessions
To revoke a session:
- Go to OAuth Sessions tab
- Find the session
- Click Revoke
- Session and tokens are immediately invalidated
Security Best Practices
Client Secret Security
- Never commit to version control
- Use environment variables in production
- Rotate regularly (every 90 days)
- Store in secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
Redirect URI Validation
- Exact match required (no wildcards)
- Use HTTPS in production (not HTTP)
- Localhost allowed for development only
- No open redirects (validate in your app too)
State Parameter
Always use the state parameter:
const state = generateRandomString(32)
sessionStorage.setItem('oauth_state', state)
// In authorization URL
const authUrl = `/oauth/authorize?state=${state}&...`
// After redirect
const returnedState = new URLSearchParams(location.search).get('state')
if (returnedState !== sessionStorage.getItem('oauth_state')) {
throw new Error('CSRF attack detected')
}
Token Storage
- Access tokens: Store in memory only (not localStorage)
- Refresh tokens: Encrypted storage or httpOnly cookies
- Never expose tokens in URLs or logs
- Clear on logout
Token Lifecycle
Access Token
- Lifetime: 1 hour (3600 seconds)
- Purpose: Authenticate MCP requests
- Storage: Memory or secure cookie
- Refresh: Use refresh token when expired
Refresh Token
- Lifetime: 30 days
- Purpose: Obtain new access tokens
- Storage: Encrypted database + client storage
- Rotation: New refresh token issued with each refresh
Session
- Lifetime: Until refresh token expires (30 days)
- Extension: Each token refresh extends session
- Revocation: Manual or on logout
Example Implementation
Node.js/Express Example
import { randomBytes, createHash } from 'crypto'
// Generate PKCE pair
function generatePKCE() {
const verifier = randomBytes(32).toString('base64url')
const challenge = createHash('sha256').update(verifier).digest('base64url')
return { verifier, challenge }
}
// Step 1: Initiate OAuth flow
app.get('/login', (req, res) => {
const { verifier, challenge } = generatePKCE()
const state = randomBytes(32).toString('hex')
req.session.pkce_verifier = verifier
req.session.oauth_state = state
const authUrl = new URL('http://localhost:3000/oauth/authorize')
authUrl.searchParams.set('client_id', process.env.OAUTH_CLIENT_ID)
authUrl.searchParams.set('redirect_uri', 'http://localhost:8080/callback')
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('code_challenge', challenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
authUrl.searchParams.set('state', state)
res.redirect(authUrl.toString())
})
// Step 2: Handle callback
app.get('/callback', async (req, res) => {
const { code, state } = req.query
// Verify state
if (state !== req.session.oauth_state) {
return res.status(400).send('Invalid state')
}
// Exchange code for tokens
const tokenResponse = await fetch('http://localhost:3000/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'http://localhost:8080/callback',
client_id: process.env.OAUTH_CLIENT_ID,
client_secret: process.env.OAUTH_CLIENT_SECRET,
code_verifier: req.session.pkce_verifier,
}),
})
const tokens = await tokenResponse.json()
// Store tokens securely
req.session.access_token = tokens.access_token
req.session.refresh_token = tokens.refresh_token
res.redirect('/dashboard')
})
// Step 3: Use access token
app.get('/api/n8n/workflows', async (req, res) => {
const response = await fetch('http://localhost:3001/mcp', {
method: 'POST',
headers: {
Authorization: `Bearer ${req.session.access_token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
method: 'tools/call',
params: { name: 'list_workflows' },
}),
})
res.json(await response.json())
})
Troubleshooting
"Invalid redirect_uri" Error
Ensure:
- URI matches exactly (including trailing slash)
- URI is registered in OAuth client settings
- Using correct protocol (http/https)
"Invalid PKCE challenge" Error
Verify:
- Code verifier and challenge are properly generated
- Using SHA-256 for challenge method
- Code verifier is sent in token exchange
Tokens Expired
If tokens are expired:
- Use refresh token to get new access token
- If refresh token expired, re-authorize user
- Check token expiration before use