n8n MCP Bridge

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

  1. Go to http://localhost:3000/dashboard
  2. Click the OAuth Clients tab
  3. 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 ID
  • redirect_uri: Must match registered URI
  • response_type: Always code
  • code_challenge: PKCE challenge (base64url-encoded)
  • code_challenge_method: Always S256 (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

  1. Generate Code Verifier

    const codeVerifier = generateRandomString(128)
    // Example: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  2. Create Code Challenge

    const codeChallenge = base64url(sha256(codeVerifier))
    // Example: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
  3. Send Challenge in Authorization

    /oauth/authorize?code_challenge=E9Melhoa...&code_challenge_method=S256
  4. Send Verifier in Token Exchange

    POST /oauth/token
    ...&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  5. 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:

  1. Create a new OAuth client
  2. Update your application with new credentials
  3. Test thoroughly
  4. Delete the old client

Delete OAuth Client

Deleting a client:

  1. Revokes all active sessions
  2. Invalidates all tokens
  3. Prevents new authorizations
  4. 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:

  1. Go to OAuth Sessions tab
  2. Find the session
  3. Click Revoke
  4. 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

Next Steps

On this page