Copy and paste these code samples into your application to implement OAuth authentication with LaunchDarkly.
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');