This project demonstrates how to create a Webex integration (OAuth client) to obtain a scoped API access token for the authenticating Webex user. This code accompanies the tutorial Run a Webex OAuth Integration Locally on the Webex Developer Portal. See the tutorial for details about creating an integration and configuring this project.
- 🔒 OAuth 2.0 Authorization - Complete authorization code flow implementation
- 🎯 Token Management - Secure access and refresh token handling with session storage
- 👤 User Authentication - Obtain scoped access to user's Webex account
- 🏠 Room Listing - Display user's Webex spaces with appropriate scopes
- 🛡️ State Validation - CSRF protection with state parameter verification
⚠️ Error Handling - Comprehensive error handling for all OAuth scenarios- 🎨 Clean UI - Simple, responsive web interface using Water.css
- 🔁 Session Management - Express sessions with secure logout functionality
- Node.js 14.x or later
- npm or yarn package manager
- Webex Integration configured on Developer Portal
-
Clone the repository:
git clone <repository-url> cd webex-oauth-integration
-
Install dependencies:
npm install # or yarn install
-
Configure environment variables:
Copy the
.env
file template and configure your integration settings:# Create .env file with your integration details AUTH_INIT_URL=https://webexapis.com/v1/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost:8080/oauth&scope=spark:all CLIENT_SECRET=your_client_secret_here STATE=your_random_state_string PORT=8080
-
Start the server:
npm start # or yarn start
-
Access the application:
- Open your browser to
http://localhost:8080
- Click "Start OAuth flow" to begin authentication
- Complete the Webex authorization process
- Open your browser to
-
Initiate OAuth:
- User clicks "Start OAuth flow" on the home page
- Application redirects to Webex authorization server
- User grants permissions to your integration
-
Authorization Callback:
- Webex redirects back to your application with authorization code
- Application exchanges code for access token
- User is authenticated and session is established
-
API Access:
- Use the access token to make Webex API calls
- Display user information and available spaces
- Maintain session for continued access
Route | Method | Description |
---|---|---|
/ |
GET | Home page with OAuth initiation link |
/index.html |
GET | Main application page (redirects based on auth status) |
/oauth |
GET | OAuth callback endpoint for authorization code |
/logout |
GET | Logout user and destroy session |
/listrooms |
GET | Display user's Webex spaces (requires auth) |
webex-oauth-integration/
├── server.js # Main Express application and OAuth logic
├── package.json # Dependencies and scripts
├── .env # Environment variables (not in repo)
├── .gitignore # Git ignore patterns
├── LICENSE # Cisco Sample Code License
├── README.md # This documentation
└── www/ # EJS templates and static files
├── index.ejs # Home page template
├── display-name.ejs # Authenticated user page
├── list-rooms.ejs # Rooms listing template
└── error.ejs # Error page template
Component | Description | File Location |
---|---|---|
OAuth Handler | Authorization code flow implementation | server.js lines 93-199 |
Token Exchange | Authorization code to access token exchange | server.js lines 170-199 |
Session Management | Express session configuration and handling | server.js lines 21-27 |
API Integration | Webex API calls with access token | server.js lines 246-264 |
Error Handling | OAuth error scenarios and user feedback | server.js lines 99-142 |
// Build authorization URL from environment variables
const initiateURL = new URL(process.env.AUTH_INIT_URL);
var authUrlParams = initiateURL.searchParams;
const clientId = authUrlParams.get("client_id");
const redirectURI = authUrlParams.get("redirect_uri");
const scopes = authUrlParams.get("scope");
// Add state parameter for CSRF protection
const state = process.env.STATE || crypto.randomBytes(64).toString("hex");
authUrlParams.set("state", state);
// Exchange authorization code for access token
const params = new URLSearchParams([
["grant_type", "authorization_code"],
["client_id", clientId],
["client_secret", clientSecret],
["code", req.query.code],
["redirect_uri", redirectURI],
]);
const response = await fetch("https://webexapis.com/v1/access_token", {
method: "POST",
headers: { "Content-type": "application/x-www-form-urlencoded" },
body: params,
});
const data = await response.json();
// Store token in session for API calls
sess.token = data.access_token;
// Make authenticated API call to get user info
async function getUserInfo(access_token, res) {
const response = await fetch("https://webexapis.com/v1/people/me", {
method: "GET",
headers: { authorization: "Bearer " + access_token },
});
const data = await response.json();
// Render template with user data
const compiled = ejs.compile(template)({ displayName: data.displayName });
res.send(compiled);
}
The application uses EJS templates for dynamic content rendering:
<h1>Webex Integration example: Home</h1>
<a href="<%= link %>">Start OAuth flow</a>
<h1>Webex Integration example: Authenticated</h1>
<a href="/logout">Logout</a>
<p>So happy to meet, <%= displayName %>!</p>
<h1>Webex Integration example: Spaces List</h1>
<ul>
<% rooms.forEach(function(room) { %>
<li><%= room.title %></li>
<% }); %>
</ul>
The application uses Water.css for clean, minimal styling without custom CSS:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
# Required OAuth settings
AUTH_INIT_URL=https://webexapis.com/v1/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost:8080/oauth&scope=spark:all
CLIENT_SECRET=your_integration_client_secret
STATE=random_string_for_csrf_protection
PORT=8080
The integration can request various scopes depending on your needs:
Scope | Description | Usage |
---|---|---|
spark:all |
Full access to user's Webex account | Complete integration access |
spark:people_read |
Read user profile information | User identification |
spark:rooms_read |
Read user's spaces/rooms | Room listing functionality |
spark:messages_read |
Read messages in spaces | Message access |
spark:messages_write |
Send messages to spaces | Message posting |
-
Create Integration:
- Visit Webex Developer Portal
- Click "Create a New App" → "Integration"
- Configure redirect URI:
http://localhost:8080/oauth
-
Copy Credentials:
- Client ID and Client Secret from integration page
- Construct AUTH_INIT_URL with your client ID and desired scopes
// Generate cryptographically secure state parameter
const state = process.env.STATE || crypto.randomBytes(64).toString("hex");
// Validate state in callback to prevent CSRF attacks
if (state != req.query.state) {
// Reject request - possible CSRF attack
return res.send(errorTemplate);
}
// Secure session configuration
app.use(session({
secret: crypto.randomBytes(64).toString("hex"), // Random session secret
resave: false, // Don't save unchanged sessions
saveUninitialized: false, // Don't create sessions until needed
}));
// Comprehensive OAuth error handling
if (req.query.error) {
switch(req.query.error) {
case "access_denied":
// User declined authorization
case "invalid_scope":
// Requested invalid scope
case "server_error":
// Webex server error
default:
// Handle unexpected errors
}
}
-
OAuth Flow Testing:
- Start application and initiate OAuth flow
- Test with valid Webex account
- Verify user information display
- Test logout functionality
-
Error Scenarios:
- Decline authorization and verify error handling
- Test with invalid client credentials
- Verify state parameter validation
-
API Integration:
- Test user info retrieval
- Verify rooms listing (if scope included)
- Test session persistence
// Test OAuth callback endpoint
app.get('/oauth', async function (req, res) {
// Verify required parameters
if (!req.query.code || !req.query.state) {
return res.send(errorTemplate);
}
// Validate state parameter
if (state != req.query.state) {
return res.send(errorTemplate);
}
// Exchange code for token
const tokenResponse = await exchangeCodeForToken(req.query.code);
// Continue with user authentication...
});
Issue | Solution |
---|---|
Redirect URI Mismatch | Ensure redirect URI in .env matches integration configuration |
Invalid Client | Verify CLIENT_ID and CLIENT_SECRET are correct |
Invalid Scope | Check that requested scopes are configured in your integration |
State Mismatch | Ensure STATE parameter is consistent across requests |
Session Issues | Clear browser cookies and restart application |
Enable debug logging to troubleshoot OAuth issues:
// Add to server.js for detailed logging
const debug = require("debug")("oauth");
debug("OAuth flow initiated with client:", clientId);
debug("OAuth callback received:", req.query);
Run with debug output:
DEBUG=oauth npm start
// Error handling examples from the application
"User declined data access request" // User clicked "Deny"
"Invalid scope requested" // Scope not configured in integration
"State does not match" // Possible CSRF attack
"Unexpected query parameters" // Missing code or state parameters
This code accompanies the tutorial Run a Webex OAuth Integration Locally on the Webex Developer Portal. See the tutorial for:
- Step-by-step integration creation
- Detailed configuration instructions
- OAuth flow explanation
- Production deployment considerations
We truly appreciate your contribution to the Webex Samples!
- Fork the repository
- Create a feature branch:
git checkout -b feature/oauth-enhancement
- Commit changes:
git commit -am 'Add OAuth feature'
- Push to branch:
git push origin feature/oauth-enhancement
- Submit a Pull Request
- Follow OAuth 2.0 security best practices
- Implement comprehensive error handling
- Test with multiple OAuth scenarios
- Update documentation for new features
- Ensure environment variable security
This project is licensed under the Cisco Sample Code License - see the LICENSE file for details.
For technical support and questions:
- Issues: Submit via GitHub Issues
- OAuth Documentation: Webex OAuth Guide
- Integration Support: Webex Developer Portal
- Community: Webex Developer Community
Made with ❤️ by the Webex Developer Relations Team at Cisco
Note: This integration is designed for learning and development purposes. For production use, implement additional security measures, proper error handling, and consider using a production-grade session store.