-
Notifications
You must be signed in to change notification settings - Fork 38
Chore: Implement token exchange #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ankita10119
wants to merge
14
commits into
main
Choose a base branch
from
SDK-6833
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
f344c37
Chore: Implement token exchange
ankita10119 58bc4b4
Potential fix for code scanning alert no. 44: Missing rate limiting
ankita10119 633ce48
Potential fix for code scanning alert no. 51: Log injection
ankita10119 f7eaff3
Potential fix for code scanning alert no. 46: Missing rate limiting
ankita10119 0b436bb
Potential fix for code scanning alert no. 48: Missing rate limiting
ankita10119 07d84b8
Potential fix for code scanning alert no. 54: Log injection
ankita10119 486356d
Potential fix for code scanning alert no. 55: Log injection
ankita10119 07e718d
Add express-rate-limit for token exchange endpoints and update TypeSc…
ankita10119 26cf98b
Update TypeScript version to use a tilde for more controlled versioning
ankita10119 3012a81
Update TypeScript version to use a tilde for more controlled versioning
ankita10119 1380a99
Fix error handling in token exchange response parsing and update test…
ankita10119 fcf0734
Enhance rate limiting for token exchange operations: reduce limit to …
ankita10119 a9437f0
Refactor package structure: replace 'oauth2-bearer' with '@internal/o…
ankita10119 4c9e180
Refactor imports: replace 'oauth2-bearer' with '@internal/oauth2-bear…
ankita10119 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,213 @@ | ||
| /** | ||
| * Comprehensive Token Exchange Examples | ||
| * | ||
| * This file demonstrates both approaches for token exchange: | ||
| * 1. Direct exchangeToken() function - for manual token exchange | ||
| * 2. req.auth.exchange() method - for Express middleware integration | ||
| */ | ||
|
|
||
| import express from 'express'; | ||
| import { auth, exchangeToken } from 'express-oauth2-jwt-bearer'; | ||
| import rateLimit from 'express-rate-limit'; | ||
|
|
||
| const app = express(); | ||
| app.use(express.json()); | ||
|
|
||
| // Rate limiter: max 100 requests per 15 minutes per IP for expensive routes | ||
| const limiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes) | ||
| standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers | ||
| legacyHeaders: false, // Disable the `X-RateLimit-*` headers | ||
| }); | ||
|
|
||
| // Setup the auth middleware | ||
| const authenticateToken = auth({ | ||
| issuerBaseURL: 'https://dev-ankita-t.us.auth0.com', | ||
| audience: 'https://api.example.com' | ||
| }); | ||
|
|
||
| // Health check endpoint (no authentication required) | ||
| app.get('/health', (req, res) => { | ||
| res.json({ status: 'OK', message: 'Token exchange service is running' }); | ||
| }); | ||
|
|
||
| // Example 1: Exchange using request auth context (recommended for Express middleware) | ||
| // This approach automatically uses the token from the current authenticated request | ||
| app.post('/exchange-via-context', authenticateToken, limiter, async (req, res) => { | ||
| try { | ||
| if (!req.auth) { | ||
| return res.status(401).json({ error: 'Authentication required' }); | ||
| } | ||
|
|
||
| const exchangedToken = await req.auth.exchange({ | ||
| tokenEndpoint: 'https://dev-ankita-t.us.auth0.com/oauth/token', | ||
| clientId: 'your-client-id', | ||
| clientSecret: 'your-client-secret', | ||
| targetAudience: 'https://api.example.com', | ||
| scope: 'read:data write:data', | ||
| }); | ||
|
|
||
| res.json({ | ||
| message: 'Token exchanged successfully via auth context', | ||
| exchangedToken, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Token exchange failed:', error); | ||
|
||
| res.status(500).json({ | ||
| error: 'Token exchange failed', | ||
| details: error instanceof Error ? error.message : 'Unknown error', | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| // Example 2: Direct token exchange using the exchangeToken function | ||
| // This approach allows you to exchange any token manually | ||
| app.post('/exchange-direct', authenticateToken, limiter, async (req, res) => { | ||
|
||
| try { | ||
| if (!req.auth) { | ||
| return res.status(401).json({ error: 'Authentication required' }); | ||
| } | ||
|
|
||
| // Get the current token from the authenticated request | ||
| const currentToken = req.auth.token; | ||
|
|
||
| // Use the direct exchangeToken function - useful for custom scenarios | ||
| const exchangedToken = await exchangeToken(currentToken, { | ||
| tokenEndpoint: 'https://dev-ankita-t.us.auth0.com/oauth/token', | ||
| clientId: 'your-client-id', | ||
| clientSecret: 'your-client-secret', | ||
| targetAudience: 'https://api.example.com', | ||
| scope: 'read:data write:data', | ||
| }); | ||
|
|
||
| res.json({ | ||
| message: 'Token exchanged successfully via direct function', | ||
| exchangedToken, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Token exchange failed:', error); | ||
|
||
| res.status(500).json({ | ||
| error: 'Token exchange failed', | ||
| details: error instanceof Error ? error.message : 'Unknown error', | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| // Example 3: Exchange a token from request body (demonstrating flexibility of direct function) | ||
| // This shows how you can exchange any token, not just the current request's token | ||
| app.post('/exchange-any-token', async (req, res) => { | ||
| try { | ||
| const { token } = req.body; | ||
|
|
||
| if (!token) { | ||
| return res.status(400).json({ error: 'Token is required in request body' }); | ||
| } | ||
|
|
||
| // Use exchangeToken to exchange any provided token | ||
| const exchangedToken = await exchangeToken(token, { | ||
| tokenEndpoint: 'https://dev-ankita-t.us.auth0.com/oauth/token', | ||
| clientId: 'your-client-id', | ||
| clientSecret: 'your-client-secret', | ||
| targetAudience: 'https://api.example.com', | ||
| scope: 'read:data write:data', | ||
| }); | ||
|
|
||
| res.json({ | ||
| message: 'External token exchanged successfully', | ||
| exchangedToken, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Token exchange failed:', error); | ||
|
||
| res.status(500).json({ | ||
| error: 'Token exchange failed', | ||
| details: error instanceof Error ? error.message : 'Unknown error', | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| // Demonstration endpoint showing the difference | ||
| app.get('/compare-approaches', authenticateToken, async (req, res) => { | ||
|
||
| if (!req.auth) { | ||
| return res.status(401).json({ error: 'Authentication required' }); | ||
| } | ||
|
|
||
| res.json({ | ||
| approaches: { | ||
| authContext: { | ||
| description: 'Uses req.auth.exchange() - automatically uses current request token', | ||
| pros: [ | ||
| 'Simpler API - no need to extract token manually', | ||
| 'Follows express-openid-connect pattern', | ||
| 'Integrated with Express middleware', | ||
| 'Less error-prone' | ||
| ], | ||
| useCase: 'Best for typical Express apps where you want to exchange the current user\'s token' | ||
| }, | ||
| directFunction: { | ||
| description: 'Uses exchangeToken(token, options) - manual token exchange', | ||
| pros: [ | ||
| 'More flexible - can exchange any token', | ||
| 'Can be used outside Express middleware context', | ||
| 'Useful for batch processing or admin operations', | ||
| 'Direct control over which token to exchange' | ||
| ], | ||
| useCase: 'Best for custom scenarios, background jobs, or when you need to exchange tokens from different sources' | ||
| } | ||
| }, | ||
| examples: { | ||
| authContext: 'POST /exchange-via-context (with Authorization header)', | ||
| directFunction: 'POST /exchange-direct (with Authorization header) or POST /exchange-any-token (with token in body)' | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| // Information endpoint for getting tokens (for testing) | ||
| app.get('/get-token', (req, res) => { | ||
| res.json({ | ||
| message: 'To get tokens for testing, use one of these approaches:', | ||
| approaches: { | ||
| spa: { | ||
| description: 'For Single Page Applications', | ||
| flow: 'Authorization Code with PKCE', | ||
| steps: [ | ||
| '1. Configure your Auth0 application as "Single Page Application"', | ||
| '2. Use Auth0 SDK or direct OAuth flow with PKCE', | ||
| '3. Redirect user to authorize endpoint', | ||
| '4. Exchange authorization code for tokens' | ||
| ], | ||
| authUrl: 'https://dev-ankita-t.us.auth0.com/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_CALLBACK&scope=openid%20profile&audience=https://api.example.com&code_challenge=YOUR_CODE_CHALLENGE&code_challenge_method=S256' | ||
| }, | ||
| m2m: { | ||
| description: 'For Machine-to-Machine Applications', | ||
| flow: 'Client Credentials', | ||
| note: 'Enable "Client Credentials" grant in Auth0 Dashboard > Applications > Your App > Settings > Advanced Settings > Grant Types', | ||
| curl: `curl -X POST https://dev-ankita-t.us.auth0.com/oauth/token \\ | ||
| -H "Content-Type: application/json" \\ | ||
| -d '{ | ||
| "client_id": "YOUR_CLIENT_ID", | ||
| "client_secret": "YOUR_CLIENT_SECRET", | ||
| "audience": "https://api.example.com", | ||
| "grant_type": "client_credentials" | ||
| }'` | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| const port = process.env.PORT || 3006; | ||
| app.listen(port, () => { | ||
| console.log(`Server running on port ${port}`); | ||
| console.log(''); | ||
| console.log('Available endpoints:'); | ||
| console.log(' GET /health - Health check (no auth)'); | ||
| console.log(' GET /get-token - Token acquisition info (no auth)'); | ||
| console.log(' GET /compare-approaches - Compare token exchange methods (requires auth)'); | ||
| console.log(' POST /exchange-via-context - Exchange via req.auth.exchange() (requires auth)'); | ||
| console.log(' POST /exchange-direct - Exchange via exchangeToken() function (requires auth)'); | ||
| console.log(' POST /exchange-any-token - Exchange any token (no auth, token in body)'); | ||
| console.log(''); | ||
| console.log('Key Differences:'); | ||
| console.log(' • req.auth.exchange() - Automatic, uses current request token'); | ||
| console.log(' • exchangeToken() - Manual, can exchange any token'); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.