@connexa/mcp-oauth-firebase-middleware 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,632 @@
1
+ # MCP OAuth Firebase Middleware
2
+
3
+ OAuth 2.0 middleware for Model Context Protocol (MCP) servers, using Firebase Authentication as the backend. This middleware translates Claude's OAuth requests into Firebase Auth operations, enabling OAuth-compliant MCP servers.
4
+
5
+ ## Features
6
+
7
+ ✅ **OAuth 2.0 Compliant** - Implements authorization code flow with PKCE
8
+ ✅ **Firebase Auth Backend** - Uses Firebase for user authentication and token management
9
+ ✅ **Token Validation** - Middleware for protecting MCP endpoints
10
+ ✅ **Role-Based Access Control** - Built-in support for roles and permissions via Firebase custom claims
11
+ ✅ **Easy Integration** - Drop-in middleware for Express.js applications
12
+ ✅ **Automatic Token Refresh** - Supports refresh token grant type
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
18
+ │ Claude │ OAuth │ This Middleware │ Firebase│ Firebase │
19
+ │ Desktop │────────▶│ (OAuth Layer) │────────▶│ Auth │
20
+ └─────────────┘ └──────────────────┘ └──────────────┘
21
+ ```
22
+
23
+ The middleware implements three core OAuth 2.0 endpoints:
24
+ - `GET /.well-known/oauth-authorization-server` - Server metadata
25
+ - `GET /oauth/authorize` - Authorization endpoint
26
+ - `POST /oauth/token` - Token exchange and refresh
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @connexa/mcp-oauth-firebase-middleware
32
+ ```
33
+
34
+ ## Prerequisites
35
+
36
+ - Node.js 18+
37
+ - Express.js application
38
+ - Firebase project with Authentication enabled
39
+ - Firebase service account credentials
40
+
41
+ ## Quick Start
42
+
43
+ ### 1. Set Up Firebase
44
+
45
+ 1. Create a Firebase project at [console.firebase.google.com](https://console.firebase.google.com)
46
+ 2. Enable **Email/Password** authentication
47
+ 3. Create a test user in Firebase Authentication
48
+ 4. Download service account key:
49
+ - Go to Project Settings → Service Accounts
50
+ - Click "Generate New Private Key"
51
+ - Save as `serviceAccountKey.json`
52
+
53
+ ### 2. Basic Integration
54
+
55
+ ```javascript
56
+ const express = require('express');
57
+ const { createOAuthMiddleware } = require('@connexa/mcp-oauth-firebase-middleware');
58
+
59
+ const app = express();
60
+
61
+ // Create OAuth middleware
62
+ const oauth = createOAuthMiddleware({
63
+ firebase: {
64
+ apiKey: process.env.FIREBASE_API_KEY,
65
+ authDomain: process.env.FIREBASE_AUTH_DOMAIN,
66
+ projectId: process.env.FIREBASE_PROJECT_ID,
67
+ serviceAccountPath: './serviceAccountKey.json'
68
+ },
69
+ baseUrl: process.env.BASE_URL || 'https://your-app.com',
70
+ clientSecret: process.env.OAUTH_CLIENT_SECRET,
71
+ clientEmail: process.env.OAUTH_CLIENT_EMAIL
72
+ });
73
+
74
+ // Add OAuth endpoints
75
+ app.get('/.well-known/oauth-authorization-server', oauth.metadata);
76
+ app.get('/oauth/authorize', oauth.authorize);
77
+ app.post('/oauth/token', oauth.token);
78
+
79
+ // Protect your MCP endpoints
80
+ app.post('/mcp', oauth.validateToken, (req, res) => {
81
+ // Your MCP handler - req.user contains authenticated user info
82
+ res.json({
83
+ message: 'Protected endpoint',
84
+ user: req.user
85
+ });
86
+ });
87
+
88
+ app.listen(8080);
89
+ ```
90
+
91
+ ### 3. Configure Environment
92
+
93
+ Create a `.env` file:
94
+
95
+ ```env
96
+ FIREBASE_API_KEY=your_api_key
97
+ FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
98
+ FIREBASE_PROJECT_ID=your-project-id
99
+ BASE_URL=https://your-app.com
100
+ OAUTH_CLIENT_SECRET=user_password
101
+ OAUTH_CLIENT_EMAIL=user@example.com
102
+ ```
103
+
104
+
105
+ ## API Reference
106
+
107
+ ### createOAuthMiddleware(config)
108
+
109
+ Creates OAuth middleware instance with handlers for all OAuth endpoints.
110
+
111
+ **Parameters:**
112
+
113
+ ```javascript
114
+ {
115
+ firebase: {
116
+ apiKey: string, // Firebase API key
117
+ authDomain: string, // Firebase auth domain
118
+ projectId: string, // Firebase project ID
119
+ serviceAccountPath: string, // Path to service account JSON (optional)
120
+ serviceAccountJson: string // Service account JSON string (optional)
121
+ },
122
+ baseUrl: string, // Base URL for OAuth endpoints (optional, auto-detected)
123
+ clientId: string, // OAuth client ID for validation (optional)
124
+ clientSecret: string, // User password for Firebase auth
125
+ clientEmail: string, // User email for Firebase auth
126
+ redirectUri: string // Allowed redirect URI (optional)
127
+ }
128
+ ```
129
+
130
+ **Returns:**
131
+
132
+ ```javascript
133
+ {
134
+ metadata: Function, // GET /.well-known/oauth-authorization-server
135
+ authorize: Function, // GET /oauth/authorize
136
+ token: Function, // POST /oauth/token
137
+ validateToken: Function // Middleware for protecting endpoints
138
+ }
139
+ ```
140
+
141
+ ### validateToken Middleware
142
+
143
+ Express middleware that validates Firebase ID tokens from the `Authorization` header.
144
+
145
+ **Usage:**
146
+
147
+ ```javascript
148
+ // Basic usage - any authenticated user
149
+ app.post('/protected', oauth.validateToken(), (req, res) => {
150
+ // req.user contains:
151
+ // - uid: User's Firebase UID
152
+ // - email: User's email
153
+ // - emailVerified: Email verification status
154
+ // - role: User's role (from custom claims)
155
+ // - permissions: Array of user permissions (from custom claims)
156
+ // - customClaims: Full decoded token with all custom claims
157
+ });
158
+
159
+ // Require specific role
160
+ app.get('/admin', oauth.validateToken({ role: 'admin' }), handler);
161
+
162
+ // Require specific permission(s)
163
+ app.post('/write', oauth.validateToken({ permissions: 'write' }), handler);
164
+ app.delete('/item', oauth.validateToken({ permissions: ['write', 'delete'] }), handler);
165
+
166
+ // Custom validation
167
+ app.get('/premium', oauth.validateToken({
168
+ customCheck: (token) => token.subscriptionTier === 'premium'
169
+ }), handler);
170
+ ```
171
+
172
+ **Convenience Methods:**
173
+
174
+ ```javascript
175
+ // Require specific role
176
+ app.get('/admin', oauth.requireRole('admin'), handler);
177
+
178
+ // Require admin role
179
+ app.get('/admin', oauth.requireAdmin(), handler);
180
+
181
+ // Require permission(s)
182
+ app.post('/write', oauth.requirePermissions('write'), handler);
183
+ app.delete('/item', oauth.requirePermissions(['write', 'delete']), handler);
184
+ ```
185
+
186
+ **Error Response:**
187
+
188
+ Returns `401 Unauthorized` with `WWW-Authenticate` header if token is invalid or missing.
189
+ Returns `403 Forbidden` if user lacks required role/permissions.
190
+
191
+ ### Custom Claims Management
192
+
193
+ Helper functions for managing user roles and permissions:
194
+
195
+ ```javascript
196
+ const { setCustomClaims, getCustomClaims, updateCustomClaims } = require('@connexa/mcp-oauth-firebase-middleware');
197
+
198
+ // Set complete custom claims
199
+ await setCustomClaims('user-uid', {
200
+ role: 'admin',
201
+ permissions: ['read', 'write', 'delete']
202
+ });
203
+
204
+ // Get user's custom claims
205
+ const claims = await getCustomClaims('user-uid');
206
+ // Returns: { role: 'admin', permissions: [...] }
207
+
208
+ // Update specific claims (merges with existing)
209
+ await updateCustomClaims('user-uid', {
210
+ permissions: ['read', 'write', 'delete', 'manage']
211
+ });
212
+
213
+ // Remove all custom claims
214
+ await removeCustomClaims('user-uid');
215
+ ```
216
+
217
+ ## OAuth Endpoints
218
+
219
+ #### Server Metadata
220
+ ```http
221
+ GET /.well-known/oauth-authorization-server
222
+ ```
223
+
224
+ Returns OAuth server configuration.
225
+
226
+ #### Authorization
227
+ ```http
228
+ GET /oauth/authorize?client_id=...&code_challenge=...&code_challenge_method=S256&redirect_uri=...&response_type=code&state=...
229
+ ```
230
+
231
+ Authenticates user and returns authorization code.
232
+
233
+ #### Token Exchange
234
+ ```http
235
+ POST /oauth/token
236
+ Content-Type: application/x-www-form-urlencoded
237
+
238
+ grant_type=authorization_code&code=...&client_id=...&code_verifier=...&redirect_uri=...
239
+ ```
240
+
241
+ Exchanges authorization code for access token.
242
+
243
+ #### Token Refresh
244
+ ```http
245
+ POST /oauth/token
246
+ Content-Type: application/x-www-form-urlencoded
247
+
248
+ grant_type=refresh_token&refresh_token=...&client_id=...
249
+ ```
250
+
251
+ Refreshes expired access token.
252
+
253
+ ### Protected Endpoints
254
+
255
+ #### MCP Handler (Example)
256
+ ```http
257
+ POST /mcp
258
+ Authorization: Bearer <access_token>
259
+
260
+ {
261
+ "jsonrpc": "2.0",
262
+ "method": "your_method",
263
+ "params": {},
264
+ "id": 1
265
+ }
266
+ ```
267
+
268
+ #### Resources (Example)
269
+ ```http
270
+ GET /resources
271
+ Authorization: Bearer <access_token>
272
+ ```
273
+
274
+ ### Utility Endpoints
275
+
276
+ #### Health Check
277
+ ```http
278
+ GET /health
279
+ ```
280
+
281
+ Returns server status.
282
+
283
+ ## Example Server
284
+
285
+ The package includes an example server (`server.js`) for testing:
286
+
287
+ ```bash
288
+ npm start
289
+ ```
290
+
291
+ This runs a complete OAuth server on `http://localhost:8080` with example protected endpoints.
292
+
293
+ ## Advanced Usage
294
+
295
+ ### Custom Client Validation
296
+
297
+ Implement database-backed client validation:
298
+
299
+ ```javascript
300
+ const oauth = createOAuthMiddleware({
301
+ firebase: { /* ... */ },
302
+ validateClient: async (clientId, redirectUri) => {
303
+ const client = await db.clients.findOne({ clientId });
304
+ return client && client.redirectUris.includes(redirectUri);
305
+ }
306
+ });
307
+ ```
308
+
309
+ ### Using Service Account JSON from Environment
310
+
311
+ For cloud deployments, pass service account as JSON string:
312
+
313
+ ```javascript
314
+ const oauth = createOAuthMiddleware({
315
+ firebase: {
316
+ apiKey: process.env.FIREBASE_API_KEY,
317
+ authDomain: process.env.FIREBASE_AUTH_DOMAIN,
318
+ projectId: process.env.FIREBASE_PROJECT_ID,
319
+ serviceAccountJson: process.env.FIREBASE_SERVICE_ACCOUNT_JSON
320
+ }
321
+ });
322
+ ```
323
+
324
+ ### Auto-Detecting Base URL
325
+
326
+ When deployed (e.g., on Cloud Run), the middleware auto-detects the base URL from request headers:
327
+
328
+ ```javascript
329
+ const oauth = createOAuthMiddleware({
330
+ firebase: { /* ... */ },
331
+ // baseUrl omitted - will auto-detect from x-forwarded-proto and host headers
332
+ });
333
+ ```
334
+
335
+ ## Role-Based Access Control (RBAC)
336
+
337
+ This middleware supports role-based and permission-based access control using Firebase custom claims.
338
+
339
+ ### Setting Up Roles and Permissions
340
+
341
+ Custom claims are stored directly in the Firebase user token and are automatically included when the token is validated. They must be set using the Firebase Admin SDK:
342
+
343
+ ```javascript
344
+ const { setCustomClaims } = require('@connexa/mcp-oauth-firebase-middleware');
345
+
346
+ // Set user role and permissions
347
+ await setCustomClaims('user-firebase-uid', {
348
+ role: 'admin',
349
+ permissions: ['read', 'write', 'delete'],
350
+ subscriptionTier: 'premium' // Any custom data
351
+ });
352
+ ```
353
+
354
+ **Important:** After setting custom claims, users must refresh their tokens (or wait up to 1 hour for automatic refresh) to see the changes. Users can force a refresh by:
355
+ - Logging out and back in
356
+ - Calling `user.getIdToken(true)` on the client
357
+
358
+ ### Using Roles in Your Application
359
+
360
+ ```javascript
361
+ const { createOAuthMiddleware, setCustomClaims } = require('@connexa/mcp-oauth-firebase-middleware');
362
+
363
+ // Method 1: Using validateToken with options
364
+ app.get('/admin/dashboard', oauth.validateToken({ role: 'admin' }), (req, res) => {
365
+ res.json({ message: 'Admin dashboard', user: req.user });
366
+ });
367
+
368
+ // Method 2: Using convenience helpers
369
+ app.get('/admin/users', oauth.requireAdmin(), handler);
370
+ app.get('/editor/content', oauth.requireRole('editor'), handler);
371
+
372
+ // Method 3: Using permissions
373
+ app.post('/api/write', oauth.requirePermissions('write'), handler);
374
+ app.delete('/api/item', oauth.requirePermissions(['write', 'delete']), handler);
375
+
376
+ // Method 4: Custom validation logic
377
+ app.get('/premium/feature', oauth.validateToken({
378
+ customCheck: (token) => {
379
+ return token.subscriptionTier === 'premium' && token.emailVerified;
380
+ }
381
+ }), handler);
382
+
383
+ // Method 5: Manual check in handler
384
+ app.get('/resource', oauth.validateToken(), (req, res) => {
385
+ if (req.user.role !== 'admin' && !req.user.permissions.includes('read')) {
386
+ return res.status(403).json({ error: 'Insufficient permissions' });
387
+ }
388
+
389
+ res.json({ data: 'sensitive data' });
390
+ });
391
+ ```
392
+
393
+ ### Role Management Endpoints
394
+
395
+ Create admin endpoints to manage user roles:
396
+
397
+ ```javascript
398
+ const { setCustomClaims, getCustomClaims } = require('@connexa/mcp-oauth-firebase-middleware');
399
+
400
+ // Set user role (admin only)
401
+ app.post('/admin/users/:uid/role', oauth.requireAdmin(), async (req, res) => {
402
+ const { uid } = req.params;
403
+ const { role, permissions } = req.body;
404
+
405
+ await setCustomClaims(uid, { role, permissions });
406
+ res.json({ message: 'Role updated', uid, role, permissions });
407
+ });
408
+
409
+ // Get user's current role
410
+ app.get('/admin/users/:uid/role', oauth.requireAdmin(), async (req, res) => {
411
+ const claims = await getCustomClaims(req.params.uid);
412
+ res.json(claims);
413
+ });
414
+ ```
415
+
416
+ ### Common Role Patterns
417
+
418
+ ```javascript
419
+ // Basic roles
420
+ const roles = {
421
+ USER: 'user',
422
+ EDITOR: 'editor',
423
+ ADMIN: 'admin',
424
+ SUPER_ADMIN: 'super_admin'
425
+ };
426
+
427
+ // Permission-based (more flexible)
428
+ const permissions = {
429
+ READ: 'read',
430
+ WRITE: 'write',
431
+ DELETE: 'delete',
432
+ MANAGE_USERS: 'manage_users',
433
+ MANAGE_SETTINGS: 'manage_settings'
434
+ };
435
+
436
+ // Set permissions by role
437
+ await setCustomClaims(userId, {
438
+ role: 'editor',
439
+ permissions: ['read', 'write']
440
+ });
441
+
442
+ await setCustomClaims(adminId, {
443
+ role: 'admin',
444
+ permissions: ['read', 'write', 'delete', 'manage_users']
445
+ });
446
+ ```
447
+
448
+ ### Best Practices
449
+
450
+ 1. **Use permissions over roles** when possible for more granular control
451
+ 2. **Keep custom claims under 1000 bytes** (Firebase limit)
452
+ 3. **Cache role checks** at the application level if needed for performance
453
+ 4. **Implement role hierarchy** in your application logic if needed
454
+ 5. **Always validate on the server** - never trust client-side role checks
455
+ 6. **Log role changes** for audit trails
456
+ 7. **Force token refresh** after role updates for immediate effect
457
+
458
+ ## Project Structure
459
+
460
+ ```
461
+ mcp-oauth-firebase-middleware/
462
+ ├── lib/
463
+ │ ├── index.js # Main module export
464
+ │ ├── metadata.js # OAuth metadata endpoint
465
+ │ ├── authorize.js # Authorization endpoint
466
+ │ ├── token.js # Token endpoint
467
+ │ ├── firebase-client.js # Firebase Auth operations
468
+ │ ├── pkce.js # PKCE utilities
469
+ │ └── storage.js # Authorization code storage
470
+ ├── middleware/
471
+ │ └── validate-token.js # Token validation middleware
472
+ ├── server.js # Example Express server
473
+ ├── package.json
474
+ ├── .env.example
475
+ └── README.md
476
+ ```
477
+
478
+ ## Security Considerations
479
+
480
+ 🔒 **PKCE Required** - All authorization requests must use PKCE with S256
481
+ 🔒 **Single-Use Codes** - Authorization codes are invalidated after use
482
+ 🔒 **Code Expiration** - Authorization codes expire after 10 minutes
483
+ 🔒 **HTTPS Only** - All production endpoints must use HTTPS
484
+ 🔒 **Token Verification** - All protected endpoints validate Firebase ID tokens
485
+
486
+ ## Publishing to npm
487
+
488
+ 1. Update `package.json` with your details:
489
+ - Set `author` field
490
+ - Update `repository` URL
491
+ - Choose appropriate `name` (check availability on npm)
492
+
493
+ 2. Login to npm:
494
+ ```bash
495
+ npm login
496
+ ```
497
+
498
+ 3. Publish:
499
+ ```bash
500
+ npm publish
501
+ ```
502
+
503
+ The `files` field in `package.json` ensures only necessary files are included in the package.
504
+
505
+ ## Development
506
+
507
+ ### Running the Example Server
508
+
509
+ ```bash
510
+ # Install dependencies
511
+ npm install
512
+
513
+ # Copy environment template
514
+ cp .env.example .env
515
+
516
+ # Edit .env with your Firebase credentials
517
+
518
+ # Run server
519
+ npm start
520
+ ```
521
+
522
+ ### Testing with MCP Inspector
523
+
524
+ 1. Install MCP Inspector:
525
+ ```bash
526
+ npm install -g @modelcontextprotocol/inspector
527
+ ```
528
+
529
+ 2. Test OAuth flow:
530
+ ```bash
531
+ mcp-inspector http://localhost:8080
532
+ ```
533
+
534
+ 3. Verify:
535
+ - ✅ Metadata endpoint returns correct URLs
536
+ - ✅ Authorization redirects with code
537
+ - ✅ Token exchange returns valid tokens
538
+ - ✅ Protected endpoints accept access token
539
+ - ✅ Token refresh works
540
+
541
+ ## Project Structure
542
+
543
+ ```
544
+ mcp-oauth-firebase-middleware/
545
+ ├── lib/
546
+ │ ├── index.js # Main module export
547
+ │ ├── metadata.js # OAuth metadata endpoint
548
+ │ ├── authorize.js # Authorization endpoint
549
+ │ ├── token.js # Token endpoint
550
+ │ ├── firebase-client.js # Firebase Auth operations
551
+ │ ├── pkce.js # PKCE utilities
552
+ │ └── storage.js # Authorization code storage
553
+ ├── middleware/
554
+ │ └── validate-token.js # Token validation middleware
555
+ ├── server.js # Example Express server
556
+ ├── package.json
557
+ ├── .env.example
558
+ └── README.md
559
+ ```
560
+
561
+ ## Security Considerations
562
+
563
+ 🔒 **PKCE Required** - All authorization requests must use PKCE with S256
564
+ 🔒 **Single-Use Codes** - Authorization codes are invalidated after use
565
+ 🔒 **Code Expiration** - Authorization codes expire after 10 minutes
566
+ 🔒 **HTTPS Only** - All production endpoints must use HTTPS
567
+ 🔒 **Token Verification** - All protected endpoints validate Firebase ID tokens
568
+
569
+ ## Publishing to npm
570
+
571
+ 1. Update `package.json` with your details:
572
+ - Set `author` field
573
+ - Update `repository` URL
574
+ - Choose appropriate `name` (check availability on npm)
575
+
576
+ 2. Login to npm:
577
+ ```bash
578
+ npm login
579
+ ```
580
+
581
+ 3. Publish:
582
+ ```bash
583
+ npm publish
584
+ ```
585
+
586
+ The `files` field in `package.json` ensures only necessary files are included in the package.
587
+
588
+ ## Development
589
+
590
+ ### Running the Example Server
591
+
592
+ ```bash
593
+ # Install dependencies
594
+ npm install
595
+
596
+ # Copy environment template
597
+ cp .env.example .env
598
+
599
+ # Edit .env with your Firebase credentials
600
+
601
+ # Run server
602
+ npm start
603
+ ```
604
+
605
+ ### Testing with MCP Inspector
606
+
607
+ ## Troubleshooting
608
+
609
+ ### Firebase Authentication Error
610
+ - Verify Firebase credentials in `.env`
611
+ - Ensure user exists in Firebase Authentication
612
+ - Check that Email/Password provider is enabled
613
+
614
+ ### PKCE Validation Failed
615
+ - Ensure client is using S256 method
616
+ - Verify code_verifier matches code_challenge
617
+
618
+ ### Token Verification Failed
619
+ - Check Firebase Admin SDK is initialized correctly
620
+ - Verify service account has proper permissions
621
+ - Ensure token hasn't expired (3600s lifetime)
622
+
623
+ ## License
624
+
625
+ MIT
626
+
627
+ ## Support
628
+
629
+ For issues and questions:
630
+ - Check [MCP Documentation](https://modelcontextprotocol.io)
631
+ - Review Firebase Auth [documentation](https://firebase.google.com/docs/auth)
632
+ - Open an issue in this repository