diff --git a/Directory.Packages.props b/Directory.Packages.props
index acdc0ee8..2244ada3 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -67,5 +67,9 @@
     <PackageVersion Include="xunit.v3" Version="2.0.1" />
     <PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
     <PackageVersion Include="System.Net.Http" Version="4.3.4" />
+	<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
+	<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.4" />
+	<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
+	<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.8.0" />
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/ModelContextProtocol.sln b/ModelContextProtocol.sln
index 0e4fd721..7ca668e7 100644
--- a/ModelContextProtocol.sln
+++ b/ModelContextProtocol.sln
@@ -56,6 +56,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.AspNet
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.AspNetCore.Tests", "tests\ModelContextProtocol.AspNetCore.Tests\ModelContextProtocol.AspNetCore.Tests.csproj", "{85557BA6-3D29-4C95-A646-2A972B1C2F25}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreSseServerAuthPrototype", "samples\AspNetCoreSseServerAuthPrototype\AspNetCoreSseServerAuthPrototype.csproj", "{DBBBB4F8-3513-4FA3-95EA-AF885C30F152}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -110,6 +112,10 @@ Global
 		{85557BA6-3D29-4C95-A646-2A972B1C2F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{85557BA6-3D29-4C95-A646-2A972B1C2F25}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{85557BA6-3D29-4C95-A646-2A972B1C2F25}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DBBBB4F8-3513-4FA3-95EA-AF885C30F152}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DBBBB4F8-3513-4FA3-95EA-AF885C30F152}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DBBBB4F8-3513-4FA3-95EA-AF885C30F152}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DBBBB4F8-3513-4FA3-95EA-AF885C30F152}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -128,6 +134,7 @@ Global
 		{17B8453F-AB72-99C5-E5EA-D0B065A6AE65} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 		{37B6A5E0-9995-497D-8B43-3BC6870CC716} = {A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD}
 		{85557BA6-3D29-4C95-A646-2A972B1C2F25} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928}
+		{DBBBB4F8-3513-4FA3-95EA-AF885C30F152} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89}
diff --git a/samples/AspNetCoreSseServerAuthPrototype/.gitignore b/samples/AspNetCoreSseServerAuthPrototype/.gitignore
new file mode 100644
index 00000000..b58b9e17
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/.gitignore
@@ -0,0 +1 @@
+/devkey.key
diff --git a/samples/AspNetCoreSseServerAuthPrototype/AspNetCoreSseServerAuthPrototype.csproj b/samples/AspNetCoreSseServerAuthPrototype/AspNetCoreSseServerAuthPrototype.csproj
new file mode 100644
index 00000000..03ebc365
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/AspNetCoreSseServerAuthPrototype.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+	<PropertyGroup>
+		<TargetFramework>net9.0</TargetFramework>
+		<Nullable>enable</Nullable>
+		<ImplicitUsings>enable</ImplicitUsings>
+	</PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
+		<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
+		<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
+		<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
+	</ItemGroup>
+
+	<ItemGroup>
+		<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
+		<ProjectReference Include="..\..\src\ModelContextProtocol.AspNetCore\ModelContextProtocol.AspNetCore.csproj" />
+	</ItemGroup>
+
+</Project>
diff --git a/samples/AspNetCoreSseServerAuthPrototype/OAuth.cs b/samples/AspNetCoreSseServerAuthPrototype/OAuth.cs
new file mode 100644
index 00000000..d308e3a9
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/OAuth.cs
@@ -0,0 +1,373 @@
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Tokens;
+using System.Diagnostics.CodeAnalysis;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Web;
+
+namespace AspNetCoreSseServerAuthPrototype
+{
+	/// <summary>
+	/// Experimental implementation of OAuth endpoints for MCP servers.
+	/// </summary>
+	public static class OAuth
+	{
+		public class Options
+		{
+			/// <summary>
+			/// The audience for the issued JWT token.
+			/// </summary>
+			public string? Audience { get; set; }
+		}
+
+		public interface IKeyProvider
+		{
+			/// <summary>
+			/// Get the signing key ti sign the issued JWT token.
+			/// </summary>
+			Task<SecurityKey> GetSigningKey();
+		}
+
+		public interface IClientRepository
+		{
+			/// <summary>
+			/// Remember a newly registered client.
+			/// </summary>
+			Task Register(string clientId, ClientRegistration clientRegistration);
+
+			/// <summary>
+			/// Recalla registered client.
+			/// </summary>
+			Task<ClientRegistration?> Get(string clientId);
+		}
+
+		public class ClientRegistration
+		{
+			[JsonPropertyName("redirect_uris")]
+			public string[] RedirectUris { get; set; } = null!;
+			[JsonPropertyName("grant_types")]
+			public string[] GrantTypes { get; set; } = null!;
+			[JsonPropertyName("response_types")]
+			public string[] ResponseTypes { get; set; } = null!;
+			[JsonPropertyName("client_name")]
+			public string ClientName { get; set; } = null!;
+			[JsonPropertyName("client_uri")]
+			public string ClientUri { get; set; } = null!;
+			[JsonPropertyName("token_endpoint_auth_method")]
+			public string TokenEndpointAuthMethod { get; set; } = null!;
+			[JsonPropertyName("client_secret")]
+			public string? ClientSecret { get; set; }
+			[JsonPropertyName("scopes")]
+			public string[]? Scopes { get; set; }
+		}
+
+		class AuthCode
+		{
+			public string UserId { get; set; } = null!;
+			public string? UserName { get; set; }
+			public string ClientId { get; set; } = null!;
+			public string[]? Scopes { get; set; }
+			public string RedirectUri { get; set; } = null!;
+			public string CodeChallenge { get; set; } = null!;
+			public string CodeChallengeMethod { get; set; } = null!;
+			public DateTime Expiry { get; set; }
+		}
+
+		/// <summary>
+		/// Register reguired services for OAuth endpoints.
+		/// </summary>
+		public static IServiceCollection AddOAuth<TSigningKeyProvider, TClientRepository>(this IServiceCollection services, Action<Options> optionsConfiguration)
+			where TSigningKeyProvider : class, IKeyProvider
+			where TClientRepository : class, IClientRepository
+		{
+			services.TryAddSingleton<IKeyProvider, TSigningKeyProvider>();
+			services.TryAddSingleton<IClientRepository, TClientRepository>();
+			services.Configure(optionsConfiguration);
+
+			return services;
+		}
+
+		/// <summary>
+		/// Map OAuth endpoints.
+		/// </summary>
+		public static IEndpointConventionBuilder MapOAuth(this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern = "")
+		{
+			var routePathPrefix = !string.IsNullOrEmpty(pattern) ? $"/{pattern}" : string.Empty;
+			var supported = new
+			{
+				ResponseTypesSupported = new[] { "code" },
+				ResponseModesSupported = new[] { "query" },
+				GrantTypesSupported = new[] { "authorization_code", "refresh_token" },
+				TokenEndpointAuthMethodsSupported = new[] { "client_secret_post", "none" },
+				CodeChallengeMethodsSupported = new[] { "S256" },
+			};
+
+			endpoints.MapGet("/.well-known/oauth-authorization-server", (HttpRequest request) =>
+			{
+				var iss = new Uri($"{request.Scheme}://{request.Host}").AbsoluteUri.TrimEnd('/');
+				return Results.Ok(new
+				{
+					issuer = iss,
+					authorization_endpoint = $"{iss}{routePathPrefix}/authorize",
+					token_endpoint = $"{iss}{routePathPrefix}/token",
+					registration_endpoint = $"{iss}{routePathPrefix}/register",
+					//revocation_endpoint = $"{iss}{pathPrefix}/token",
+					response_types_supported = supported.ResponseTypesSupported,
+					response_modes_supported = supported.ResponseModesSupported,
+					grant_types_supported = supported.GrantTypesSupported,
+					token_endpoint_auth_methods_supported = supported.TokenEndpointAuthMethodsSupported,
+					code_challenge_methods_supported = supported.CodeChallengeMethodsSupported,
+				});
+			});
+
+			var routeGroup = endpoints.MapGroup(pattern);
+
+			routeGroup.MapPost("/register", async ([FromBody] ClientRegistration clientRegistration, IClientRepository clientRepository) =>
+			{
+				var client_id = Guid.NewGuid().ToString();
+
+				if (string.IsNullOrEmpty(clientRegistration.ClientName))
+				{
+					return Results.BadRequest(new { error = "invalid_request", error_description = "Client name is required" });
+				}
+
+				if (clientRegistration.ResponseTypes.Intersect(supported.ResponseTypesSupported).Count() != clientRegistration.ResponseTypes.Length)
+				{
+					return Results.BadRequest(new { error = "invalid_request", error_description = "Invalid response types" });
+				}
+
+				if (clientRegistration.GrantTypes.Intersect(supported.GrantTypesSupported).Count() != clientRegistration.GrantTypes.Length)
+				{
+					return Results.BadRequest(new { error = "invalid_request", error_description = "Invalid grant types" });
+				}
+
+				if (!supported.TokenEndpointAuthMethodsSupported.Contains(clientRegistration.TokenEndpointAuthMethod))
+				{
+					return Results.BadRequest(new { error = "invalid_request", error_description = "Invalid token endpoint auth" });
+				}
+
+				await clientRepository.Register(client_id, clientRegistration);
+
+				var createdAt = $"{routePathPrefix}/register/{client_id}";
+				return Results.Created(createdAt, new
+				{
+					client_id,
+					redirect_uris = clientRegistration.RedirectUris,
+					client_name = clientRegistration.ClientName,
+					client_uri = clientRegistration.ClientUri,
+					grant_types = clientRegistration.GrantTypes,
+					response_types = clientRegistration.ResponseTypes,
+					token_endpoint_auth_method = clientRegistration.TokenEndpointAuthMethod,
+					registration_client_uri = createdAt,
+					client_id_issued_at = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
+				});
+			});
+
+			routeGroup.MapGet("/authorize", async (
+				HttpRequest request,
+				IDataProtectionProvider dataProtectionProvider,
+				IOptions<Options> options,
+				IClientRepository clientRepository) =>
+			{
+				var iss = new Uri($"{request.Scheme}://{request.Host}").AbsoluteUri.TrimEnd('/');
+				request.Query.TryGetValue("state", out var state);
+
+				if (!request.Query.TryGetValue("client_id", out var clientId))
+				{
+					return Results.BadRequest(new { error = "unauthorized_client", state, iss, });
+				}
+
+				var client = await clientRepository.Get(clientId.ToString());
+				if (client is null)
+				{
+					return Results.BadRequest(new { error = "unauthorized_client", state, iss, });
+				}
+
+				if (!request.Query.TryGetValue("response_type", out var responseType) || !client.ResponseTypes.Contains(responseType.ToString()))
+				{
+					return Results.BadRequest(new { error = "invalid_request", state, iss, });
+				}
+
+				request.Query.TryGetValue("code_challenge", out var codeChallenge);
+
+				if (!request.Query.TryGetValue("code_challenge_method", out var codeChallengeMethod) || !supported.CodeChallengeMethodsSupported.Contains(codeChallengeMethod.ToString()))
+				{
+					return Results.BadRequest(new { error = "invalid_request", state, iss, });
+				}
+
+				request.Query.TryGetValue("redirect_uri", out var redirectUri);
+
+				var requestScopes = default(string[]?);
+				if (request.Query.TryGetValue("scope", out var scope))
+				{
+					var scopes = scope.ToString().Split(' ', StringSplitOptions.RemoveEmptyEntries);
+					if (scopes.Intersect(client.Scopes ?? []).Count() != scopes.Length)
+					{
+						return Results.BadRequest(new { error = "invalid_scope", state, iss, });
+					}
+
+					var userScopes = request.HttpContext.User.Claims
+						.Where(c => c.Type == "scope")
+						.Select(c => c.Value)
+						.ToList();
+
+					requestScopes = [.. scopes.Where(userScopes.Contains)];
+				}
+
+				var protector = dataProtectionProvider.CreateProtector("oauth");
+				var authCode = new AuthCode
+				{
+					UserId = request.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)!,
+					UserName = request.HttpContext.User.FindFirstValue("name"),
+					ClientId = clientId!,
+					Scopes = requestScopes,
+					RedirectUri = redirectUri!,
+					CodeChallenge = codeChallenge!,
+					CodeChallengeMethod = codeChallengeMethod!,
+					Expiry = DateTime.UtcNow.AddMinutes(5)
+				};
+				var code = protector.Protect(JsonSerializer.Serialize(authCode));
+				return Results.Redirect($"{redirectUri}?code={code}&state={state}&iss={HttpUtility.UrlEncode(iss)}");
+			}).RequireAuthorization();
+
+			routeGroup.MapPost("/token", async (
+				HttpRequest request,
+				IDataProtectionProvider dataProtectionProvider,
+				IOptions<Options> options,
+				IClientRepository clientRepository,
+				IKeyProvider keyProvider) =>
+			{
+				var bodyBytes = await request.BodyReader.ReadAsync();
+				var bodyContent = Encoding.UTF8.GetString(bodyBytes.Buffer);
+				request.BodyReader.AdvanceTo(bodyBytes.Buffer.End);
+
+				string grantType = "", code = "", redirectUri = "", codeVerifier = "", clientId = "", clientSecret = "", refreshToken = "";
+				foreach (var part in bodyContent.Split('&'))
+				{
+					var subParts = part.Split('=');
+					var key = subParts[0];
+					var value = subParts[1];
+					if (key == "grant_type") grantType = value;
+					else if (key == "code") code = value;
+					else if (key == "redirect_uri") redirectUri = HttpUtility.UrlDecode(value);
+					else if (key == "code_verifier") codeVerifier = HttpUtility.UrlDecode(value);
+					else if (key == "client_id") clientId = value;
+					else if (key == "client_secret") clientSecret = value;
+					else if (key == "refresh_token") refreshToken = value;
+				}
+
+				var client = await clientRepository.Get(clientId);
+				if (client is null)
+				{
+					return Results.BadRequest(new { error = "invalid_client", error_description = "Invalid client id" });
+				}
+
+				if (client.TokenEndpointAuthMethod == "client_secret_post")
+				{
+					if (clientSecret != client.ClientSecret)
+					{
+						return Results.BadRequest(new { error = "invalid_client", error_description = "Invalid client secret" });
+					}
+				}
+				else if (client.TokenEndpointAuthMethod == "none")
+				{
+					if (!string.IsNullOrEmpty(clientSecret))
+					{
+						return Results.BadRequest(new { error = "invalid_client", error_description = "Client secret not allowed" });
+					}
+				}
+				else
+				{
+					return Results.BadRequest(new { error = "invalid_client", error_description = "Invalid client auth method" });
+				}
+
+
+				if (string.IsNullOrEmpty(grantType) || !client.GrantTypes.Contains(grantType))
+				{
+					return Results.BadRequest(new { error = "invalid_grant", error_description = "Invalid grant type" });
+				}
+
+				string userId;
+				string userName;
+				string[] scopes;
+				if (grantType == "authorization_code")
+				{
+					if (code == string.Empty)
+					{
+						return Results.BadRequest(new { error = "invalid_grant", error_description = "Authorization code missing" });
+					}
+
+					var protector = dataProtectionProvider.CreateProtector("oauth");
+					var codeString = protector.Unprotect(code);
+					var authCode = JsonSerializer.Deserialize<AuthCode>(codeString);
+
+					if (authCode == null)
+					{
+						return Results.BadRequest(new { error = "invalid_grant", error_description = "Authorization code missing" });
+					}
+
+					if (authCode.Expiry < DateTime.UtcNow)
+					{
+						return Results.BadRequest(new { error = "invalid_grant", error_description = "Authorization code expired" });
+					}
+
+					if (authCode.RedirectUri != redirectUri)
+					{
+						return Results.BadRequest(new { error = "invalid_grant", error_description = "Invalid redirect uri" });
+					}
+
+					using var sha256 = SHA256.Create();
+					var codeChallenge = Base64UrlEncoder.Encode(sha256.ComputeHash(Encoding.ASCII.GetBytes(codeVerifier)));
+					if (authCode.CodeChallenge != codeChallenge)
+					{
+						return Results.BadRequest(new { error = "invalid_grant", error_description = "Invalid code verifier" });
+					}
+
+					userId = authCode.UserId;
+					userName = authCode.UserName ?? string.Empty;
+					scopes = authCode.Scopes ?? [];
+				}
+				else if (grantType == "refresh_token")
+				{
+					throw new NotImplementedException();
+				}
+				else
+				{
+					return Results.BadRequest(new { error = "invalid_grant", error_description = "Invalid grant type" });
+				}
+
+				var handler = new JsonWebTokenHandler();
+				var iss = new Uri($"{request.Scheme}://{request.Host}").AbsoluteUri.TrimEnd('/');
+				var accessToken = handler.CreateToken(new SecurityTokenDescriptor
+				{
+					Issuer = iss,
+					Subject = new ClaimsIdentity(
+					[
+						new Claim("sub", userId),
+						new Claim("name", userName),
+						..scopes.Select(s => new Claim("scope", s)),
+					]),
+					Audience = options.Value.Audience,
+					Expires = DateTime.UtcNow.AddMinutes(5),
+					TokenType = "Bearer",
+					SigningCredentials = new SigningCredentials(await keyProvider.GetSigningKey(), SecurityAlgorithms.RsaSha256),
+				});
+				return Results.Ok(new
+				{
+					access_token = accessToken,
+					token_type = "Bearer",
+					//refresh_token = "",
+				});
+			});
+
+			return routeGroup;
+		}
+	}
+}
\ No newline at end of file
diff --git a/samples/AspNetCoreSseServerAuthPrototype/Program.cs b/samples/AspNetCoreSseServerAuthPrototype/Program.cs
new file mode 100644
index 00000000..e78fcb8b
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/Program.cs
@@ -0,0 +1,191 @@
+using AspNetCoreSseServerAuthPrototype;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Authentication.OpenIdConnect;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.Tokens;
+using System.Collections.Concurrent;
+using System.Security.Claims;
+using System.Security.Cryptography;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddMcpServer().WithToolsFromAssembly();
+
+builder.Services.AddCors(options =>
+{
+	options.AddDefaultPolicy(policy =>
+	{
+		policy
+		.AllowAnyOrigin()
+		.AllowAnyMethod()
+		.AllowAnyHeader();
+	});
+});
+
+builder.Services.AddAuthentication(config =>
+{
+	config.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+	config.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+	config.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
+})
+	//This is the cookie we are going to use for the OIDC authentication flow.
+	.AddCookie(options =>
+	{
+		options.Cookie.Name = "MyMCPServer.Sse.Session";
+	})
+	//This is the remote OIDC authentication the MCP server should use.
+	.AddOpenIdConnect(o =>
+	{
+		o.Authority = "https://localhost:5001";
+		o.ClientId = "mcp_server";
+		o.ClientSecret = "secret";
+		o.ResponseType = OpenIdConnectResponseType.Code;
+		o.Scope.Add("openid");
+		o.Scope.Add("profile");
+		o.Scope.Add("api");
+		o.SaveTokens = true;
+		o.Events.OnTicketReceived = ctx =>
+		{
+			// Add the access token claims to the cookie
+			var accesstoken = ctx.Properties?.GetTokenValue("access_token");
+			var handler = new JsonWebTokenHandler();
+			var token = handler.ReadJsonWebToken(accesstoken);
+			ctx.Principal!.AddIdentity(new ClaimsIdentity(token.Claims, OpenIdConnectDefaults.AuthenticationScheme));
+
+			// Remove the access token from the cookie
+			ctx.Properties?.GetTokens().ToList().ForEach(token => ctx.Properties.UpdateTokenValue(token.Name, string.Empty));
+
+			return Task.CompletedTask;
+		};
+	})
+	//This is the bearer authentication we are going to require for the MCP endpoints.
+	.AddJwtBearer(options =>
+	{
+		options.TokenValidationParameters = new TokenValidationParameters
+		{
+			ValidateIssuer = false,//We dont want to provide a valid issuer URL here, which is changing based on local config. Validating the issuer signing key is enough.
+			ValidateAudience = true,
+			ValidateLifetime = true,
+			ValidateIssuerSigningKey = true,
+			ValidAudience = "mcp_server",
+		};
+	});
+
+builder.Services.AddAuthorization(options =>
+{
+	//With this policy we are going to require bearer authentication for the MCP endpoints.
+	options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
+	{
+		policy.AuthenticationSchemes = [JwtBearerDefaults.AuthenticationScheme];
+		policy.RequireAuthenticatedUser();
+	});
+});
+
+//Lets configure MCP OAuth by providing the token signing key, a client repository for dynamic registration, and the JWT audience.
+builder.Services.AddOAuth<SigningKey, ClientRespository>(options =>
+{
+	options.Audience = "mcp_server";
+});
+
+//We need to configure the signing key for the JWT bearer authentication.
+builder.Services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSigningKeyConfiguration>();
+
+
+
+
+
+
+
+var app = builder.Build();
+
+app.UseCors();
+
+app.UseAuthentication();
+app.UseAuthorization();
+
+app.MapGet("/", () => "Hello MCP!");
+app.MapMcp().RequireAuthorization(JwtBearerDefaults.AuthenticationScheme);
+app.MapOAuth();
+
+app.Run();
+
+
+
+
+
+
+
+
+class ClientRespository : OAuth.IClientRepository
+{
+	readonly ConcurrentDictionary<string, OAuth.ClientRegistration> clientRegistrations;
+
+	public ClientRespository()
+	{
+		clientRegistrations = new ConcurrentDictionary<string, OAuth.ClientRegistration>(StringComparer.Ordinal);
+	}
+
+	public Task<OAuth.ClientRegistration?> Get(string clientId)
+	{
+		return clientRegistrations.TryGetValue(clientId, out var clientRegistration)
+			? Task.FromResult<OAuth.ClientRegistration?>(clientRegistration)
+			: Task.FromResult<OAuth.ClientRegistration?>(null);
+	}
+
+	public Task Register(string clientId, OAuth.ClientRegistration clientRegistration)
+	{
+		clientRegistrations.AddOrUpdate(clientId, clientRegistration, (key, oldValue) => clientRegistration);
+		return Task.CompletedTask;
+	}
+}
+
+class SigningKey : OAuth.IKeyProvider
+{
+	public SigningKey(IWebHostEnvironment env)
+	{
+		var rsaKey = RSA.Create();
+		var path = Path.Combine(env.ContentRootPath, "devkey.key");
+		if (File.Exists(path))
+		{
+			rsaKey.ImportRSAPrivateKey(File.ReadAllBytes(path), out _);
+		}
+		else
+		{
+			var privateKey = rsaKey.ExportRSAPrivateKey();
+			File.WriteAllBytes(path, privateKey);
+		}
+
+		this.SecurityKey = new RsaSecurityKey(rsaKey);
+	}
+
+	public SecurityKey SecurityKey { get; }
+
+	public Task<SecurityKey> GetSigningKey()
+	{
+		return Task.FromResult(this.SecurityKey);
+	}
+
+	public void PostConfigure(string? name, JwtBearerOptions options)
+	{
+		options.TokenValidationParameters.IssuerSigningKey = SecurityKey;
+	}
+}
+
+class JwtBearerOptionsSigningKeyConfiguration : IPostConfigureOptions<JwtBearerOptions>
+{
+	private readonly OAuth.IKeyProvider keyProvider;
+
+	public JwtBearerOptionsSigningKeyConfiguration(OAuth.IKeyProvider keyProvider)
+	{
+		this.keyProvider = keyProvider;
+	}
+
+	public void PostConfigure(string? name, JwtBearerOptions options)
+	{
+		options.TokenValidationParameters.IssuerSigningKey = keyProvider.GetSigningKey().Result;
+	}
+}
\ No newline at end of file
diff --git a/samples/AspNetCoreSseServerAuthPrototype/Properties/launchSettings.json b/samples/AspNetCoreSseServerAuthPrototype/Properties/launchSettings.json
new file mode 100644
index 00000000..5efd4e62
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": false,
+      "applicationUrl": "http://localhost:5235",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "https": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": false,
+      "applicationUrl": "https://localhost:7214;http://localhost:5235",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}
diff --git a/samples/AspNetCoreSseServerAuthPrototype/README.md b/samples/AspNetCoreSseServerAuthPrototype/README.md
new file mode 100644
index 00000000..665dc10b
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/README.md
@@ -0,0 +1,18 @@
+# AspNetCoreSseServerAuthPrototype
+
+## Usage
+
+Start the inspector.
+
+```powershell
+npx @modelcontextprotocol/inspector
+```
+
+Run the web app and copy the non-https address (<http://localhost:5235>) into the inspectors URL field. Make sure to keep the `/sse` path.
+
+Let the inspector connect.
+
+## Limitations
+
+- Not 100% OAuth compatible yet.
+- No support for refresh tokens yet.
diff --git a/samples/AspNetCoreSseServerAuthPrototype/VibeTool.cs b/samples/AspNetCoreSseServerAuthPrototype/VibeTool.cs
new file mode 100644
index 00000000..066dfbd2
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/VibeTool.cs
@@ -0,0 +1,23 @@
+using ModelContextProtocol.Server;
+using System.ComponentModel;
+
+namespace AspNetCoreSseServerAuthPrototype
+{
+	[McpServerToolType]
+	public class VibeTool
+	{
+		private readonly ILogger<VibeTool> logger;
+
+		public VibeTool(ILogger<VibeTool> logger)
+		{
+			this.logger = logger;
+		}
+
+		[McpServerTool, Description("Gets the vibe in the provided location.")]
+		public string GetVibe(string location)
+		{
+			this.logger.LogInformation("Getting vibe in {location}.", location);
+			return $"Curious vibes in {location}.";
+		}
+	}
+}
diff --git a/samples/AspNetCoreSseServerAuthPrototype/appsettings.Development.json b/samples/AspNetCoreSseServerAuthPrototype/appsettings.Development.json
new file mode 100644
index 00000000..0c208ae9
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}
diff --git a/samples/AspNetCoreSseServerAuthPrototype/appsettings.json b/samples/AspNetCoreSseServerAuthPrototype/appsettings.json
new file mode 100644
index 00000000..10f68b8c
--- /dev/null
+++ b/samples/AspNetCoreSseServerAuthPrototype/appsettings.json
@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}