Skip to content
Merged
2 changes: 1 addition & 1 deletion docs/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ public class SomeController : Controller
}
```

Notice that since there's a `Controller.Context` property, they can pass that through, but it generally looks the same. Using implicit conversions, the `Microsoft.AspNetCore.Http.HttpContext` can be converted into the adapter that could then be passed around through the levels utilizing the code in the same way.
Notice that since there's a `Controller.Context` property, they can pass that through, but it generally looks the same. Using implicit conversions, the `Microsoft.AspNetCore.Http.HttpContext` can be converted into the adapter that could then be passed around through the levels utilizing the code in the same way.
1 change: 0 additions & 1 deletion docs/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,3 @@ app.Run();
```

This opts into all the behavior (described below), as well as sets up a remote session state (see [here](session-state/session.md) for details).

17 changes: 14 additions & 3 deletions docs/remote-authentication/remote-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ SystemWebAdapterConfiguration.AddSystemWebAdapters(this)
.AddProxySupport(options => options.UseForwardedHeaders = true)
.AddRemoteApp(options =>
{
options.ApiKey = "MySecretKey";
// ApiKey is a string representing a GUID
options.ApiKey = "00000000-0000-0000-0000-000000000000";
})
.AddRemoteAppAuthentication();
```

In the options configuration method passed to the `AddRemoteApp` call, you must specify an API key which is used to secure the endpoint so that only trusted callers can make requests to it (this same API key will be provided to the ASP.NET Core app when it is configured).
In the options configuration method passed to the `AddRemoteApp` call, you must specify an API key which is used to secure the endpoint so that only trusted callers can make requests to it (this same API key will be provided to the ASP.NET Core app when it is configured). The API key is a string and must be parsable as a GUID (128-bit hex number). Hyphens in the key are optional.

### ASP.NET Core app configuration

Expand All @@ -31,7 +32,9 @@ builder.Services.AddSystemWebAdapters()
.AddRemoteApp(options =>
{
options.RemoteAppUrl = new(builder.Configuration["http://URL-for-the-ASPNet-app"]);
options.ApiKey = "MySecretKey";

// ApiKey is a string representing a GUID
options.ApiKey = "00000000-0000-0000-0000-000000000000";
})
.AddRemoteAppAuthentication(true);
```
Expand All @@ -52,6 +55,14 @@ Finally, if the ASP.NET Core app didn't previously include authentication middle
app.UseAuthentication();
```

## Securing the remote app connection

Because remote app authentication involves serving requests on a new endpoint from the ASP.NET app, it's important that communication to and from the ASP.NET app be secure.

First, make sure that the API key string used to authenticate the ASP.NET Core app with the ASP.NET app is unique and kept secret. It is a best practice to not store the API key in source control. Instead, load it at runtime from a secure source such as Azure Key Vault or other secure runtime configuration. In order to encourage secure API keys, remote app connections require that the keys be non-empty GUIDs (128-bit hex numbers).

Second, because it's important for the ASP.NET Core app to be able to trust that it is requesting identity information from the correct ASP.NET app, the ASP.NET app should use HTTPS in any production scenarios so that the ASP.NET Core app can know identity is being served by a trusted source.

## Design

1. When requests are processed by the ASP.NET Core app, if remote app authentication is the default scheme or specified by the request's endpoint, the `RemoteAuthenticationAuthHandler` will attempt to authenticate the user.
Expand Down
15 changes: 13 additions & 2 deletions docs/session-state/remote-session.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ builder.Services.AddSystemWebAdapters()
options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]);

// Provide a strong API key that will be used to authenticate the request on the remote app for querying the session
options.ApiKey = "strong-api-key";
// ApiKey is a string representing a GUID
options.ApiKey = "00000000-0000-0000-0000-000000000000";
})
.AddRemoteAppSession()
.AddJsonSessionSerializer(options =>
Expand Down Expand Up @@ -66,7 +67,8 @@ The framework equivalent would look like the following change in `Global.asax.cs
```csharp
SystemWebAdapterConfiguration.AddSystemWebAdapters(this)
// Provide a strong API key that will be used to authenticate the request on the remote app for querying the session
.AddRemoteApp(options => options.ApiKey = "strong-api-key")
// ApiKey is a string representing a GUID
.AddRemoteApp(options => options.ApiKey = "00000000-0000-0000-0000-000000000000")
.AddRemoteAppSession()
.AddJsonSessionSerializer(options =>
{
Expand All @@ -75,6 +77,15 @@ SystemWebAdapterConfiguration.AddSystemWebAdapters(this)
options.RegisterKey<SessionDemoModel>("SampleSessionItem");
});
```

## Securing the remote app connection

Because remote session involves serving requests on a new endpoint from the ASP.NET app, it's important that communication to and from the ASP.NET app be secure.

First, make sure that the API key string used to authenticate the ASP.NET Core app with the ASP.NET app is unique and kept secret. It is a best practice to not store the API key in source control. Instead, load it at runtime from a secure source such as Azure Key Vault or other secure runtime configuration. In order to encourage secure API keys, remote app connections require that the keys be non-empty GUIDs (128-bit hex numbers).

Second, because it's important for the ASP.NET Core app to be able to trust that it is requesting session information from the correct ASP.NET app, the ASP.NET app should use HTTPS in any production scenarios so that the ASP.NET Core app can know responses are being served by a trusted source.

# Protocol

## Readonly
Expand Down
3 changes: 2 additions & 1 deletion samples/ClassLibrary/RemoteServiceUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ namespace ClassLibrary;

public class RemoteServiceUtils
{
public static string ApiKey = "test-key";
// Do not re-use this ApiKey; every solution should use a unique ApiKey
public static string ApiKey = "54b69938-90dd-4f79-adcd-27fbd6f0e4b7";

public static void RegisterSessionKeys(IDictionary<string, Type> knownTypes)
{
Expand Down
3 changes: 2 additions & 1 deletion samples/RemoteAuth/Bearer/RemoteBearer/Global.asax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ protected void Application_Start()
{
// A real application would not hard code this, but load it
// securely from environment or configuration
options.ApiKey = "TopSecretString";
// Do not re-use this ApiKey; every solution should use a unique ApiKey
options.ApiKey = "40c807bd-6c00-4e5a-9650-ea20c2e6c02d";
})
.AddAuthentication());
}
Expand Down
3 changes: 2 additions & 1 deletion samples/RemoteAuth/Bearer/RemoteBearerCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

// A real application would not hard code this, but load it
// securely from environment or configuration
options.ApiKey = "TopSecretString";
// Do not re-use this ApiKey; every solution should use a unique ApiKey
options.ApiKey = "40c807bd-6c00-4e5a-9650-ea20c2e6c02d";
})

// This registers the remote app authentication handler. The boolean argument indicates whether remote app auth
Expand Down
3 changes: 2 additions & 1 deletion samples/RemoteAuth/Forms/FormsAuth/Global.asax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ void Application_Start(object sender, EventArgs e)
.AddRemoteAppServer(remote => remote
.Configure(options =>
{
options.ApiKey = "FormsAuthSampleKey";
// Do not re-use this ApiKey; every solution should use a unique ApiKey
options.ApiKey = "8e470586-24e5-4f2a-8245-69bbdbf9f767";
})
.AddAuthentication());
}
Expand Down
4 changes: 3 additions & 1 deletion samples/RemoteAuth/Forms/FormsAuthCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
.Configure(options =>
{
options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]);
options.ApiKey = "FormsAuthSampleKey";

// Do not re-use this ApiKey; every solution should use a unique ApiKey
options.ApiKey = "8e470586-24e5-4f2a-8245-69bbdbf9f767";
})
.AddAuthentication(true));

Expand Down
3 changes: 2 additions & 1 deletion samples/RemoteAuth/OIDC/OIDCAuth/Global.asax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ protected void Application_Start()
SystemWebAdapterConfiguration.AddSystemWebAdapters(this)
.AddProxySupport(options => options.UseForwardedHeaders = true)
.AddRemoteAppServer(remote => remote
.Configure(options => options.ApiKey = "test-key")
// Do not re-use this ApiKey; every solution should use a unique ApiKey
.Configure(options => options.ApiKey = "121257f2-c121-4f51-b30c-d1f617933290")
.AddAuthentication());

}
Expand Down
3 changes: 2 additions & 1 deletion samples/RemoteAuth/OIDC/OIDCAuthCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
.Configure(options =>
{
options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]);
options.ApiKey = "test-key";
// Do not re-use this ApiKey; every solution should use a unique ApiKey
options.ApiKey = "121257f2-c121-4f51-b30c-d1f617933290";
})
.AddAuthentication(true));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public static ISystemWebAdapterBuilder AddRemoteAppClient(this ISystemWebAdapter
}

builder.Services.AddOptions<RemoteAppOptions>()
.ValidateDataAnnotations();
.ValidateDataAnnotations()
.Validate(RemoteAppOptions.Validate, "ApiKey must be a non-empty GUID");

configure(new Builder(builder.Services));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public static ISystemWebAdapterBuilder AddRemoteAppServer(this ISystemWebAdapter
}

var options = builder.Services.AddOptions<RemoteAppOptions>()
.Validate(options => !string.IsNullOrEmpty(options.ApiKey), "ApiKey must be set")
.Validate(options => !string.IsNullOrEmpty(options.ApiKeyHeader), "ApiKeyHeader must be set");
.Validate(options => !string.IsNullOrEmpty(options.ApiKeyHeader), "ApiKeyHeader must be set")
.Validate(RemoteAppOptions.Validate, "ApiKey must be a non-empty GUID");

configure(new Builder(builder.Services));

Expand Down
7 changes: 6 additions & 1 deletion src/Services/RemoteAppOptions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
#if NET6_0_OR_GREATER
using System;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
#endif
Expand Down Expand Up @@ -45,4 +45,9 @@ public class RemoteAppOptions
/// </summary>
public HttpClient? BackchannelHttpClient { get; set; }
#endif

// To ensure secure API keys are used, enforce that
// keys are parsable as GUIDs
public static bool Validate(RemoteAppOptions options) =>
Guid.TryParse(options.ApiKey, out var keyGuid) && keyGuid != Guid.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ namespace Microsoft.AspNetCore.SystemWebAdapters.Tests;

public class RemoteAppOptionsTests
{
[InlineData("HeaderName", "MyKey", true)]
[InlineData("HeaderName", "36705d36-eba0-44f9-a2e0-c57e6f521274", true)]
[InlineData(null, "36705d36-eba0-44f9-a2e0-c57e6f521274", false)]
[InlineData("", "36705d36-eba0-44f9-a2e0-c57e6f521274", false)]
[InlineData("HeaderName", "MyKey", false)]
[InlineData(null, "MyKey", false)]
[InlineData("", "MyKey", false)]
[InlineData("HeaderName", null, false)]
Expand Down