This document explains the authentication options available in the MCP Calculator Web server.
The server supports four authentication types, configurable via appsettings.json:
| Type | Header | Use Case | Security Level |
|---|---|---|---|
| None | - | Development only | None |
| ApiKey | X-API-Key |
Internal services | Basic |
| Jwt | Authorization: Bearer |
Web/API clients | Standard |
| Windows | (Automatic) | IIS intranet | Enterprise |
Set the authentication type in appsettings.json:
{
"Authentication": {
"Type": "ApiKey"
}
}ASP.NET Core supports multiple configuration sources, loaded in order of precedence (later sources override earlier ones):
| Source | Precedence | Use Case |
|---|---|---|
appsettings.json |
1 (lowest) | Default/template values |
appsettings.{Environment}.json |
2 | Environment-specific overrides |
| User Secrets | 3 | Development secrets (local only) |
| Environment Variables | 4 | Production secrets, containers |
| Azure Key Vault | 5 (highest) | Enterprise secret management |
User Secrets store sensitive configuration outside your project directory, preventing accidental commits.
Initialize user secrets:
cd McpCalculator.Web
dotnet user-secrets initSet API keys:
dotnet user-secrets set "Authentication:ApiKey:ValidKeys:0" "dev-api-key-12345"
dotnet user-secrets set "Authentication:ApiKey:ValidKeys:1" "dev-api-key-67890"Set JWT secret:
dotnet user-secrets set "Authentication:Jwt:SecretKey" "your-dev-jwt-secret-key-at-least-32-chars"View all secrets:
dotnet user-secrets listRemove a secret:
dotnet user-secrets remove "Authentication:ApiKey:ValidKeys:0"Note: User secrets are only loaded when
ASPNETCORE_ENVIRONMENT=Development. They are stored in:
- Windows:
%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json- macOS/Linux:
~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
Environment variables are ideal for production deployments, containers, and CI/CD pipelines.
Use __ (double underscore) as the hierarchy separator:
Windows (PowerShell):
$env:Authentication__ApiKey__ValidKeys__0 = "prod-api-key-12345"
$env:Authentication__ApiKey__ValidKeys__1 = "prod-api-key-67890"
$env:Authentication__Jwt__SecretKey = "your-prod-jwt-secret-key-at-least-32-chars"Windows (Command Prompt):
set Authentication__ApiKey__ValidKeys__0=prod-api-key-12345
set Authentication__ApiKey__ValidKeys__1=prod-api-key-67890Linux/macOS:
export Authentication__ApiKey__ValidKeys__0="prod-api-key-12345"
export Authentication__ApiKey__ValidKeys__1="prod-api-key-67890"
export Authentication__Jwt__SecretKey="your-prod-jwt-secret-key-at-least-32-chars"Docker:
ENV Authentication__ApiKey__ValidKeys__0=prod-api-key-12345Docker Compose:
services:
mcp-calculator:
environment:
- Authentication__ApiKey__ValidKeys__0=prod-api-key-12345
- Authentication__Jwt__SecretKey=your-prod-jwt-secretFor enterprise deployments, Azure Key Vault provides centralized secret management with auditing, rotation, and access policies.
1. Add the NuGet package:
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity2. Configure in Program.cs:
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
// Add Azure Key Vault as a configuration source
var keyVaultUrl = builder.Configuration["KeyVault:Url"];
if (!string.IsNullOrEmpty(keyVaultUrl))
{
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUrl),
new DefaultAzureCredential());
}3. Store secrets in Key Vault:
Key Vault secret names use -- instead of : for hierarchy:
Authentication--ApiKey--ValidKeys--0Authentication--Jwt--SecretKey
Use Case: Local development and testing only.
{
"Authentication": {
"Type": "None"
}
}Behavior:
- All requests are allowed without authentication
- A placeholder identity is created for authorization middleware compatibility
Example Request:
curl -X POST https://localhost:5001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'WARNING: Never use
Type: Nonein production. It allows anyone to access your server.
Use Case: Simple internal services, server-to-server communication.
appsettings.json (template only, no secrets):
{
"Authentication": {
"Type": "ApiKey",
"ApiKey": {
"HeaderName": "X-API-Key"
}
}
}Add keys via User Secrets (development):
dotnet user-secrets set "Authentication:ApiKey:ValidKeys:0" "your-api-key-here"
dotnet user-secrets set "Authentication:ApiKey:ValidKeys:1" "another-valid-key"Or via Environment Variables (production):
export Authentication__ApiKey__ValidKeys__0="your-api-key-here"
export Authentication__ApiKey__ValidKeys__1="another-valid-key"| Setting | Description | Default |
|---|---|---|
HeaderName |
HTTP header name for the API key | X-API-Key |
ValidKeys |
Array of valid API keys (use secrets, not appsettings!) | Empty array |
- Client includes API key in request header
- Server validates key against
ValidKeysarray - Uses constant-time comparison to prevent timing attacks
- Returns 401 if key is missing or invalid
curl -X POST https://localhost:5001/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key-here" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'- Use HTTPS: API keys sent over HTTP can be intercepted
- Rotate keys periodically: Change keys every 30-90 days
- Use unique keys per client: Easier to revoke compromised keys
- Store keys securely: Use environment variables or secret managers in production
Use a cryptographically secure random generator:
# PowerShell
[Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))# Bash
openssl rand -base64 32Use Case: Standard web API authentication, external clients, OAuth integration.
appsettings.json (template only, no secrets):
{
"Authentication": {
"Type": "Jwt",
"Jwt": {
"Issuer": "McpCalculator",
"Audience": "McpCalculatorClients"
}
}
}Add secret key via User Secrets (development):
dotnet user-secrets set "Authentication:Jwt:SecretKey" "your-secret-key-must-be-at-least-32-characters-long"Or via Environment Variables (production):
export Authentication__Jwt__SecretKey="your-secret-key-must-be-at-least-32-characters-long"| Setting | Description | Requirement |
|---|---|---|
Issuer |
Token issuer identifier | Required |
Audience |
Intended audience | Required |
SecretKey |
HMAC signing key (use secrets, not appsettings!) | Min 32 characters |
- Client obtains JWT from identity provider (or generates it)
- Client includes JWT in
Authorizationheader - Server validates signature, issuer, audience, and expiration
- Returns 401 if token is invalid or expired
curl -X POST https://localhost:5001/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'For testing, you can generate a JWT using various tools:
Using jwt.io:
- Go to https://jwt.io/
- Select HS256 algorithm
- Set payload with
iss,aud, andexpclaims - Enter your secret key
- Copy the generated token
JWT Payload Example:
{
"sub": "test-client",
"iss": "McpCalculator",
"aud": "McpCalculatorClients",
"exp": 1735689600,
"iat": 1704067200
}Using C#:
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("your-secret-key-must-be-at-least-32-characters-long"));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "McpCalculator",
audience: "McpCalculatorClients",
claims: new[] { new Claim("sub", "client-id") },
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: credentials);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);- Use strong secret keys: At least 32 bytes (256 bits) of entropy
- Set appropriate expiration: Balance security vs. user experience
- Use HTTPS: Tokens can be stolen if intercepted
- Consider RS256: For production, asymmetric keys (RS256) are more secure than HMAC (HS256)
Use Case: IIS-hosted intranet applications with Active Directory.
{
"Authentication": {
"Type": "Windows"
}
}No additional configuration is needed - IIS handles authentication automatically.
-
Enable Windows Authentication in IIS:
- Open IIS Manager
- Select your site
- Open Authentication
- Enable "Windows Authentication"
- Disable "Anonymous Authentication"
-
Install the Negotiate module (if not present):
- Windows Features → Internet Information Services
- → Web Management Tools → IIS 6 Management Compatibility
- → Windows Authentication
- Client (browser/app) sends request
- IIS challenges client for Windows credentials
- Client responds with Kerberos ticket or NTLM hash
- IIS validates credentials against Active Directory
- User identity is passed to ASP.NET Core
Browsers automatically handle Windows authentication when:
- User is on the same domain
- Site is in the Intranet zone (IE/Edge)
- Credentials are available
Invoke-RestMethod -Uri "http://intranet-server/mcp" `
-Method POST `
-UseDefaultCredentials `
-ContentType "application/json" `
-Body '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'In the server code, access the authenticated user:
var username = context.User.Identity?.Name; // DOMAIN\usernameUse different configurations for different environments:
appsettings.json (Base configuration):
{
"Authentication": {
"Type": "ApiKey",
"ApiKey": {
"HeaderName": "X-API-Key"
}
}
}appsettings.Development.json (Development - no auth for convenience):
{
"Authentication": {
"Type": "None"
}
}Production secrets via environment variables:
export Authentication__ApiKey__ValidKeys__0="production-key-1"
export Authentication__ApiKey__ValidKeys__1="production-key-2"The configuration is loaded based on the ASPNETCORE_ENVIRONMENT variable, with secrets from environment variables or Key Vault.
Returned when authentication fails:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.2",
"title": "Unauthorized",
"status": 401
}| Auth Type | WWW-Authenticate Header |
|---|---|
| ApiKey | ApiKey realm="McpCalculator", header="X-API-Key" |
| Jwt | Bearer error="invalid_token" |
| Windows | Negotiate |
- Always use HTTPS in production - All auth methods transmit credentials
- Use environment-specific config - Different auth for dev/staging/prod
- Log authentication failures - Monitor for brute force attempts
- Implement rate limiting - Prevent credential stuffing attacks
- Rotate secrets regularly - API keys and JWT secrets should be rotated
- Use secret management - Azure Key Vault, AWS Secrets Manager, etc.
| Feature | None | ApiKey | JWT | Windows |
|---|---|---|---|---|
| Setup Complexity | None | Low | Medium | Medium |
| External Clients | Yes | Yes | Yes | No |
| Token Expiration | N/A | Manual | Built-in | N/A |
| Identity Claims | No | Limited | Full | Full |
| Suitable for Production | No | Yes | Yes | Yes |
| Requires IIS | No | No | No | Yes |