spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / plugins / plugin-dev / skills / mcp-integration / references / authentication.md
682 lines · 13.4 KBPreviewCodeRaw
1# MCP Authentication Patterns
2
3Complete guide to authentication methods for MCP servers in Claude Code plugins.
4
5## Overview
6
7MCP servers support multiple authentication methods depending on the server type and service requirements. Choose the method that best matches your use case and security requirements.
8
9## OAuth (Automatic)
10
11### How It Works
12
13Claude Code automatically handles the complete OAuth 2.0 flow for SSE and HTTP servers:
14
151. User attempts to use MCP tool
162. Claude Code detects authentication needed
173. Opens browser for OAuth consent
184. User authorizes in browser
195. Tokens stored securely by Claude Code
206. Automatic token refresh
21
22### Configuration
23
24```json
25{
26 "service": {
27 "type": "sse",
28 "url": "https://mcp.example.com/sse"
29 }
30}
31```
32
33No additional auth configuration needed! Claude Code handles everything.
34
35### Supported Services
36
37**Known OAuth-enabled MCP servers:**
38- Asana: `https://mcp.asana.com/sse`
39- GitHub (when available)
40- Google services (when available)
41- Custom OAuth servers
42
43### OAuth Scopes
44
45OAuth scopes are determined by the MCP server. Users see required scopes during the consent flow.
46
47**Document required scopes in your README:**
48```markdown
49## Authentication
50
51This plugin requires the following Asana permissions:
52- Read tasks and projects
53- Create and update tasks
54- Access workspace data
55```
56
57### Token Storage
58
59Tokens are stored securely by Claude Code:
60- Not accessible to plugins
61- Encrypted at rest
62- Automatic refresh
63- Cleared on sign-out
64
65### Troubleshooting OAuth
66
67**Authentication loop:**
68- Clear cached tokens (sign out and sign in)
69- Check OAuth redirect URLs
70- Verify server OAuth configuration
71
72**Scope issues:**
73- User may need to re-authorize for new scopes
74- Check server documentation for required scopes
75
76**Token expiration:**
77- Claude Code auto-refreshes
78- If refresh fails, prompts re-authentication
79
80## Token-Based Authentication
81
82### Bearer Tokens
83
84Most common for HTTP and WebSocket servers.
85
86**Configuration:**
87```json
88{
89 "api": {
90 "type": "http",
91 "url": "https://api.example.com/mcp",
92 "headers": {
93 "Authorization": "Bearer ${API_TOKEN}"
94 }
95 }
96}
97```
98
99**Environment variable:**
100```bash
101export API_TOKEN="your-secret-token-here"
102```
103
104### API Keys
105
106Alternative to Bearer tokens, often in custom headers.
107
108**Configuration:**
109```json
110{
111 "api": {
112 "type": "http",
113 "url": "https://api.example.com/mcp",
114 "headers": {
115 "X-API-Key": "${API_KEY}",
116 "X-API-Secret": "${API_SECRET}"
117 }
118 }
119}
120```
121
122### Custom Headers
123
124Services may use custom authentication headers.
125
126**Configuration:**
127```json
128{
129 "service": {
130 "type": "sse",
131 "url": "https://mcp.example.com/sse",
132 "headers": {
133 "X-Auth-Token": "${AUTH_TOKEN}",
134 "X-User-ID": "${USER_ID}",
135 "X-Tenant-ID": "${TENANT_ID}"
136 }
137 }
138}
139```
140
141### Documenting Token Requirements
142
143Always document in your README:
144
145```markdown
146## Setup
147
148### Required Environment Variables
149
150Set these environment variables before using the plugin:
151
152\`\`\`bash
153export API_TOKEN="your-token-here"
154export API_SECRET="your-secret-here"
155\`\`\`
156
157### Obtaining Tokens
158
1591. Visit https://api.example.com/tokens
1602. Create a new API token
1613. Copy the token and secret
1624. Set environment variables as shown above
163
164### Token Permissions
165
166The API token needs the following permissions:
167- Read access to resources
168- Write access for creating items
169- Delete access (optional, for cleanup operations)
170\`\`\`
171```
172
173## Environment Variable Authentication (stdio)
174
175### Passing Credentials to Server
176
177For stdio servers, pass credentials via environment variables:
178
179```json
180{
181 "database": {
182 "command": "python",
183 "args": ["-m", "mcp_server_db"],
184 "env": {
185 "DATABASE_URL": "${DATABASE_URL}",
186 "DB_USER": "${DB_USER}",
187 "DB_PASSWORD": "${DB_PASSWORD}"
188 }
189 }
190}
191```
192
193### User Environment Variables
194
195```bash
196# User sets these in their shell
197export DATABASE_URL="postgresql://localhost/mydb"
198export DB_USER="myuser"
199export DB_PASSWORD="mypassword"
200```
201
202### Documentation Template
203
204```markdown
205## Database Configuration
206
207Set these environment variables:
208
209\`\`\`bash
210export DATABASE_URL="postgresql://host:port/database"
211export DB_USER="username"
212export DB_PASSWORD="password"
213\`\`\`
214
215Or create a `.env` file (add to `.gitignore`):
216
217\`\`\`
218DATABASE_URL=postgresql://localhost:5432/mydb
219DB_USER=myuser
220DB_PASSWORD=mypassword
221\`\`\`
222
223Load with: \`source .env\` or \`export $(cat .env | xargs)\`
224\`\`\`
225```
226
227## Dynamic Headers
228
229### Headers Helper Script
230
231For tokens that change or expire, use a helper script:
232
233```json
234{
235 "api": {
236 "type": "sse",
237 "url": "https://api.example.com",
238 "headersHelper": "${CLAUDE_PLUGIN_ROOT}/scripts/get-headers.sh"
239 }
240}
241```
242
243**Script (get-headers.sh):**
244```bash
245#!/bin/bash
246# Generate dynamic authentication headers
247
248# Fetch fresh token
249TOKEN=$(get-fresh-token-from-somewhere)
250
251# Output JSON headers
252cat <<EOF
253{
254 "Authorization": "Bearer $TOKEN",
255 "X-Timestamp": "$(date -Iseconds)"
256}
257EOF
258```
259
260### Use Cases for Dynamic Headers
261
262- Short-lived tokens that need refresh
263- Tokens with HMAC signatures
264- Time-based authentication
265- Dynamic tenant/workspace selection
266
267### TTL Configuration
268
269By default, dynamically generated API keys are cached for 5 minutes. Configure the TTL with:
270
271```bash
272export CLAUDE_CODE_API_KEY_HELPER_TTL_MS=300000 # 5 minutes (default)
273```
274
275### Writing Robust Helper Scripts
276
277Helper scripts can cause issues if they hang or fail repeatedly. Follow these best practices to prevent infinite retry loops and connection hangs:
278
279**1. Always set timeouts on network operations:**
280```bash
281#!/bin/bash
282# get-token.sh - Robust token fetcher
283
284# Set a timeout for the entire script
285TIMEOUT_SECONDS=10
286
287# Use timeout for network calls
288TOKEN=$(timeout ${TIMEOUT_SECONDS}s curl -s --max-time ${TIMEOUT_SECONDS} \
289 "https://auth.example.com/token" 2>/dev/null)
290
291if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
292 # Exit with error - don't output invalid JSON
293 echo "Failed to fetch token" >&2
294 exit 1
295fi
296
297echo "{\"Authorization\": \"Bearer $TOKEN\"}"
298```
299
300**2. Handle VPN/network dependency failures:**
301```bash
302#!/bin/bash
303# get-headers.sh - VPN-aware token fetcher
304
305# Quick connectivity check before attempting auth
306if ! timeout 2s ping -c 1 vpn-dependent-service.internal >/dev/null 2>&1; then
307 echo "VPN not connected or service unreachable" >&2
308 exit 1
309fi
310
311# Proceed with token fetch (with timeout)
312TOKEN=$(timeout 10s get-token-from-vpn-service)
313
314if [ $? -ne 0 ] || [ -z "$TOKEN" ]; then
315 echo "Token fetch failed" >&2
316 exit 1
317fi
318
319echo "{\"Authorization\": \"Bearer $TOKEN\"}"
320```
321
322**3. Cache tokens locally to reduce network calls:**
323```bash
324#!/bin/bash
325# get-headers-cached.sh - Token fetcher with local caching
326
327CACHE_FILE="${HOME}/.cache/my-api-token"
328CACHE_MAX_AGE=240 # seconds (refresh before 5min TTL)
329
330# Check cache validity
331if [ -f "$CACHE_FILE" ]; then
332 CACHE_AGE=$(($(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE")))
333 if [ "$CACHE_AGE" -lt "$CACHE_MAX_AGE" ]; then
334 cat "$CACHE_FILE"
335 exit 0
336 fi
337fi
338
339# Fetch new token with timeout
340TOKEN=$(timeout 10s fetch-new-token 2>/dev/null)
341
342if [ -z "$TOKEN" ]; then
343 # If fetch fails, try to use expired cache as fallback
344 if [ -f "$CACHE_FILE" ]; then
345 echo "Warning: Using expired cached token" >&2
346 cat "$CACHE_FILE"
347 exit 0
348 fi
349 echo "Failed to fetch token and no cache available" >&2
350 exit 1
351fi
352
353# Update cache
354mkdir -p "$(dirname "$CACHE_FILE")"
355echo "{\"Authorization\": \"Bearer $TOKEN\"}" > "$CACHE_FILE"
356cat "$CACHE_FILE"
357```
358
359**4. Fail fast with clear error messages:**
360```bash
361#!/bin/bash
362set -e # Exit on any error
363
364# Check prerequisites before attempting network calls
365if [ -z "$API_SECRET" ]; then
366 echo "API_SECRET environment variable not set" >&2
367 exit 1
368fi
369
370# Use short timeouts to fail fast
371TOKEN=$(timeout 5s curl -sf --max-time 5 \
372 -H "X-Secret: $API_SECRET" \
373 "https://auth.example.com/token") || {
374 echo "Token request failed or timed out" >&2
375 exit 1
376}
377
378echo "{\"Authorization\": \"Bearer $TOKEN\"}"
379```
380
381### Troubleshooting Helper Scripts
382
383**Infinite retry loop / hanging:**
384- Add timeouts to all network operations
385- Use `set -e` to exit on errors
386- Check VPN/network connectivity before making requests
387- Ensure script outputs valid JSON or exits with error code
388
389**Script takes too long:**
390- Use `timeout` command wrapper
391- Set `--max-time` on curl requests
392- Consider caching tokens locally
393- Reduce TTL if tokens refresh too slowly
394
395**VPN-dependent helpers failing:**
396- Add connectivity check at start of script
397- Implement graceful degradation with cached tokens
398- Log clear error messages to stderr
399
400## Security Best Practices
401
402### DO
403
404✅ **Use environment variables:**
405```json
406{
407 "headers": {
408 "Authorization": "Bearer ${API_TOKEN}"
409 }
410}
411```
412
413✅ **Document required variables in README**
414
415✅ **Use HTTPS/WSS always**
416
417✅ **Implement token rotation**
418
419✅ **Store tokens securely (env vars, not files)**
420
421✅ **Let OAuth handle authentication when available**
422
423### DON'T
424
425❌ **Hardcode tokens:**
426```json
427{
428 "headers": {
429 "Authorization": "Bearer sk-abc123..." // NEVER!
430 }
431}
432```
433
434❌ **Commit tokens to git**
435
436❌ **Share tokens in documentation**
437
438❌ **Use HTTP instead of HTTPS**
439
440❌ **Store tokens in plugin files**
441
442❌ **Log tokens or sensitive headers**
443
444## Multi-Tenancy Patterns
445
446### Workspace/Tenant Selection
447
448**Via environment variable:**
449```json
450{
451 "api": {
452 "type": "http",
453 "url": "https://api.example.com/mcp",
454 "headers": {
455 "Authorization": "Bearer ${API_TOKEN}",
456 "X-Workspace-ID": "${WORKSPACE_ID}"
457 }
458 }
459}
460```
461
462**Via URL:**
463```json
464{
465 "api": {
466 "type": "http",
467 "url": "https://${TENANT_ID}.api.example.com/mcp"
468 }
469}
470```
471
472### Per-User Configuration
473
474Users set their own workspace:
475
476```bash
477export WORKSPACE_ID="my-workspace-123"
478export TENANT_ID="my-company"
479```
480
481## Authentication Troubleshooting
482
483### Common Issues
484
485**401 Unauthorized:**
486- Check token is set correctly
487- Verify token hasn't expired
488- Check token has required permissions
489- Ensure header format is correct
490
491**403 Forbidden:**
492- Token valid but lacks permissions
493- Check scope/permissions
494- Verify workspace/tenant ID
495- May need admin approval
496
497**Token not found:**
498```bash
499# Check environment variable is set
500echo $API_TOKEN
501
502# If empty, set it
503export API_TOKEN="your-token"
504```
505
506**Token in wrong format:**
507```json
508// Correct
509"Authorization": "Bearer sk-abc123"
510
511// Wrong
512"Authorization": "sk-abc123"
513```
514
515### Debugging Authentication
516
517**Enable debug mode:**
518```bash
519claude --debug
520```
521
522Look for:
523- Authentication header values (sanitized)
524- OAuth flow progress
525- Token refresh attempts
526- Authentication errors
527
528**Test authentication separately:**
529```bash
530# Test HTTP endpoint
531curl -H "Authorization: Bearer $API_TOKEN" \
532 https://api.example.com/mcp/health
533
534# Should return 200 OK
535```
536
537## Migration Patterns
538
539### From Hardcoded to Environment Variables
540
541**Before:**
542```json
543{
544 "headers": {
545 "Authorization": "Bearer sk-hardcoded-token"
546 }
547}
548```
549
550**After:**
551```json
552{
553 "headers": {
554 "Authorization": "Bearer ${API_TOKEN}"
555 }
556}
557```
558
559**Migration steps:**
5601. Add environment variable to plugin README
5612. Update configuration to use ${VAR}
5623. Test with variable set
5634. Remove hardcoded value
5645. Commit changes
565
566### From Basic Auth to OAuth
567
568**Before:**
569```json
570{
571 "headers": {
572 "Authorization": "Basic ${BASE64_CREDENTIALS}"
573 }
574}
575```
576
577**After:**
578```json
579{
580 "type": "sse",
581 "url": "https://mcp.example.com/sse"
582}
583```
584
585**Benefits:**
586- Better security
587- No credential management
588- Automatic token refresh
589- Scoped permissions
590
591## Advanced Authentication
592
593### Mutual TLS (mTLS)
594
595Some enterprise services require client certificates.
596
597**Not directly supported in MCP configuration.**
598
599**Workaround:** Wrap in stdio server that handles mTLS:
600
601```json
602{
603 "secure-api": {
604 "command": "${CLAUDE_PLUGIN_ROOT}/servers/mtls-wrapper",
605 "args": ["--cert", "${CLIENT_CERT}", "--key", "${CLIENT_KEY}"],
606 "env": {
607 "API_URL": "https://secure.example.com"
608 }
609 }
610}
611```
612
613### JWT Tokens
614
615Generate JWT tokens dynamically with headers helper:
616
617```bash
618#!/bin/bash
619# generate-jwt.sh
620
621# Generate JWT (using library or API call)
622JWT=$(generate-jwt-token)
623
624echo "{\"Authorization\": \"Bearer $JWT\"}"
625```
626
627```json
628{
629 "headersHelper": "${CLAUDE_PLUGIN_ROOT}/scripts/generate-jwt.sh"
630}
631```
632
633### HMAC Signatures
634
635For APIs requiring request signing:
636
637```bash
638#!/bin/bash
639# generate-hmac.sh
640
641TIMESTAMP=$(date -Iseconds)
642SIGNATURE=$(echo -n "$TIMESTAMP" | openssl dgst -sha256 -hmac "$SECRET_KEY" | cut -d' ' -f2)
643
644cat <<EOF
645{
646 "X-Timestamp": "$TIMESTAMP",
647 "X-Signature": "$SIGNATURE",
648 "X-API-Key": "$API_KEY"
649}
650EOF
651```
652
653## Best Practices Summary
654
655### For Plugin Developers
656
6571. **Prefer OAuth** when service supports it
6582. **Use environment variables** for tokens
6593. **Document all required variables** in README
6604. **Provide setup instructions** with examples
6615. **Never commit credentials**
6626. **Use HTTPS/WSS only**
6637. **Test authentication thoroughly**
664
665### For Plugin Users
666
6671. **Set environment variables** before using plugin
6682. **Keep tokens secure** and private
6693. **Rotate tokens regularly**
6704. **Use different tokens** for dev/prod
6715. **Don't commit .env files** to git
6726. **Review OAuth scopes** before authorizing
673
674## Conclusion
675
676Choose the authentication method that matches your MCP server's requirements:
677- **OAuth** for cloud services (easiest for users)
678- **Bearer tokens** for API services
679- **Environment variables** for stdio servers
680- **Dynamic headers** for complex auth flows
681
682Always prioritize security and provide clear setup documentation for users.