Code Samples for Your OAuth App

Copy and paste these code samples into your application to implement OAuth authentication with LaunchDarkly.

Node.js

Complete OAuth flow implementation using Express.js

import express from 'express';
import axios from 'axios';

const app = express();
app.use(express.json());

const CLIENT_ID = 'your-client-id';
const CLIENT_SECRET = 'your-client-secret';
const REDIRECT_URI = 'https://ld-oauth-framework.vercel.app/api/callback/your-session-id';

// Root route - landing page
app.get('/', (req, res) => {
  res.send(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>LaunchDarkly OAuth Client</title>
      <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
        .button { display: inline-block; padding: 10px 20px; background: #0066cc; color: white; text-decoration: none; border-radius: 5px; }
        .button:hover { background: #0052a3; }
      </style>
    </head>
    <body>
      <h1>LaunchDarkly OAuth Client</h1>
      <p>This is a simple OAuth client for LaunchDarkly. Click the button below to start the OAuth flow.</p>
      <div style="background: #f0f8ff; border: 1px solid #0066cc; padding: 15px; border-radius: 5px; margin: 20px 0;">
        <strong>📋 Important:</strong> When configuring your OAuth client, use this callback URL:
        <br><code style="background: #fff; padding: 5px; border-radius: 3px;">http://localhost:3000/callback</code>
        <br><small>✅ This server works with both direct OAuth and framework proxy - no code changes needed!</small>
        <br><small>🔧 Framework proxy automatically uses correct redirect URI for token exchange</small>
      </div>
      <a href="/auth" class="button">Start OAuth Flow</a>
      <p><small>Available routes:</small></p>
      <ul>
        <li><code>/</code> - This page</li>
        <li><code>/auth</code> - Start OAuth authorization</li>
        <li><code>/callback</code> - Handle OAuth callback</li>
      </ul>
    </body>
    </html>
  `);
});

// Step 1: Redirect user to LaunchDarkly authorization
app.get('/auth', (req, res) => {
  const authUrl = `https://app.launchdarkly.com/trust/oauth/authorize?${new URLSearchParams({
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    response_type: 'code',
    scope: 'reader'
  })}`;
  
  res.redirect(authUrl);
});

// Step 2: Handle the callback
app.get('/callback', async (req, res) => {
  const { code, sessionId } = req.query;
  
  if (!code) {
    return res.status(400).json({ error: 'Authorization code not provided' });
  }

  try {
    // Exchange code for access token (works with both direct OAuth and framework proxy)
    // When using framework proxy, use the framework's redirect URI for token exchange
    // This ensures the redirect_uri matches what was used in the authorization request
    const tokenExchangeRedirectUri = sessionId 
      ? `https://ld-oauth-framework.vercel.app/api/callback/${sessionId}`
      : REDIRECT_URI;
    
    console.log('Token exchange redirect URI:', tokenExchangeRedirectUri);
    
    // OAuth token exchange requires form-encoded data, not JSON
    const formData = new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code: code,
      redirect_uri: tokenExchangeRedirectUri
    });
    
    const tokenResponse = await axios.post('https://app.launchdarkly.com/trust/oauth/token', formData, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    const { access_token } = tokenResponse.data;
    
    // Log if this came through the framework
    if (sessionId) {
      console.log('OAuth flow completed via framework proxy for session:', sessionId);
    } else {
      console.log('OAuth flow completed directly');
    }
    
    // Example 1: Get caller identity
    const userResponse = await axios.get('https://app.launchdarkly.com/api/v2/caller-identity', {
      headers: {
        'Authorization': `Bearer ${access_token}`
      }
    });

    // Example 2: List all projects
    const projectsResponse = await axios.get('https://app.launchdarkly.com/api/v2/projects', {
      headers: {
        'Authorization': `Bearer ${access_token}`
      }
    });

    // Example 3: Get flags from a specific project (using first project as example)
    let flags = [];
    if (projectsResponse.data.items && projectsResponse.data.items.length > 0) {
      const firstProject = projectsResponse.data.items[0];
      const flagsResponse = await axios.get(`https://app.launchdarkly.com/api/v2/flags/${firstProject.key}`, {
        headers: {
          'Authorization': `Bearer ${access_token}`
        }
      });
      flags = flagsResponse.data.items || [];
    }

    // Return a nice HTML success page instead of JSON
    const html = `
<!DOCTYPE html>
<html>
<head>
  <title>OAuth Success - LaunchDarkly</title>
  <style>
    body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
    .success { background: #d4edda; border: 1px solid #c3e6cb; padding: 20px; border-radius: 8px; margin: 20px 0; }
    .error { background: #f8d7da; border: 1px solid #f5c6cb; padding: 20px; border-radius: 8px; margin: 20px 0; }
    .info { background: #d1ecf1; border: 1px solid #bee5eb; padding: 20px; border-radius: 8px; margin: 20px 0; }
    .button { display: inline-block; padding: 12px 24px; background: #0066cc; color: white; text-decoration: none; border-radius: 6px; margin: 10px 5px; border: none; cursor: pointer; }
    .button:hover { background: #0052a3; }
    .button.secondary { background: #6c757d; }
    .button.secondary:hover { background: #5a6268; }
    .button.success { background: #28a745; }
    .button.success:hover { background: #218838; }
    .data-section { background: #f8f9fa; border: 1px solid #dee2e6; padding: 15px; border-radius: 6px; margin: 15px 0; }
    .loading { color: #6c757d; font-style: italic; }
    pre { background: #f8f9fa; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
  </style>
</head>
<body>
  <div class="success">
    <h1>✅ OAuth Success!</h1>
    <p>Your OAuth authentication with LaunchDarkly was successful!</p>
    <p><strong>Access Token:</strong> <code>${access_token.substring(0, 20)}...</code></p>
  </div>

  <div class="info">
    <h2>🔍 API Verification</h2>
    <p>Click the buttons below to verify your access token by calling LaunchDarkly APIs:</p>
    
    <button class="button" onclick="loadUserInfo()">👤 Get User Info</button>
    <button class="button" onclick="loadProjects()">📁 List Projects</button>
    <button class="button" onclick="loadFlags()">🚩 List Flags</button>
    <button class="button secondary" onclick="window.location.href='/'">🏠 Back to Home</button>
  </div>

  <div id="results"></div>

  <script>
    const accessToken = '${access_token}';
    
    async function loadUserInfo() {
      const button = event.target;
      const originalText = button.textContent;
      button.textContent = 'Loading...';
      button.disabled = true;
      
      try {
        const response = await fetch('https://app.launchdarkly.com/api/v2/caller-identity', {
          headers: { 'Authorization': 'Bearer ' + accessToken }
        });
        
        if (!response.ok) throw new Error('HTTP ' + response.status);
        
        const data = await response.json();
        displayResult('User Information', data);
      } catch (error) {
        displayError('Failed to load user info: ' + error.message);
      } finally {
        button.textContent = originalText;
        button.disabled = false;
      }
    }
    
    async function loadProjects() {
      const button = event.target;
      const originalText = button.textContent;
      button.textContent = 'Loading...';
      button.disabled = true;
      
      try {
        const response = await fetch('https://app.launchdarkly.com/api/v2/projects', {
          headers: { 'Authorization': 'Bearer ' + accessToken }
        });
        
        if (!response.ok) throw new Error('HTTP ' + response.status);
        
        const data = await response.json();
        displayResult('Projects', data);
      } catch (error) {
        displayError('Failed to load projects: ' + error.message);
      } finally {
        button.textContent = originalText;
        button.disabled = false;
      }
    }
    
    async function loadFlags() {
      const button = event.target;
      const originalText = button.textContent;
      button.textContent = 'Loading...';
      button.disabled = true;
      
      try {
        // First get projects to find a project key
        const projectsResponse = await fetch('https://app.launchdarkly.com/api/v2/projects', {
          headers: { 'Authorization': 'Bearer ' + accessToken }
        });
        
        if (!projectsResponse.ok) throw new Error('HTTP ' + projectsResponse.status);
        
        const projects = await projectsResponse.json();
        if (!projects.items || projects.items.length === 0) {
          displayError('No projects found to load flags from');
          return;
        }
        
        // Use the first project to get flags
        const projectKey = projects.items[0].key;
        const flagsResponse = await fetch('https://app.launchdarkly.com/api/v2/flags/' + projectKey, {
          headers: { 'Authorization': 'Bearer ' + accessToken }
        });
        
        if (!flagsResponse.ok) throw new Error('HTTP ' + flagsResponse.status);
        
        const data = await flagsResponse.json();
        displayResult('Flags from ' + projectKey, data);
      } catch (error) {
        displayError('Failed to load flags: ' + error.message);
      } finally {
        button.textContent = originalText;
        button.disabled = false;
      }
    }
    
    function displayResult(title, data) {
      const resultsDiv = document.getElementById('results');
      resultsDiv.innerHTML = '<div class="data-section"><h3>✅ ' + title + '</h3><pre>' + JSON.stringify(data, null, 2) + '</pre></div>';
    }
    
    function displayError(message) {
      const resultsDiv = document.getElementById('results');
      resultsDiv.innerHTML = '<div class="error"><h3>❌ Error</h3><p>' + message + '</p></div>';
    }
  </script>
</body>
</html>`;
    
    res.send(html);
  } catch (error) {
    console.error('Token exchange error:', error);
    res.status(500).json({ error: 'Failed to exchange code for token' });
  }
});

app.listen(3000, () => {
  console.log('🚀 Server running on http://localhost:3000');
  console.log('📋 Callback URL: http://localhost:3000/callback');
  console.log('✅ Works with both direct OAuth and framework proxy!');
  console.log('🔍 Framework proxy forwards authorization code transparently');
}).on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.error('❌ Error: Port 3000 is already in use!');
    console.error('   Please either:');
    console.error('   1. Stop the process using port 3000');
    console.error('   2. Use a different port by changing the port number in the code');
    console.error('   3. Run: lsof -ti:3000 | xargs kill -9 (to force kill processes on port 3000)');
    console.error('');
    console.error('   You can check what\'s using the port with: lsof -i :3000');
  } else {
    console.error('❌ Server error:', err);
  }
  process.exit(1);
});

// Example: Using the access token for API calls
// You can add these functions to your application to interact with LaunchDarkly API

async function getCallerIdentity(accessToken) {
  try {
    const response = await axios.get('https://app.launchdarkly.com/api/v2/caller-identity', {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    });
    return response.data;
  } catch (error) {
    console.error('Failed to get caller identity:', error.message);
    throw error;
  }
}

async function listProjects(accessToken) {
  try {
    const response = await axios.get('https://app.launchdarkly.com/api/v2/projects', {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    });
    return response.data.items;
  } catch (error) {
    console.error('Failed to list projects:', error.message);
    throw error;
  }
}

async function getFlags(accessToken, projectKey) {
  try {
    const response = await axios.get(`https://app.launchdarkly.com/api/v2/flags/${projectKey}`, {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    });
    return response.data.items;
  } catch (error) {
    console.error(`Failed to get flags for project ${projectKey}:`, error.message);
    throw error;
  }
}

// Example usage:
// const identity = await getCallerIdentity(access_token);
// const projects = await listProjects(access_token);
// const flags = await getFlags(access_token, 'your-project-key');

Additional Resources