OAuth 2.1 Authentication
OAuth 2.1 is the gold standard for modern API authentication. NitroStack makes it incredibly easy to implement production-grade OAuth - no complex setup, no security pitfalls.
๐ฏ Why OAuth 2.1?
โ Industry Standard - Used by Google, Microsoft, Auth0, etc. โ User-Facing Apps - Perfect for applications with end users โ Fine-Grained Permissions - Scope-based access control โ Temporary Access - Short-lived tokens with refresh โ Federated Identity - Users login with existing accounts โ MCP Compliant - Follows MCP & OpenAI Apps SDK specs
๐ How Easy Is It?
Traditional OAuth Implementation โ
// 500+ lines of code
// - Token validation
// - JWKS fetching & caching
// - Audience validation
// - Issuer validation
// - Scope checking
// - Metadata endpoints
// - Error handling
// ... and more
NitroStack OAuth Implementation โ
// 10 lines of code!
import { OAuthModule, UseGuards, OAuthGuard } from 'nitrostack';
OAuthModule.forRoot({
  resourceUri: 'http://localhost:3002',
  authorizationServers: ['https://auth.example.com'],
  scopesSupported: ['read', 'write', 'admin'],
});
@Tool({ name: 'protected' })
@UseGuards(OAuthGuard)
async protected() { /* ... */ }
That's it! NitroStack handles everything else automatically.
๐ฆ Built-in OAuth Module
NitroStack's OAuthModule is a complete, production-ready OAuth 2.1 implementation.
What It Does Automatically
โ Token Validation - JWT signature, expiry, audience, issuer โ JWKS Management - Fetches & caches public keys โ Metadata Endpoints - RFC 9728 Protected Resource Metadata โ Audience Binding - RFC 8707 Resource Indicators โ Token Introspection - RFC 7662 for opaque tokens โ Scope Validation - Fine-grained permissions โ Error Handling - Clear, actionable error messages โ Dual Transport - STDIO for MCP + HTTP for OAuth metadata
๐ Quick Start
1. Install Template
npx nitrostack init my-oauth-server
# Select: typescript-oauth
cd my-oauth-server
npm install
2. Setup Auth0 (5 Minutes)
Follow the complete guide in templates/typescript-oauth/OAUTH_SETUP.md
Quick Summary:
- Create Auth0 Application (get Client ID/Secret)
 - Create Auth0 API (set Identifier = your RESOURCE_URI)
 - Add scopes: 
read,write,admin - Configure 
.envwith your Auth0 settings 
3. Configure OAuth Module
// src/app.module.ts
import { Module, OAuthModule } from 'nitrostack';
@Module({
  name: 'app',
  imports: [
    OAuthModule.forRoot({
      // Your MCP server's public URL
      resourceUri: process.env.RESOURCE_URI!,
      
      // Your OAuth provider(s)
      authorizationServers: [process.env.AUTH_SERVER_URL!],
      
      // Supported scopes
      scopesSupported: ['read', 'write', 'admin'],
      
      // Optional: Token validation
      audience: process.env.TOKEN_AUDIENCE,
      issuer: process.env.TOKEN_ISSUER,
    }),
  ],
})
export class AppModule {}
4. Start Server
npm run dev
You'll see:
๐ HTTP MCP Server listening on http://0.0.0.0:3002/mcp
๐ OAuth 2.1 enabled
๐ Server started successfully (DUAL: STDIO + HTTP)
๐ก MCP Protocol: STDIO (for Studio/Claude)
๐ OAuth Metadata: HTTP (port 3002)
5. Test in Studio
- Open Studio at 
http://localhost:3000 - Auth โ OAuth 2.1
 - Discover: 
http://localhost:3002 - Enter Client ID/Secret
 - Start OAuth Flow
 - Login & Authorize
 - โ Done! Test protected tools
 
๐ก๏ธ Protecting Tools
Basic Protection
import { Tool, UseGuards, OAuthGuard } from 'nitrostack';
export class DemoTools {
  // Public tool - no authentication
  @Tool({
    name: 'get_server_info',
    description: 'Get public server information',
  })
  async getServerInfo() {
    return {
      name: 'My MCP Server',
      version: '1.0.0',
    };
  }
  // Protected tool - requires valid OAuth token
  @Tool({
    name: 'get_user_profile',
    description: 'Get authenticated user profile',
  })
  @UseGuards(OAuthGuard)
  async getUserProfile(@ExecutionContext() context: ExecutionContext) {
    return {
      user: context.auth.subject,      // 'auth0|abc123'
      scopes: context.auth.scopes,     // ['read', 'write']
      claims: context.auth.claims,     // Full token payload
    };
  }
}
Scope-Based Access
import { Tool, UseGuards, OAuthGuard, createScopeGuard } from 'nitrostack';
export class ResourceTools {
  // Requires 'read' scope
  @Tool({ name: 'list_resources' })
  @UseGuards(OAuthGuard, createScopeGuard('read'))
  async listResources() {
    return { resources: [...] };
  }
  // Requires 'write' scope
  @Tool({ name: 'create_resource' })
  @UseGuards(OAuthGuard, createScopeGuard('write'))
  async createResource() {
    // Only users with 'write' scope can call this
  }
  // Requires BOTH 'read' AND 'admin' scopes
  @Tool({ name: 'admin_stats' })
  @UseGuards(OAuthGuard, createScopeGuard('read', 'admin'))
  async adminStats() {
    // Only admins with read access
  }
}
๐๏ธ Architecture: Dual Transport
NitroStack runs two transports simultaneously for OAuth servers:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ      Your OAuth 2.1 MCP Server           โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ                                          โ
โ  ๐ก STDIO Transport                      โ
โ  โโ MCP Protocol Communication           โ
โ  โโ Tool Execution                       โ
โ  โโ Connected to Studio/Claude           โ
โ  โโ Fast, efficient, standard            โ
โ                                          โ
โ  ๐ HTTP Server (Port 3002)              โ
โ  โโ OAuth Metadata Endpoints             โ
โ  โโ /.well-known/oauth-protected-        โ
โ  โ   resource (RFC 9728)                 โ
โ  โโ Token Validation                     โ
โ  โโ Discovery & Registration             โ
โ                                          โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Why Dual Transport?
- STDIO - Fast MCP protocol for tool calls
 - HTTP - Standards-compliant OAuth metadata
 - Best of Both - Performance + Compatibility
 - Automatic - You don't configure anything!
 
When you enable OAuthModule, NitroStack automatically:
- โ Keeps STDIO for MCP protocol
 - โ Starts HTTP server for OAuth metadata
 - โ Exposes discovery endpoints
 - โ Handles all the complexity
 
๐ Token Validation
NitroStack validates every aspect of OAuth tokens automatically.
What Gets Validated
// Token must be:
โ
 Valid JWT format
โ
 Not expired (exp claim)
โ
 Correct audience (aud claim)
โ
 Correct issuer (iss claim)
โ
 Valid signature (verified against JWKS)
โ
 Not used before valid time (nbf claim)
โ
 Has required scopes
Example Token
{
  "iss": "https://auth.example.com/",
  "sub": "auth0|abc123",
  "aud": "http://localhost:3002",
  "exp": 1234567890,
  "iat": 1234564290,
  "scope": "read write admin",
  "email": "user@example.com"
}
Access Token Claims
@Tool({ name: 'check_token' })
@UseGuards(OAuthGuard)
async checkToken(@ExecutionContext() context: ExecutionContext) {
  const token = context.auth;
  
  return {
    // Standard claims
    subject: token.subject,          // User ID
    scopes: token.scopes,            // ['read', 'write']
    expiresAt: token.expiresAt,      // Expiration timestamp
    
    // Full token payload
    claims: token.claims,            // All claims
    email: token.claims.email,       // Custom claims
  };
}
๐ OAuth Flow Explained
Complete Flow Diagram
โโโโโโโโโโ                                  โโโโโโโโโโโโโโ
โ Studio โ                                  โ  Your MCP  โ
โ        โ                                  โ   Server   โ
โโโโโฌโโโโโ                                  โโโโโโโฌโโโโโโโ
    โ                                             โ
    โ 1. Discover OAuth Config                   โ
    โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ  โ
    โ    GET /.well-known/oauth-protected-resource
    โ                                             โ
    โ 2. OAuth Metadata Response                 โ
    โ  โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
    โ    { resource, authorization_servers, ... }
    โ                                             โ
โโโโโดโโโโโโโ
โ          โ
โ User     โ
โ Enters   โ 3. User Enters Client ID/Secret
โ Creds    โ
โ          โ
โโโโโฌโโโโโโโ
    โ
    โ 4. Start OAuth Flow
    โโโโโโโโโโโโโโโโโโโโโโโโโโโ
    โ                         โ
    โ                   โโโโโโโผโโโโโโโ
    โ                   โ   Auth0    โ
    โ 5. Redirect       โ   Login    โ
    โ  โโโโโโโโโโโโโโโโโโค            โ
    โ                   โโโโโโโฌโโโโโโโ
    โ                         โ
    โ 6. User Logs In         โ
    โโโโโโโโโโโโโโโโโโโโโโโโโโบโ
    โ                         โ
    โ 7. Authorization Code   โ
    โ  โโโโโโโโโโโโโโโโโโโโโโโโค
    โ                         โ
    โ 8. Exchange Code        โ
    โโโโโโโโโโโโโโโโโโโโโโโโโโบโ
    โ                         โ
    โ 9. JWT Access Token     โ
    โ  โโโโโโโโโโโโโโโโโโโโโโโโค
    โ                         โ
    โ 10. Store Token         โ
    โ                         โ
    โ 11. Call Tool (+ Token) โ
    โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบโ
    โ                         โ                  โ
    โ                         โ  12. Validate    โ
    โ                         โ      Token       โ
    โ                         โ                  โ
    โ 13. Tool Response       โ                  โ
    โ  โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
    โ                         โ                  โ
๐ง Advanced Configuration
Token Introspection (Opaque Tokens)
If your OAuth provider uses opaque tokens (not JWTs):
OAuthModule.forRoot({
  resourceUri: process.env.RESOURCE_URI!,
  authorizationServers: [process.env.AUTH_SERVER_URL!],
  scopesSupported: ['read', 'write'],
  
  // Token introspection (RFC 7662)
  tokenIntrospectionEndpoint: process.env.INTROSPECTION_ENDPOINT,
  tokenIntrospectionClientId: process.env.INTROSPECTION_CLIENT_ID,
  tokenIntrospectionClientSecret: process.env.INTROSPECTION_CLIENT_SECRET,
})
Custom Token Validation
Add your own validation logic:
OAuthModule.forRoot({
  resourceUri: process.env.RESOURCE_URI!,
  authorizationServers: [process.env.AUTH_SERVER_URL!],
  scopesSupported: ['read', 'write'],
  
  // Custom validation
  customValidation: async (tokenPayload) => {
    // Check if user is active in your database
    const user = await db.users.findOne({ id: tokenPayload.sub });
    if (!user || !user.active) {
      return false;
    }
    
    // Check subscription status
    if (!user.subscription || user.subscription.expired) {
      return false;
    }
    
    return true;
  },
})
Multiple Authorization Servers
Support federated authentication:
OAuthModule.forRoot({
  resourceUri: process.env.RESOURCE_URI!,
  
  // Accept tokens from multiple providers
  authorizationServers: [
    'https://auth0.example.com',
    'https://okta.example.com',
    'https://azuread.example.com',
  ],
  
  scopesSupported: ['read', 'write'],
})
๐ Security Features
1. Token Audience Binding (RFC 8707)
Critical for security! Tokens are validated to ensure they were issued specifically for your MCP server.
// Token MUST have your RESOURCE_URI in the audience claim
{
  "aud": "http://localhost:3002",  // โ Must match RESOURCE_URI
  "sub": "user123",
  "scope": "read write"
}
Why This Matters:
- โ Without audience binding: Tokens from ANY service work
 - โ With audience binding: Only tokens for YOUR service work
 
2. No Token Passthrough
Never forward OAuth tokens to other services!
// โ WRONG - Security vulnerability!
async function callUpstream(clientToken: string) {
  await upstreamAPI.call({
    headers: { Authorization: clientToken }
  });
}
// โ
 CORRECT - Get separate token
async function callUpstream() {
  const upstreamToken = await getTokenForUpstream();
  await upstreamAPI.call({
    headers: { Authorization: upstreamToken }
  });
}
3. Short-Lived Tokens
Configure your OAuth provider to issue short-lived tokens:
Access Token: 1 hour
Refresh Token: 30 days
NitroStack Studio automatically handles token refresh.
๐งช Testing
In Studio
Complete flow:
- Auth โ OAuth 2.1
 - Discover server
 - Enter credentials
 - Start OAuth flow
 - Login & authorize
 - Test tools
 
Programmatic Testing
// test/oauth.test.ts
import { createTestClient } from 'nitrostack/testing';
describe('OAuth Authentication', () => {
  it('should protect tools with OAuth', async () => {
    const client = await createTestClient(AppModule);
    
    // Without token - should fail
    await expect(
      client.callTool('protected_tool', {})
    ).rejects.toThrow('OAuth token required');
    
    // With token - should succeed
    const result = await client.callTool('protected_tool', {}, {
      token: 'eyJhbGciOiJSUzI1NiIs...'  // Valid JWT
    });
    
    expect(result.success).toBe(true);
  });
  
  it('should validate scopes', async () => {
    const client = await createTestClient(AppModule);
    
    // Token without required scope
    await expect(
      client.callTool('admin_tool', {}, {
        token: tokenWithoutAdminScope
      })
    ).rejects.toThrow('Insufficient scope');
  });
});
๐ Production Deployment
1. Use Production OAuth Provider
# Production environment variables
RESOURCE_URI=https://mcp.yourapp.com
AUTH_SERVER_URL=https://auth.yourapp.com
TOKEN_AUDIENCE=https://mcp.yourapp.com
TOKEN_ISSUER=https://auth.yourapp.com/
2. Configure HTTPS
OAuth requires HTTPS in production:
// Use reverse proxy (nginx, Cloudflare, etc.)
// Or configure HTTPS directly:
OAuthModule.forRoot({
  resourceUri: 'https://mcp.yourapp.com',  // HTTPS!
  // ...
})
3. Monitor Token Usage
// Add logging/metrics
OAuthModule.forRoot({
  customValidation: async (token) => {
    // Log token usage
    await metrics.increment('oauth.token.validated', {
      user: token.sub,
      scopes: token.scope,
    });
    
    return true;
  },
})
๐ก Common Patterns
Role-Based Access Control
// Map OAuth scopes to roles
@Tool({ name: 'admin_panel' })
@UseGuards(OAuthGuard, createScopeGuard('admin'))
async adminPanel(@ExecutionContext() context: ExecutionContext) {
  const userRoles = context.auth.claims.roles;  // ['admin', 'moderator']
  
  if (!userRoles.includes('admin')) {
    throw new Error('Admin role required');
  }
  
  // ... admin operations
}
Tenant Isolation
// Multi-tenant applications
@Tool({ name: 'get_data' })
@UseGuards(OAuthGuard)
async getData(@ExecutionContext() context: ExecutionContext) {
  const tenantId = context.auth.claims.tenant_id;
  
  // Only return data for user's tenant
  return await db.data.find({ tenantId });
}
๐ Standards Compliance
NitroStack implements:
โ OAuth 2.1 (draft-ietf-oauth-v2-1-13) โ RFC 9728 - Protected Resource Metadata โ RFC 8414 - Authorization Server Metadata โ RFC 7591 - Dynamic Client Registration โ RFC 8707 - Resource Indicators (Token Audience Binding) โ RFC 7636 - PKCE โ RFC 7662 - Token Introspection
Compatible with: โ MCP Specification โ OpenAI Apps SDK
๐ Supported Providers
NitroStack works with any RFC-compliant OAuth 2.1 provider:
- โ Auth0 - Easiest for testing
 - โ Okta - Enterprise-ready
 - โ Keycloak - Self-hosted
 - โ Azure AD / Entra ID
 - โ Google Identity Platform
 - โ AWS Cognito
 - โ Custom OAuth servers
 
See templates/typescript-oauth/OAUTH_SETUP.md for provider-specific setup guides.
๐ Why NitroStack Makes OAuth Easy
Traditional Approach โ
Complexity:
- 500+ lines of boilerplate code
 - Manual JWKS fetching & caching
 - Token validation logic
 - Metadata endpoints
 - Audience/issuer validation
 - Scope checking
 - Error handling
 - Security pitfalls everywhere
 
Time: 2-3 days to implement correctly
NitroStack Approach โ
Simplicity:
OAuthModule.forRoot({ resourceUri, authorizationServers, scopesSupported });
@UseGuards(OAuthGuard)
Time: 5 minutes!
What You Get
โ Production-Grade - Battle-tested OAuth implementation โ Standards-Compliant - Follows all RFCs โ Secure by Default - No security pitfalls โ Zero Config - Sensible defaults, works out of the box โ Fully Tested - Comprehensive test suite โ Well Documented - Clear, actionable docs โ Active Maintenance - Regular updates
๐ Learn More
- OAuth 2.1 Template
 - Complete Setup Guide
 - Multi-Auth Patterns
 - Guards Reference
 - ExecutionContext Reference
 
Previous: โ API Key Authentication