@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 +632 -0
- package/lib/authorize.js +126 -0
- package/lib/firebase-client.js +250 -0
- package/lib/index.js +70 -0
- package/lib/metadata.js +36 -0
- package/lib/pkce.js +53 -0
- package/lib/storage.js +109 -0
- package/lib/token.js +150 -0
- package/middleware/validate-token.js +127 -0
- package/package.json +51 -0
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
|