/sdk
/typescript
/auth
/oauth

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:

  1. Create Auth0 Application (get Client ID/Secret)
  2. Create Auth0 API (set Identifier = your RESOURCE_URI)
  3. Add scopes: read, write, admin
  4. Configure .env with 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

  1. Open Studio at http://localhost:3000
  2. Auth โ†’ OAuth 2.1
  3. Discover: http://localhost:3002
  4. Enter Client ID/Secret
  5. Start OAuth Flow
  6. Login & Authorize
  7. โœ… 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?

  1. STDIO - Fast MCP protocol for tool calls
  2. HTTP - Standards-compliant OAuth metadata
  3. Best of Both - Performance + Compatibility
  4. 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:

  1. Auth โ†’ OAuth 2.1
  2. Discover server
  3. Enter credentials
  4. Start OAuth flow
  5. Login & authorize
  6. 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


Previous: โ† API Key Authentication