From 8ae63ab5eb6b9deb708db7c7ac191e8d88879cb0 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 15 Aug 2025 13:00:00 -0500 Subject: [PATCH 01/18] update diff styles (no borders, show + in green, and - in red) --- src/Steeltoe.io/wwwroot/css/shared.css | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Steeltoe.io/wwwroot/css/shared.css b/src/Steeltoe.io/wwwroot/css/shared.css index 991ae390..ec6d0fb7 100644 --- a/src/Steeltoe.io/wwwroot/css/shared.css +++ b/src/Steeltoe.io/wwwroot/css/shared.css @@ -83,6 +83,10 @@ footer a:hover { color: steelblue; } +.hljs-deletion { + color: red; +} + :not(a):not(pre) > code, body[data-yaml-mime=ManagedReference] article dl.parameters > dt > code, body[data-yaml-mime=ApiPage] article dl.parameters > dt > code { @@ -97,6 +101,14 @@ body[data-yaml-mime=ApiPage] article dl.parameters > dt > code { color: #006881 } + .hljs-deletion, .hljs-addition { + display: inline-block; + } + + .hljs-addition { + color: green; + } + :not(a):not(pre) > code { color: var(--bs-primary-text-emphasis); background-color: #e6e6e6; @@ -188,6 +200,15 @@ body[data-yaml-mime=ApiPage] article dl.parameters > dt > code { .hljs-built_in { color: #569cd6; } + + .hljs-deletion { + background-color: transparent; + } + + .hljs-addition { + background-color: transparent; + color: lightgreen; + } } } From d9aba6c72fd8fea114f7ea386879efb9a6065bc8 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:18:59 +0200 Subject: [PATCH 02/18] migration steps, excluding JWT/OAuth/OpenID Connect/Certificates --- docs/docs/v4/welcome/migrate-quick-steps.md | 1194 +++++++++++++++++++ docs/docs/v4/welcome/toc.yml | 2 + docs/docs/v4/welcome/whats-new.md | 8 +- src/Steeltoe.io/Program.cs | 2 +- 4 files changed, 1202 insertions(+), 4 deletions(-) create mode 100644 docs/docs/v4/welcome/migrate-quick-steps.md diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md new file mode 100644 index 00000000..c7144354 --- /dev/null +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -0,0 +1,1194 @@ +# Migrating from Steeltoe 3 + +This topic provides quick steps to migrate existing applications to Steeltoe 4. +For non-trivial cases, see the related documentation topic and samples for v4. + +> [!TIP] +> For detailed information on what has changed, see [What's new in Steeltoe 4](./whats-new.md). + +## Bootstrap + +For additional information, see the updated [Bootstrap documentation](../bootstrap/index.md). + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs: + +```diff +-using Steeltoe.Bootstrap.Autoconfig; ++using Steeltoe.Bootstrap.AutoConfiguration; + +var builder = WebApplication.CreateBuilder(args); +builder.AddSteeltoe(); +``` + +## CircuitBreaker + +CircuitBreaker (a .NET port of Netflix Hystrix) has been removed from Steeltoe in v4. +Use [Polly](https://github.com/App-vNext/Polly) instead. + +## Configuration + +For additional information, see the updated [Configuration documentation](../configuration/index.md) and +[Configuration samples](https://github.com/SteeltoeOSS/Samples/tree/main/Configuration). + +### Cloud Foundry + +Project file: + +```diff + + +- ++ + + +``` + +#### Load `VCAP_SERVICES`/`VCAP_APPLICATION` into `IConfiguration` + +Program.cs: + +```diff +-using Steeltoe.Extensions.Configuration.CloudFoundry; ++using Steeltoe.Configuration.CloudFoundry; + +var builder = WebApplication.CreateBuilder(args); +builder.AddCloudFoundryConfiguration(); + +Console.WriteLine($"Application name: {builder.Configuration["vcap:application:application_name"]}"); + +foreach (var section in builder.Configuration.GetRequiredSection("vcap:services").GetChildren()) +{ + var plans = string.Join(", ", section + .GetChildren() + .SelectMany(child => child.GetChildren()) + .Where(child => child.Key == "plan") + .Select(child => child.Value)); + Console.WriteLine($"Service: {section.Key} with plans: {plans}"); +} +``` + +#### Load `VCAP_SERVICES`/`VCAP_APPLICATION` into `OptionsMonitor` + +Program.cs: + +```diff +using Microsoft.Extensions.Options; +-using Steeltoe.Extensions.Configuration.CloudFoundry; ++using Steeltoe.Configuration.CloudFoundry; + +var builder = WebApplication.CreateBuilder(args); +builder.AddCloudFoundryConfiguration(); +-builder.Services.ConfigureCloudFoundryOptions(builder.Configuration); + +var app = builder.Build(); + +var appMonitor = app.Services.GetRequiredService>(); +Console.WriteLine($"Application name: {appMonitor.CurrentValue.ApplicationName}"); + +var servicesMonitor = app.Services.GetRequiredService>(); +foreach (var services in servicesMonitor.CurrentValue.Services) +{ + var plans = string.Join(", ", services.Value.Select(service => service.Plan)); + Console.WriteLine($"Service: {services.Key} with plans: {plans}"); +} +``` + +### Config Server + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs: + +```diff +-using Steeltoe.Extensions.Configuration.ConfigServer; ++using Steeltoe.Configuration.ConfigServer; + +var builder = WebApplication.CreateBuilder(args); +builder.AddConfigServer(); +``` + +### Kubernetes + +Direct interaction with the Kubernetes API has been removed from Steeltoe in v4. + +### Placeholder + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs: + +```diff +-using Steeltoe.Extensions.Configuration.Placeholder; ++using Steeltoe.Configuration.Placeholder; + +var builder = WebApplication.CreateBuilder(args); +-builder.AddPlaceholderResolver(); ++builder.Configuration.AddPlaceholderResolver(); +``` + +### Random Value + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs: + +```diff +-using Steeltoe.Extensions.Configuration.RandomValue; ++using Steeltoe.Configuration.RandomValue; + +var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddRandomValueSource(); +``` + +### Spring Boot + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs: + +```diff +-using Steeltoe.Extensions.Configuration.SpringBoot; ++using Steeltoe.Configuration.SpringBoot; + +var builder = WebApplication.CreateBuilder(args); +-builder.AddSpringBootConfiguration(); ++builder.Configuration.AddSpringBootFromCommandLine(args); ++builder.Configuration.AddSpringBootFromEnvironmentVariable(); +``` + +## Connectors + +For additional information, see the updated [Connectors documentation](../configuration/index.md) and +[Configuration samples](https://github.com/SteeltoeOSS/Samples/tree/main/Connectors). + +### MySQL using ADO.NET + +Project file: + +```diff + + + +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", +- "MySql:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ "Steeltoe:Client:MySql:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +} +``` + +Program.cs: + +```diff +using MySql.Data.MySqlClient; +-using Steeltoe.Connector.MySql; ++using Steeltoe.Connectors; ++using Steeltoe.Connectors.MySql; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddMySqlConnection(builder.Configuration); ++builder.AddMySql(); + +var app = builder.Build(); + +-await using var scope = app.Services.CreateAsyncScope(); +-await using var connection = scope.ServiceProvider.GetRequiredService(); ++var factory = app.Services.GetRequiredService>(); ++var connector = factory.Get(); ++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}"); ++await using var connection = connector.GetConnection(); + +await connection.OpenAsync(); +await using var command = connection.CreateCommand(); +command.CommandText = "SELECT 1"; +var result = await command.ExecuteScalarAsync(); +Console.WriteLine($"Query returned: {result}"); +``` + +### MySQL using Entity Framework Core + +Project file: + +```diff + + + +- +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", +- "MySql:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ "Steeltoe:Client:MySql:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +} +``` + +Program.cs: + +```diff +using Microsoft.EntityFrameworkCore; +-using Steeltoe.Connector.MySql; ++using Steeltoe.Connectors.MySql; +-using Steeltoe.Connector.MySql.EFCore; ++using Steeltoe.Connectors.EntityFrameworkCore.MySql; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddDbContext(options => options.UseMySql(builder.Configuration)); +-builder.Services.AddMySqlHealthContributor(builder.Configuration); ++builder.AddMySql(); ++builder.Services.AddDbContext((serviceProvider, options) => options.UseMySql(serviceProvider)); + +var app = builder.Build(); + +await using var scope = app.Services.CreateAsyncScope(); +await using var dbContext = scope.ServiceProvider.GetRequiredService(); +var rowCount = await dbContext.ExampleEntities.CountAsync(); +Console.WriteLine($"Found {rowCount} rows."); +``` + +### PostgreSQL using ADO.NET + +Project file: + +```diff + + + +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", +- "Postgres:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ "Steeltoe:Client:PostgreSQL:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +} +``` + +Program.cs: + +```diff +using Npgsql; +-using Steeltoe.Connector.PostgreSql; ++using Steeltoe.Connectors; ++using Steeltoe.Connectors.PostgreSql; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddPostgresConnection(builder.Configuration); ++builder.AddPostgreSql(); + +var app = builder.Build(); + +-await using var scope = app.Services.CreateAsyncScope(); +-await using var connection = scope.ServiceProvider.GetRequiredService(); ++var factory = app.Services.GetRequiredService>(); ++var connector = factory.Get(); ++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}"); ++await using var connection = connector.GetConnection(); + +await connection.OpenAsync(); +await using var command = connection.CreateCommand(); +command.CommandText = "SELECT 1"; +var result = await command.ExecuteScalarAsync(); +Console.WriteLine($"Query returned: {result}"); +``` + +### PostgreSQL using Entity Framework Core + +Project file: + +```diff + + + +- +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", +- "Postgres:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ "Steeltoe:Client:PostgreSQL:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +} +``` + +Program.cs: + +```diff +using Microsoft.EntityFrameworkCore; +-using Steeltoe.Connector.PostgreSql; ++using Steeltoe.Connectors.PostgreSql; +-using Steeltoe.Connector.PostgreSql.EFCore; ++using Steeltoe.Connectors.EntityFrameworkCore.PostgreSql; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration)); +-builder.Services.AddPostgresHealthContributor(builder.Configuration); ++builder.AddPostgreSql(); ++builder.Services.AddDbContext((serviceProvider, options) => options.UseNpgsql(serviceProvider)); + +var app = builder.Build(); + +await using var scope = app.Services.CreateAsyncScope(); +await using var dbContext = scope.ServiceProvider.GetRequiredService(); +var rowCount = await dbContext.ExampleEntities.CountAsync(); +Console.WriteLine($"Found {rowCount} rows."); +``` + +### RabbitMQ + +Project file: + +```diff + + + +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", +- "RabbitMQ:Client:ConnectionString": "Server=localhost" ++ "Steeltoe:Client:RabbitMQ:Default:ConnectionString": "amqp://localhost:5672" +} +``` + +> [!TIP] +> See the RabbitMQ documentation [here](https://www.rabbitmq.com/docs/uri-spec) and [here](https://www.rabbitmq.com/docs/uri-query-parameters) for the `ConnectionString` URI format. + +Program.cs: + +```diff +using RabbitMQ.Client; +-using Steeltoe.Connector.RabbitMQ; ++using Steeltoe.Connectors; ++using Steeltoe.Connectors.RabbitMQ; +using System.Text; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddRabbitMQConnection(builder.Configuration, ServiceLifetime.Singleton); ++builder.AddRabbitMQ(); + +var app = builder.Build(); + +-var connectionFactory = app.Services.GetRequiredService(); +-var connection = await connectionFactory.CreateConnectionAsync(); // long-lived, do not dispose ++var factory = app.Services.GetRequiredService>(); ++var connector = factory.Get(); ++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}"); ++var connection = connector.GetConnection(); // long-lived, do not dispose +await using var channel = await connection.CreateChannelAsync(); +const string queueName = "example-queue-name"; +await channel.QueueDeclareAsync(queueName); + +byte[] messageToSend = "example-message"u8.ToArray(); +await channel.BasicPublishAsync(exchange: "", queueName, mandatory: true, new BasicProperties(), messageToSend); + +var result = await channel.BasicGetAsync(queueName, autoAck: true); +string messageReceived = result == null ? "(none)" : Encoding.UTF8.GetString(result.Body.ToArray()); +Console.WriteLine($"Received message: {messageReceived}"); +``` + +### Redis/Valkey + +Project file: + +```diff + + + +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", +- "Redis:Client:ConnectionString": "localhost:6379" ++ "Steeltoe:Client:Redis:Default:ConnectionString": "localhost" +} +``` + +Program.cs: + +```diff +using Microsoft.Extensions.Caching.Distributed; +-using Steeltoe.Connector.Redis; ++using Steeltoe.Connectors; ++using Steeltoe.Connectors.Redis; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddDistributedRedisCache(builder.Configuration); ++builder.AddRedis(); + +var app = builder.Build(); + +-var cache = app.Services.GetRequiredService(); ++var factory = app.Services.GetRequiredService>(); ++var connector = factory.Get(); ++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}"); ++var cache = connector.GetConnection(); +await cache.SetAsync("example-key", "example-value"u8.ToArray()); +var value = await cache.GetStringAsync("example-key"); +Console.WriteLine($"Received value: {value}"); +``` + +## Discovery + +For additional information, see the updated [Discovery documentation](../discovery/index.md) and +[Discovery samples](https://github.com/SteeltoeOSS/Samples/tree/main/Discovery). + +### Eureka + +#### Register your service + +Project file: + +```diff + + +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", + "Spring:Application:Name": "example-service", + "Eureka:Client:ShouldRegisterWithEureka": true, + "Eureka:Client:ShouldFetchRegistry": false +} +``` + +launchSettings.json: + +``` +{ + "profiles": { + "http": { + "commandName": "Project", + "applicationUrl": "http://+:5005" // bind to all host names and IP addressess + } + } +} +``` + +Program.cs: + +```diff +-using Steeltoe.Discovery.Client; ++using Steeltoe.Discovery.Eureka; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddDiscoveryClient(); ++builder.Services.AddEurekaDiscoveryClient(); + +var app = builder.Build(); + +app.MapGet("/ping", async httpContext => +{ + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("pong"); +}); +``` + +#### Lookup other services + +Project file: + +```diff + + +- ++ ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", + "Eureka:Client:ShouldRegisterWithEureka": false, + "Eureka:Client:ShouldFetchRegistry": true +} +``` + +Program.cs: + +```diff +-using Steeltoe.Common.Http.Discovery; +-using Steeltoe.Discovery.Client; ++using Steeltoe.Discovery.Eureka; ++using Steeltoe.Discovery.HttpClients; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddDiscoveryClient(); ++builder.Services.AddEurekaDiscoveryClient(); +builder.Services + .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("http://example-service/")) + .AddServiceDiscovery(); + +var app = builder.Build(); + +var pingClient = app.Services.GetRequiredService(); +string response = await pingClient.GetPingAsync(); +Console.WriteLine($"Response: {response}"); + +public class PingClient(HttpClient httpClient) +{ + public async Task GetPingAsync() + { + return await httpClient.GetStringAsync("ping"); + } +} +``` + +### Consul + +#### Register your service + +Project file: + +```diff + + +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", + "Spring:Application:Name": "example-service", + "Consul:Discovery:Register": true +} +``` + +launchSettings.json: + +``` +{ + "profiles": { + "http": { + "commandName": "Project", + "applicationUrl": "http://+:5005" // bind to all host names and IP addressess + } + } +} +``` + +Program.cs: + +```diff +-using Steeltoe.Discovery.Client; ++using Steeltoe.Discovery.Consul; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddDiscoveryClient(); +builder.Services.AddConsulDiscoveryClient(); + +var app = builder.Build(); + +app.MapGet("/ping", async httpContext => +{ + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("pong"); +}); +``` + +#### Lookup other services + +Project file: + +```diff + + +- ++ ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", + "Consul:Discovery:Register": false +} +``` + +Program.cs: + +```diff +-using Steeltoe.Common.Http.Discovery; +-using Steeltoe.Discovery.Client; ++using Steeltoe.Discovery.Consul; ++using Steeltoe.Discovery.HttpClients; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddDiscoveryClient(); ++builder.Services.AddConsulDiscoveryClient(); +builder.Services + .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("http://example-service/")) + .AddServiceDiscovery(); + +var app = builder.Build(); + +var pingClient = app.Services.GetRequiredService(); +string response = await pingClient.GetPingAsync(); +Console.WriteLine($"Response: {response}"); + +public class PingClient(HttpClient httpClient) +{ + public async Task GetPingAsync() + { + return await httpClient.GetStringAsync("ping"); + } +} +``` + +## Integration + +Integration (lightweight messaging for Spring-based applications) has been removed from Steeltoe in v4. + +## Logging + +For additional information, see the updated [Logging documentation](../logging/index.md). + +### Dynamic Console + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs: + +```diff +-using Steeltoe.Extensions.Logging; ++using Steeltoe.Logging; ++using Steeltoe.Logging.DynamicConsole; + +var builder = WebApplication.CreateBuilder(args); +builder.Logging.SetMinimumLevel(LogLevel.Debug); +builder.Configuration["Logging:LogLevel:Default"] = "Warning"; +builder.Logging.AddDynamicConsole(); + +var app = builder.Build(); + +var loggerFactory = app.Services.GetRequiredService(); +var exampleLogger = loggerFactory.CreateLogger("Example.Sub.Namespace"); + +exampleLogger.LogDebug("Example debug message (1) - hidden"); + +var dynamicLoggerProvider = app.Services.GetRequiredService(); +dynamicLoggerProvider.SetLogLevel("Example", LogLevel.Debug); + +exampleLogger.LogDebug("Example debug message (2)"); + +await Task.Delay(TimeSpan.FromMilliseconds(250)); // wait for logs to flush +``` + +## Management + +For additional information, see the updated [Management documentation](../management/index.md) and +[Management samples](https://github.com/SteeltoeOSS/Samples/tree/main/Management). + +### Endpoints + +Project file: + +```diff + + +- ++ + + +``` + +#### All actuators + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", + "Management:Endpoints:Actuator:Exposure:Include": ["*"], ++ "Management:Endpoints:Health:ShowComponents": "Always", ++ "Management:Endpoints:Health:ShowDetails": "Always", ++ "Management:Endpoints:Health:Readiness:Enabled": true, ++ "Management:Endpoints:Health:Liveness:Enabled": true +} +``` + +Program.cs: + +```diff +-using Steeltoe.Management.Endpoint; ++using Steeltoe.Management.Endpoint.Actuators.All; + +var builder = WebApplication.CreateBuilder(args); +-builder.AddAllActuators(); ++builder.Services.AddAllActuators(); +``` + +#### Custom health contributor + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", ++ "Management:Endpoints:Health:ShowComponents": "Always", ++ "Management:Endpoints:Health:ShowDetails": "Always", ++ "Management:Endpoints:Health:Readiness:Enabled": true, ++ "Management:Endpoints:Health:Liveness:Enabled": true +} +``` + +Program.cs: + +```diff +using Steeltoe.Common.HealthChecks; +-using Steeltoe.Management.Endpoint; ++using Steeltoe.Management.Endpoint.Actuators.Health; + +var builder = WebApplication.CreateBuilder(args); + +-builder.AddHealthActuator(); ++builder.Services.AddHealthActuator(); +-builder.Services.AddSingleton(); ++builder.Services.AddHealthContributor(); +-builder.Services.AddControllers(); + +var app = builder.Build(); +-app.MapControllers(); +app.Run(); + +public class WarningHealthContributor : IHealthContributor +{ + public string Id => "exampleContributor"; + +- public HealthCheckResult Health() ++ public async Task CheckHealthAsync(CancellationToken cancellationToken) + { ++ await Task.Yield(); + return new HealthCheckResult + { +- Status = HealthStatus.WARNING, ++ Status = HealthStatus.Warning, ++ Description = "Example health contributor reports warning.", + Details = + { +- ["status"] = HealthStatus.WARNING, +- ["description"] = "Example health contributor reports warning.", + ["currentTime"] = DateTime.UtcNow.ToString("O") + } + }; + } +} +``` + +#### Custom info contributor + +Program.cs: + +```diff +-using Steeltoe.Management.Endpoint; +-using Steeltoe.Management.Info; ++using Steeltoe.Management.Endpoint.Actuators.Info; + +var builder = WebApplication.CreateBuilder(args); +-builder.AddInfoActuator(); ++builder.Services.AddInfoActuator(); +-builder.Services.AddSingleton(); ++builder.Services.AddInfoContributor(); +-builder.Services.AddControllers(); + +var app = builder.Build(); +-app.MapControllers(); +app.Run(); + +public class ExampleInfoContributor : IInfoContributor +{ +- public void Contribute(IInfoBuilder builder) ++ public async Task ContributeAsync(InfoBuilder builder, CancellationToken cancellationToken) + { ++ await Task.Yield(); + builder.WithInfo(".NET version", Environment.Version); + } +} +``` + +#### Cloud hosting + +The `UseCloudHosting` extension method has been removed from Steeltoe in v4. Use one of the methods described at +[8 ways to set the URLs for an ASP.NET Core app](https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/) +to configure the port number(s) to listen on. + +Program.cs: + +```diff +-using Steeltoe.Common.Hosting; +-using Steeltoe.Management.Endpoint; +using Steeltoe.Management.Endpoint.Actuators.All; + +var builder = WebApplication.CreateBuilder(args); +-builder.UseCloudHosting(runLocalHttpPort: 8080, runLocalHttpsPort: 9090); ++builder.WebHost.UseUrls("http://+:8080", "https://+:9090"); +-builder.AddAllActuators(); +builder.Services.AddAllActuators(); +``` + +For deployment to Cloud Foundry, the `builder.WebHost.UseUrls` line should be omitted. + +- When using the dotnet_core_buildpack, the `PORT` environment variable is picked up automatically. +- When using the binary_buildpack, use the `PORT` environment variable in the `manifest.yml` file: + + ```yaml + --- + applications: + - name: example-app + stack: windows + buildpacks: + - binary_buildpack + command: cmd /c ./example-app --urls=http://0.0.0.0:%PORT% + ``` + +#### Spring Boot Admin + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", + "Spring:Application:Name": "example-service", + "Management:Endpoints:Actuator:Exposure:Include": [ "*" ], + "Spring:Boot:Admin:Client:Url": "http://localhost:9099", +- "Spring:Boot:Admin:Client:BasePath": "http://host.docker.internal:5050" ++ "Spring:Boot:Admin:Client:BaseHost": "host.docker.internal" +} +``` + +Program.cs: + +```diff +-using Steeltoe.Management.Endpoint; ++using Steeltoe.Management.Endpoint.Actuators.All; ++using Steeltoe.Management.Endpoint.SpringBootAdminClient; + +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseUrls("http://host.docker.internal:5050"); +-builder.AddAllActuators(); ++builder.Services.AddAllActuators(); +builder.Services.AddSpringBootAdminClient(); +``` + +### OpenTelemetry + +Using OpenTelemetry for collecting logs, metrics and distributed traces now works out of the box without requiring a Steeltoe NuGet package. +See the instructions [here](../tracing/index.md) to configure OpenTelemetry in your application. +See [here](../management/prometheus.md) to export metrics to Prometheus using Steeltoe v4. + +The sample [here](https://github.com/SteeltoeOSS/Samples/blob/main/Management/src/ActuatorWeb/README.md#viewing-metric-dashboards) demonstrates exporting to Prometheus and Grafana. + +### Kubernetes + +Direct interaction with the Kubernetes API has been removed from Steeltoe in v4. + +### Application tasks + +Project file: + +```diff + + +- ++ + + +``` + +After the following steps, run your app: + +```shell +dotnet run runtask=example-task +``` + +#### Using inline code + +Program.cs: + +```diff +-using Steeltoe.Management.TaskCore; ++using Steeltoe.Management.Tasks; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddTask("example-task", serviceProvider => ++builder.Services.AddTask("example-task", async (serviceProvider, cancellationToken) => +{ ++ await Task.Yield(); + var loggerFactory = serviceProvider.GetRequiredService(); + var logger = loggerFactory.CreateLogger("ExampleTaskLogger"); + logger.LogInformation("Example task executed."); +}); + +var app = builder.Build(); +-app.RunWithTasks(); ++await app.RunWithTasksAsync(CancellationToken.None); +``` + +#### Implementing `IApplicationTask` + +Program.cs: + +```diff +using Steeltoe.Common; +-using Steeltoe.Management.TaskCore; ++using Steeltoe.Management.Tasks; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddTask(); ++builder.Services.AddTask("example-task"); + +var app = builder.Build(); +-app.RunWithTasks(); ++await app.RunWithTasksAsync(CancellationToken.None); + +public class ExampleTask(ILogger logger) : IApplicationTask +{ +- public string Name => "example-task"; + +- public void Run() ++ public Task RunAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Example task executed."); ++ return Task.CompletedTask; + } +} +``` + +## Messaging + +Template-based support for Spring messaging systems has been removed from Steeltoe in v4. + +## Stream + +Spring Cloud Stream support has been removed from Steeltoe in v4. + +## Security + +For additional information, see the updated [Security documentation](../security/index.md) and +[Discovery samples](https://github.com/SteeltoeOSS/Samples/tree/main/Security). + +### CredHub client + +The CredHub client has been removed from Steeltoe in v4. +Use [CredHub Service Broker](https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/credhub-service-broker/services/credhub-sb/index.html) instead. + +### TODO: JWT/OAuth/OpenID Connect/Certificates... + +### DataProtection Key Store using Redis/Valkey + +```diff + + +- +- +- ++ + + +``` + +appsettings.json: + +```diff +{ +- "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", +- "Redis:Client:ConnectionString": "localhost:6379" ++ "Steeltoe:Client:Redis:Default:ConnectionString": "localhost" +} +``` + +Program.cs: + +```diff +using Microsoft.AspNetCore.DataProtection; +-using Steeltoe.Connector.Redis; ++using Steeltoe.Connectors.Redis; +-using Steeltoe.Security.DataProtection; ++using Steeltoe.Security.DataProtection.Redis; + +var builder = WebApplication.CreateBuilder(args); +-builder.Services.AddRedisConnectionMultiplexer(builder.Configuration); +-builder.Services.AddDistributedRedisCache(builder.Configuration); ++builder.AddRedis(); +builder.Services.AddDataProtection() + .PersistKeysToRedis() + .SetApplicationName("example-app"); +builder.Services.AddSession(); + +var app = builder.Build(); +app.UseSession(); + +app.MapPost("set-session", httpContext => +{ + httpContext.Session.SetString("example-key", $"example-value-{Guid.NewGuid()}"); + httpContext.Response.StatusCode = 204; + return Task.CompletedTask; +}); + +app.MapGet("get-session", async httpContext => +{ + var sessionValue = httpContext.Session.GetString("example-key"); + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync($"Session value: {sessionValue ?? "(none)"}"); +}); + +app.Run(); +``` + +# TODO: Remove temporary template below + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs: + +```diff +-XXXXX ++XXXXX + +var builder = WebApplication.CreateBuilder(args); +-XXXXX ++XXXXX +``` diff --git a/docs/docs/v4/welcome/toc.yml b/docs/docs/v4/welcome/toc.yml index 54cb274e..a146cbc6 100644 --- a/docs/docs/v4/welcome/toc.yml +++ b/docs/docs/v4/welcome/toc.yml @@ -1,5 +1,7 @@ - href: whats-new.md name: What's new in Steeltoe 4 +- href: migrate-quick-steps.md + name: Migrating from Steeltoe 3 - href: prerequisites.md name: Prerequisites - href: common-steps.md diff --git a/docs/docs/v4/welcome/whats-new.md b/docs/docs/v4/welcome/whats-new.md index 23cb73f0..6517d576 100644 --- a/docs/docs/v4/welcome/whats-new.md +++ b/docs/docs/v4/welcome/whats-new.md @@ -620,8 +620,6 @@ For more information, see the updated [Configuration documentation](../configura For more information, see the updated [Connectors documentation](../configuration/index.md) and [Configuration samples](https://github.com/SteeltoeOSS/Samples/tree/main/Connectors). ---- - ## Discovery ### Behavior changes @@ -984,7 +982,11 @@ For more information, see the updated [Logging documentation](../logging/index.m - Actuators can be turned on/off or bound to different verbs at runtime using configuration - Simplified content negotiation; updated all actuators to support latest Spring media type - New actuator `/beans` that lists the contents of the .NET dependency container, including support for keyed services -- Update health checks and actuator to align with latest Spring; hide details by default; contributors can be turned on/off at runtime using configuration +- Update health checks and actuator to align with latest Spring + - Hide components/details by default + - Liveness/readiness contributors are turned off by default + - Contributors can be turned on/off at runtime using configuration + - Contributors must be singletons (you can inject `IHttpContextAccessor`, but `HttpContext` is unavailable from Discovery) - Support Windows network shares in disk space health contributor - Update `/mappings` actuator to include endpoints from Minimal APIs, Razor Pages, and Blazor, with richer metadata and improved compatibility with Spring - Heap dumps are enabled by default in Cloud Foundry on Linux; all dump types supported on Windows/Linux/macOS diff --git a/src/Steeltoe.io/Program.cs b/src/Steeltoe.io/Program.cs index f110845f..bed7d560 100644 --- a/src/Steeltoe.io/Program.cs +++ b/src/Steeltoe.io/Program.cs @@ -23,7 +23,7 @@ } var rewriteOptions = new RewriteOptions() - .AddRedirect("^docs/v3/obsolete", "docs/v4/welcome/whats-new.html", 301) + .AddRedirect("^docs/v3/obsolete", "docs/v4/welcome/migrate-quick-steps.html", 301) .AddRedirect("^circuit-breakers.*", "attic", 301) .AddRedirect("^steeltoe-circuitbreaker", "attic", 301) .AddRedirect("^event-driven", "attic", 301) From 10dc02f1fc2fe039b73947daf7b6911d214fc8bf Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:49:28 +0200 Subject: [PATCH 03/18] Expand appsettings.json content, fix incorrect 3x settings for RabbitMQ, add callout, remove template --- docs/docs/v4/welcome/migrate-quick-steps.md | 255 +++++++++++++++----- 1 file changed, 195 insertions(+), 60 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index c7144354..61279985 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -208,6 +208,10 @@ var builder = WebApplication.CreateBuilder(args); For additional information, see the updated [Connectors documentation](../configuration/index.md) and [Configuration samples](https://github.com/SteeltoeOSS/Samples/tree/main/Connectors). +> [!IMPORTANT] +> The configuration structure for Connectors has changed in Steeltoe 4. Always use the `ConnectionString` property instead of `Host`, `Port`, `Username`, `Password`, etc. +> Replace the key `Default` with the name of the service binding if you have multiple. + ### MySQL using ADO.NET Project file: @@ -228,8 +232,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -- "MySql:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" -+ "Steeltoe:Client:MySql:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- "MySql": { +- "Client": { +- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- } +- } ++ "Steeltoe": { ++ "Client": { ++ "MySql": { ++ "Default": { ++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ } ++ } ++ } ++ } } ``` @@ -282,8 +298,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -- "MySql:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" -+ "Steeltoe:Client:MySql:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- "MySql": { +- "Client": { +- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- } +- } ++ "Steeltoe": { ++ "Client": { ++ "MySql": { ++ "Default": { ++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ } ++ } ++ } ++ } } ``` @@ -330,8 +358,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -- "Postgres:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" -+ "Steeltoe:Client:PostgreSQL:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- "Postgres": { +- "Client": { +- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- } +- } ++ "Steeltoe": { ++ "Client": { ++ "PostgreSql": { ++ "Default": { ++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ } ++ } ++ } ++ } } ``` @@ -384,8 +424,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -- "Postgres:Client:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" -+ "Steeltoe:Client:PostgreSQL:Default:ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- "Postgres": { +- "Client": { +- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" +- } +- } ++ "Steeltoe": { ++ "Client": { ++ "PostgreSql": { ++ "Default": { ++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe" ++ } ++ } ++ } ++ } } ``` @@ -432,8 +484,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -- "RabbitMQ:Client:ConnectionString": "Server=localhost" -+ "Steeltoe:Client:RabbitMQ:Default:ConnectionString": "amqp://localhost:5672" +- "Rabbitmq": { +- "Client": { +- "Uri": "amqp://guest:guest@127.0.0.1/" +- } +- } ++ "Steeltoe": { ++ "Client": { ++ "RabbitMQ": { ++ "Default": { ++ "ConnectionString": "amqp://localhost:5672" ++ } ++ } ++ } ++ } } ``` @@ -493,8 +557,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -- "Redis:Client:ConnectionString": "localhost:6379" -+ "Steeltoe:Client:Redis:Default:ConnectionString": "localhost" +- "Redis": { +- "Client": { +- "ConnectionString": "localhost:6379" +- } +- } ++ "Steeltoe": { ++ "Client": { ++ "Redis": { ++ "Default": { ++ "ConnectionString": "localhost" ++ } ++ } ++ } ++ } } ``` @@ -548,9 +624,17 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Spring:Application:Name": "example-service", - "Eureka:Client:ShouldRegisterWithEureka": true, - "Eureka:Client:ShouldFetchRegistry": false + "Spring": { + "Application": { + "Name": "example-service" + } + }, + "Eureka": { + "Client": { + "ShouldRegisterWithEureka": true, + "ShouldFetchRegistry": false + } + } } ``` @@ -607,8 +691,17 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Eureka:Client:ShouldRegisterWithEureka": false, - "Eureka:Client:ShouldFetchRegistry": true + "Spring": { + "Application": { + "Name": "example-service" + } + }, + "Eureka": { + "Client": { + "ShouldRegisterWithEureka": false, + "ShouldFetchRegistry": true + } + } } ``` @@ -663,8 +756,16 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Spring:Application:Name": "example-service", - "Consul:Discovery:Register": true + "Spring": { + "Application": { + "Name": "example-service" + } + }, + "Consul": { + "Discovery": { + "Register": true + } + } } ``` @@ -721,7 +822,11 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Consul:Discovery:Register": false + "Consul": { + "Discovery": { + "Register": false + } + } } ``` @@ -829,11 +934,25 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Management:Endpoints:Actuator:Exposure:Include": ["*"], -+ "Management:Endpoints:Health:ShowComponents": "Always", -+ "Management:Endpoints:Health:ShowDetails": "Always", -+ "Management:Endpoints:Health:Readiness:Enabled": true, -+ "Management:Endpoints:Health:Liveness:Enabled": true + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": [ "*" ] + } ++ }, ++ "Health": { ++ "ShowComponents": "Always", ++ "ShowDetails": "Always", ++ "Readiness": { ++ "Enabled": true ++ }, ++ "Liveness": { ++ "Enabled": true ++ } + } + } + } } ``` @@ -856,10 +975,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -+ "Management:Endpoints:Health:ShowComponents": "Always", -+ "Management:Endpoints:Health:ShowDetails": "Always", -+ "Management:Endpoints:Health:Readiness:Enabled": true, -+ "Management:Endpoints:Health:Liveness:Enabled": true ++ "Management": { ++ "Endpoints": { ++ "Health": { ++ "ShowComponents": "Always", ++ "ShowDetails": "Always", ++ "Readiness": { ++ "Enabled": true ++ }, ++ "Liveness": { ++ "Enabled": true ++ } ++ } ++ } ++ } } ``` @@ -980,11 +1109,29 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Spring:Application:Name": "example-service", - "Management:Endpoints:Actuator:Exposure:Include": [ "*" ], - "Spring:Boot:Admin:Client:Url": "http://localhost:9099", -- "Spring:Boot:Admin:Client:BasePath": "http://host.docker.internal:5050" -+ "Spring:Boot:Admin:Client:BaseHost": "host.docker.internal" + "Spring": { + "Application": { + "Name": "example-service" + }, + "Boot": { + "Admin": { + "Client": { + "Url": "http://localhost:9099", +- "BasePath": "http://host.docker.internal:5050" ++ "BaseHost": "host.docker.internal" + } + } + } + }, + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": [ "*" ] + } + } + } + } } ``` @@ -1125,8 +1272,20 @@ appsettings.json: { - "$schema": "https://steeltoe.io/schema/v3/schema.json", + "$schema": "https://steeltoe.io/schema/v4/schema.json", -- "Redis:Client:ConnectionString": "localhost:6379" -+ "Steeltoe:Client:Redis:Default:ConnectionString": "localhost" +- "Redis": { +- "Client": { +- "ConnectionString": "localhost:6379" +- } +- } ++ "Steeltoe": { ++ "Client": { ++ "Redis": { ++ "Default": { ++ "ConnectionString": "localhost" ++ } ++ } ++ } ++ } } ``` @@ -1168,27 +1327,3 @@ app.MapGet("get-session", async httpContext => app.Run(); ``` - -# TODO: Remove temporary template below - -Project file: - -```diff - - -- -+ - - -``` - -Program.cs: - -```diff --XXXXX -+XXXXX - -var builder = WebApplication.CreateBuilder(args); --XXXXX -+XXXXX -``` From 6f00420779a31823f39d3a90f6e3b7661c57927f Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:39:44 +0200 Subject: [PATCH 04/18] Link to migration steps from whats-new page --- docs/docs/v4/welcome/whats-new.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs/v4/welcome/whats-new.md b/docs/docs/v4/welcome/whats-new.md index 6517d576..9724bc58 100644 --- a/docs/docs/v4/welcome/whats-new.md +++ b/docs/docs/v4/welcome/whats-new.md @@ -19,9 +19,13 @@ underscored that this is the time to refocus on Steeltoe's core goals and re-eva Steeltoe 4 is a major release that brings many improvements and changes to the library. The goal of this release is to make Steeltoe better integrated in the .NET ecosystem in a more developer-friendly way, compatible with the latest versions of .NET and third-party libraries/products, and to improve the overall quality of the library. -This document provides an overview of the changes in Steeltoe 4, the impact on existing applications, and serves as the upgrade guide (with a searchable API diff and replacement notes). Steeltoe 4 requires .NET 8 or higher. +This document provides an overview of the changes in Steeltoe 4 and the impact on existing applications (with a searchable API diff and replacement notes). + +> [!TIP] +> For quick steps to upgrade an existing application from Steeltoe 3, see [Migrating from Steeltoe 3](./migrate-quick-steps.md). + ### Quality of Life improvements - Annotated for [nullable reference types](https://learn.microsoft.com/dotnet/csharp/nullable-references) From b2f3cbc39ded953428ba4c7e11ae4a656adffeb7 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 29 Aug 2025 11:51:33 +0200 Subject: [PATCH 05/18] Turn off colors in unchanged diff --- docs/docs/v4/welcome/migrate-quick-steps.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 61279985..f3b7ecea 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -640,7 +640,7 @@ appsettings.json: launchSettings.json: -``` +```text { "profiles": { "http": { @@ -771,7 +771,7 @@ appsettings.json: launchSettings.json: -``` +```text { "profiles": { "http": { From 6049e3af22e5f29e6393308dfaf53849cd4f1f39 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 29 Aug 2025 11:54:54 +0200 Subject: [PATCH 06/18] Change colors-off to diff mode --- docs/docs/v4/welcome/migrate-quick-steps.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index f3b7ecea..48b8b966 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -640,7 +640,7 @@ appsettings.json: launchSettings.json: -```text +```diff { "profiles": { "http": { @@ -771,7 +771,7 @@ appsettings.json: launchSettings.json: -```text +```diff { "profiles": { "http": { From 66aa5eafc3039abb44c8e3de1d6d9c664d7dab6f Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 20 Aug 2025 15:24:32 -0500 Subject: [PATCH 07/18] migration steps for JWT/OAuth/OpenID Connect/Certificates --- docs/docs/v4/welcome/migrate-quick-steps.md | 242 +++++++++++++++++++- 1 file changed, 241 insertions(+), 1 deletion(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 48b8b966..afd3381a 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1251,7 +1251,247 @@ For additional information, see the updated [Security documentation](../security The CredHub client has been removed from Steeltoe in v4. Use [CredHub Service Broker](https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/credhub-service-broker/services/credhub-sb/index.html) instead. -### TODO: JWT/OAuth/OpenID Connect/Certificates... +### OAuth / OpenID Connect + +Project file: + +```diff + + +- ++ +- ++ + + +``` + +Program.cs: + +```diff +using Microsoft.AspNetCore.Authentication.Cookies; ++using Microsoft.AspNetCore.Authentication.OpenIdConnect; +-using Microsoft.AspNetCore.HttpOverrides; +-using Steeltoe.Extensions.Configuration.CloudFoundry; ++using Steeltoe.Configuration.CloudFoundry; +-using Steeltoe.Security.Authentication.CloudFoundry; ++using Steeltoe.Security.Authentication.OpenIdConnect; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddCloudFoundryConfiguration(); + +builder.Services.AddAuthentication((options) => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; +- options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; ++ options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie(options => options.AccessDeniedPath = new PathString("/Home/AccessDenied")) +- .AddCloudFoundryOAuth(builder.Configuration); +- .AddCloudFoundryOpenIdConnect(builder.Configuration); ++ .AddOpenIdConnect().ConfigureOpenIdConnectForCloudFoundry(); +builder.Services.AddAuthorizationBuilder() + .AddPolicy("some-group", policy => policy.RequireClaim("scope", "some-group")); + +var app = builder.Build(); + +-app.UseForwardedHeaders(new ForwardedHeadersOptions +-{ +- ForwardedHeaders = ForwardedHeaders.XForwardedProto +-}); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); +``` + +appsettings.json: + +```diff +- "$schema": "https://steeltoe.io/schema/v3/schema.json", +- "Security": { +- "Oauth2": { +- "Client": { +- "OAuthServiceUrl": "http://localhost:8080/uaa", +- "ClientId": "steeltoesamplesclient", +- "ClientSecret": "client_secret", +- } +- } +- } ++ "Authentication": { ++ "Schemes": { ++ "OpenIdConnect": { ++ "Authority": "http://localhost:8080/uaa", ++ "ClientId": "steeltoesamplesserver", ++ "ClientSecret": "server_secret", ++ } ++ } ++ } +``` + +> [!NOTE] +> This is not a complete listing of appsettings. As of version 4, Steeltoe configures Microsoft's option class rather than maintaining separate options. +> Refer to [the OpenIdConnectOptions class documentation](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions) for the new options. + +### JWT Bearer + +Project file: + +```diff + + +- ++ +- ++ + + +``` + +Program.cs: + +```diff +using Microsoft.AspNetCore.Authentication.JwtBearer; +-using Microsoft.AspNetCore.HttpOverrides; +-using Steeltoe.Extensions.Configuration.CloudFoundry; ++using Steeltoe.Configuration.CloudFoundry; +-using Steeltoe.Security.Authentication.CloudFoundry; ++using Steeltoe.Security.Authentication.JwtBearer; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddCloudFoundryConfiguration(); + +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) +- .AddCloudFoundryJwtBearer(builder.Configuration); ++ .AddJwtBearer().ConfigureJwtBearerForCloudFoundry(); +builder.Services.AddAuthorizationBuilder() + .AddPolicy("some-group", policy => policy.RequireClaim("scope", "some-group")); + +var app = builder.Build(); + +-app.UseForwardedHeaders(new ForwardedHeadersOptions +-{ +- ForwardedHeaders = ForwardedHeaders.XForwardedProto +-}); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); +``` + +appsettings.json: + +```diff +- "$schema": "https://steeltoe.io/schema/v3/schema.json", +- "Security": { +- "Oauth2": { +- "Client": { +- "OAuthServiceUrl": "http://localhost:8080/uaa", +- "ClientId": "steeltoesamplesserver", +- "ClientSecret": "server_secret", +- } +- } +- } ++ "Authentication": { ++ "Schemes": { ++ "Bearer": { ++ "Authority": "http://localhost:8080/uaa", ++ "ClientId": "steeltoesamplesserver", ++ "ClientSecret": "server_secret", ++ } ++ } ++ } +``` + +> [!NOTE] +> This is not a complete listing of appsettings. As of version 4, Steeltoe configures Microsoft's option class rather than maintaining separate options. +> Refer to [the JwtBearerOptions class documentation](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions) for the new options. + +### Certificates / MutualTLS + +Project file: + +```diff + + +- ++ + + +``` + +Program.cs (server-side): + +```diff ++using Steeltoe.Common.Certificates; +-using Steeltoe.Security.Authentication.CloudFoundry; ++using Steeltoe.Security.Authorization.Certificate; + +var builder = WebApplication.CreateBuilder(args); + +-builder.Configuration.AddCloudFoundryContainerIdentity(); ++builder.Configuration.AddAppInstanceIdentityCertificate(); + +-builder.Services.AddCloudFoundryCertificateAuth(); ++builder.Services.AddAuthentication().AddCertificate(); ++builder.Services.AddAuthorizationBuilder().AddOrgAndSpacePolicies(); + +var app = builder.Build(); + +-app.UseCloudFoundryCertificateAuth(); ++app.UseCertificateAuthorization(); +``` + +Secured endpoints: + +```diff +-using Steeltoe.Security.Authentication.CloudFoundry; ++using Steeltoe.Security.Authorization.Certificate; + +- [Authorize(CloudFoundryDefaults.SameOrganizationAuthorizationPolicy)] ++ [Authorize(CertificateAuthorizationPolicies.SameOrg)] + [HttpGet] + public string SameOrg() + { + return "Certificate is valid and both client and server are in the same org"; + } + +- [Authorize(CloudFoundryDefaults.SameSpaceAuthorizationPolicy)] ++ [Authorize(CertificateAuthorizationPolicies.SameSpace)] + [HttpGet] + public string SameSpace() + { + return "Certificate is valid and both client and server are in the same space"; + } +``` + +Program.cs (client-side): + +```diff +-using System.Security.Cryptography.X509Certificates; +-using Microsoft.Extensions.Options; +-using Steeltoe.Common.Options; ++using Steeltoe.Common.Certificates; +-using Steeltoe.Security.Authentication.CloudFoundry; ++using Steeltoe.Security.Authorization.Certificate; + +var builder = WebApplication.CreateBuilder(args); + +-builder.Configuration.AddCloudFoundryContainerIdentity(); ++builder.Configuration.AddAppInstanceIdentityCertificate(); + +-builder.Services.AddHttpClient("withCertificate", (services, client) => +-{ +- var options = services.GetRequiredService>(); +- var b64 = Convert.ToBase64String(options.Value.Certificate.Export(X509ContentType.Cert)); +- client.DefaultRequestHeaders.Add("X-Forwarded-Client-Cert", b64); +-}); ++builder.Services.AddHttpClient("withCertificate").AddAppInstanceIdentityCertificate(); +``` ### DataProtection Key Store using Redis/Valkey From bed7f16ca9b77013ac54c21658f2de34d1e63fb3 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Thu, 21 Aug 2025 10:59:34 -0500 Subject: [PATCH 08/18] PR suggestions --- docs/docs/v4/welcome/migrate-quick-steps.md | 99 +++++++++++---------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index afd3381a..55b44b8d 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1266,6 +1266,35 @@ Project file: ``` +appsettings.json: + +```diff +- "$schema": "https://steeltoe.io/schema/v3/schema.json", +- "Security": { +- "Oauth2": { +- "Client": { +- "OAuthServiceUrl": "http://localhost:8080/uaa", +- "ClientId": "steeltoesamplesclient", +- "ClientSecret": "client_secret" +- } +- } +- } ++ "Authentication": { ++ "Schemes": { ++ "OpenIdConnect": { ++ "Authority": "http://localhost:8080/uaa", ++ "ClientId": "steeltoesamplesserver", ++ "ClientSecret": "server_secret", ++ "RequireHttpsMetadata": false ++ } ++ } ++ } +``` + +> [!NOTE] +> This is not a complete listing of appsettings. As of version 4, Steeltoe configures Microsoft's option class rather than maintaining separate options. +> Refer to [the OpenIdConnectOptions class documentation](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions) for the new options. + Program.cs: ```diff @@ -1288,7 +1317,6 @@ builder.Services.AddAuthentication((options) => + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(options => options.AccessDeniedPath = new PathString("/Home/AccessDenied")) -- .AddCloudFoundryOAuth(builder.Configuration); - .AddCloudFoundryOpenIdConnect(builder.Configuration); + .AddOpenIdConnect().ConfigureOpenIdConnectForCloudFoundry(); builder.Services.AddAuthorizationBuilder() @@ -1307,6 +1335,24 @@ app.UseAuthentication(); app.UseAuthorization(); ``` +> [!NOTE] +> The code above should also be used for applications that previously used `.AddCloudFoundryOAuth(builder.Configuration);` + +### JWT Bearer + +Project file: + +```diff + + +- ++ +- ++ + + +``` + appsettings.json: ```diff @@ -1315,14 +1361,14 @@ appsettings.json: - "Oauth2": { - "Client": { - "OAuthServiceUrl": "http://localhost:8080/uaa", -- "ClientId": "steeltoesamplesclient", -- "ClientSecret": "client_secret", +- "ClientId": "steeltoesamplesserver", +- "ClientSecret": "server_secret", - } - } - } + "Authentication": { + "Schemes": { -+ "OpenIdConnect": { ++ "Bearer": { + "Authority": "http://localhost:8080/uaa", + "ClientId": "steeltoesamplesserver", + "ClientSecret": "server_secret", @@ -1333,22 +1379,7 @@ appsettings.json: > [!NOTE] > This is not a complete listing of appsettings. As of version 4, Steeltoe configures Microsoft's option class rather than maintaining separate options. -> Refer to [the OpenIdConnectOptions class documentation](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions) for the new options. - -### JWT Bearer - -Project file: - -```diff - - -- -+ -- -+ - - -``` +> Refer to [the JwtBearerOptions class documentation](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions) for the new options. Program.cs: @@ -1383,34 +1414,6 @@ app.UseAuthentication(); app.UseAuthorization(); ``` -appsettings.json: - -```diff -- "$schema": "https://steeltoe.io/schema/v3/schema.json", -- "Security": { -- "Oauth2": { -- "Client": { -- "OAuthServiceUrl": "http://localhost:8080/uaa", -- "ClientId": "steeltoesamplesserver", -- "ClientSecret": "server_secret", -- } -- } -- } -+ "Authentication": { -+ "Schemes": { -+ "Bearer": { -+ "Authority": "http://localhost:8080/uaa", -+ "ClientId": "steeltoesamplesserver", -+ "ClientSecret": "server_secret", -+ } -+ } -+ } -``` - -> [!NOTE] -> This is not a complete listing of appsettings. As of version 4, Steeltoe configures Microsoft's option class rather than maintaining separate options. -> Refer to [the JwtBearerOptions class documentation](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions) for the new options. - ### Certificates / MutualTLS Project file: From bd33802944a6ce1e2d068f00093c404874ec65e2 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 22 Aug 2025 08:08:34 -0500 Subject: [PATCH 09/18] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- docs/docs/v4/welcome/migrate-quick-steps.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 55b44b8d..800f0d58 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1251,7 +1251,9 @@ For additional information, see the updated [Security documentation](../security The CredHub client has been removed from Steeltoe in v4. Use [CredHub Service Broker](https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/credhub-service-broker/services/credhub-sb/index.html) instead. -### OAuth / OpenID Connect +### OAuth and OpenID Connect + +OAuth support has been removed from Steeltoe in v4. Use OpenID Connect instead. Project file: @@ -1307,9 +1309,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; +using Steeltoe.Security.Authentication.OpenIdConnect; var builder = WebApplication.CreateBuilder(args); - builder.AddCloudFoundryConfiguration(); - builder.Services.AddAuthentication((options) => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; From d482e3d0dec0be833ee13460ccaee5f7bcd64ce9 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 22 Aug 2025 18:52:13 -0500 Subject: [PATCH 10/18] PR feedback + make code work w/Steeltoe UAA image --- docs/docs/v4/welcome/migrate-quick-steps.md | 31 +++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 800f0d58..73d1b02d 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1275,9 +1275,14 @@ appsettings.json: - "Security": { - "Oauth2": { - "Client": { -- "OAuthServiceUrl": "http://localhost:8080/uaa", +- "Authority": "http://localhost:8080/uaa", +- "CallbackPath": "/signin-oidc", - "ClientId": "steeltoesamplesclient", -- "ClientSecret": "client_secret" +- "ClientSecret": "client_secret", +- "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", +- "AdditionalScopes": "profile sampleapi.read", +- "SaveTokens": true, +- "RequireHttpsMetadata": false - } - } - } @@ -1285,9 +1290,13 @@ appsettings.json: + "Schemes": { + "OpenIdConnect": { + "Authority": "http://localhost:8080/uaa", -+ "ClientId": "steeltoesamplesserver", -+ "ClientSecret": "server_secret", ++ "CallbackPath": "/signin-oidc", ++ "ClientId": "steeltoesamplesclient", ++ "ClientSecret": "client_secret", ++ "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", + "RequireHttpsMetadata": false ++ "SaveTokens": true, ++ "Scope": [ "openid", "sampleapi.read" ], + } + } + } @@ -1303,6 +1312,7 @@ Program.cs: using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.AspNetCore.HttpOverrides; +-using Microsoft.Extensions.Options; -using Steeltoe.Extensions.Configuration.CloudFoundry; +using Steeltoe.Configuration.CloudFoundry; -using Steeltoe.Security.Authentication.CloudFoundry; @@ -1329,14 +1339,12 @@ var app = builder.Build(); - ForwardedHeaders = ForwardedHeaders.XForwardedProto -}); -app.UseRouting(); - app.UseAuthentication(); app.UseAuthorization(); ``` > [!NOTE] -> The code above should also be used for applications that previously used `.AddCloudFoundryOAuth(builder.Configuration);` +> Use the code above for applications that previously used `.AddCloudFoundryOAuth(builder.Configuration);`. ### JWT Bearer @@ -1360,9 +1368,12 @@ appsettings.json: - "Security": { - "Oauth2": { - "Client": { -- "OAuthServiceUrl": "http://localhost:8080/uaa", +- "AuthDomain": "http://localhost:8080/uaa", - "ClientId": "steeltoesamplesserver", - "ClientSecret": "server_secret", +- "JwtKeyUrl": "http://localhost:8080/token_keys", +- "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", +- "RequireHttpsMetadata": false - } - } - } @@ -1408,8 +1419,6 @@ var app = builder.Build(); - ForwardedHeaders = ForwardedHeaders.XForwardedProto -}); -app.UseRouting(); - app.UseAuthentication(); app.UseAuthorization(); ``` @@ -1491,7 +1500,7 @@ var builder = WebApplication.CreateBuilder(args); -{ - var options = services.GetRequiredService>(); - var b64 = Convert.ToBase64String(options.Value.Certificate.Export(X509ContentType.Cert)); -- client.DefaultRequestHeaders.Add("X-Forwarded-Client-Cert", b64); +- client.DefaultRequestHeaders.Add("X-Client-Cert", b64); -}); +builder.Services.AddHttpClient("withCertificate").AddAppInstanceIdentityCertificate(); ``` From b7a8b65fa1928ae5bfef4fcc9b018e064332469d Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 25 Aug 2025 16:11:11 -0500 Subject: [PATCH 11/18] Update docs/docs/v4/welcome/migrate-quick-steps.md Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- docs/docs/v4/welcome/migrate-quick-steps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 73d1b02d..13412d2f 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1423,7 +1423,7 @@ app.UseAuthentication(); app.UseAuthorization(); ``` -### Certificates / MutualTLS +### Client Certificates (Mutual TLS) Project file: From 12d07f39b992f6d46e56188c12cceb9af5066338 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 27 Aug 2025 12:30:06 -0500 Subject: [PATCH 12/18] Separate OAuth from OIDC, more complete examples - use Steeltoe UAA for fully-functional examples --- docs/docs/v4/welcome/migrate-quick-steps.md | 201 +++++++++++++++----- 1 file changed, 150 insertions(+), 51 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 13412d2f..f37ce890 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1251,10 +1251,73 @@ For additional information, see the updated [Security documentation](../security The CredHub client has been removed from Steeltoe in v4. Use [CredHub Service Broker](https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/credhub-service-broker/services/credhub-sb/index.html) instead. -### OAuth and OpenID Connect +### OAuth OAuth support has been removed from Steeltoe in v4. Use OpenID Connect instead. +Consider migrating to OpenID Connect on Steeltoe v3 before moving to v4. + +appsettings.json: + +```diff +{ + "$schema": "https://steeltoe.io/schema/v3/schema.json", + "Security": { + "Oauth2": { + "Client": { +- "AuthDomain": "http://localhost:8080", ++ "Authority": "http://localhost:8080/uaa", + "CallbackPath": "/signin-oidc", + "ClientId": "steeltoesamplesclient", + "ClientSecret": "client_secret", ++ "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", ++ "RequireHttpsMetadata": false, ++ "AdditionalScopes": "sampleapi.read" + } + } + } +} +``` + +> [!NOTE] +> Depending on your application's needs, you may need to add scopes to the application's configuration that did not previously need to be specified. + +program.cs: + +```diff +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.HttpOverrides; +using Steeltoe.Extensions.Configuration.CloudFoundry; +using Steeltoe.Security.Authentication.CloudFoundry; + +var builder = WebApplication.CreateBuilder(args); +builder.AddCloudFoundryConfiguration(); +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; +}) + .AddCookie(options => options.AccessDeniedPath = new PathString("/Home/AccessDenied")) +- .AddCloudFoundryOAuth(builder.Configuration); ++ .AddCloudFoundryOpenIdConnect(builder.Configuration); +builder.Services.AddAuthorizationBuilder() + .AddPolicy("read", policy => policy.RequireClaim("scope", "sampleapi.read")) + .AddPolicy("write", policy => policy.RequireClaim("scope", "sampleapi.write")); + +var app = builder.Build(); + +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.XForwardedProto +}); + +app.UseAuthentication(); +app.UseAuthorization(); + +``` + +### OpenID Connect + Project file: ```diff @@ -1271,7 +1334,9 @@ Project file: appsettings.json: ```diff +{ - "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Security": { - "Oauth2": { - "Client": { @@ -1280,7 +1345,7 @@ appsettings.json: - "ClientId": "steeltoesamplesclient", - "ClientSecret": "client_secret", - "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", -- "AdditionalScopes": "profile sampleapi.read", +- "AdditionalScopes": "sampleapi.read", - "SaveTokens": true, - "RequireHttpsMetadata": false - } @@ -1294,12 +1359,13 @@ appsettings.json: + "ClientId": "steeltoesamplesclient", + "ClientSecret": "client_secret", + "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", -+ "RequireHttpsMetadata": false ++ "RequireHttpsMetadata": false, + "SaveTokens": true, -+ "Scope": [ "openid", "sampleapi.read" ], ++ "Scope": [ "openid", "sampleapi.read" ] + } + } + } +} ``` > [!NOTE] @@ -1310,27 +1376,29 @@ Program.cs: ```diff using Microsoft.AspNetCore.Authentication.Cookies; -+using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.AspNetCore.HttpOverrides; --using Microsoft.Extensions.Options; -using Steeltoe.Extensions.Configuration.CloudFoundry; +using Steeltoe.Configuration.CloudFoundry; ++using Steeltoe.Configuration.CloudFoundry.ServiceBindings; -using Steeltoe.Security.Authentication.CloudFoundry; ++using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Steeltoe.Security.Authentication.OpenIdConnect; var builder = WebApplication.CreateBuilder(args); builder.AddCloudFoundryConfiguration(); -builder.Services.AddAuthentication((options) => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; -- options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; -+ options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }) ++builder.Configuration.AddCloudFoundryServiceBindings(); +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; +- options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; ++ options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; +}) .AddCookie(options => options.AccessDeniedPath = new PathString("/Home/AccessDenied")) - .AddCloudFoundryOpenIdConnect(builder.Configuration); + .AddOpenIdConnect().ConfigureOpenIdConnectForCloudFoundry(); builder.Services.AddAuthorizationBuilder() - .AddPolicy("some-group", policy => policy.RequireClaim("scope", "some-group")); + .AddPolicy("read", policy => policy.RequireClaim("scope", "sampleapi.read")) + .AddPolicy("write", policy => policy.RequireClaim("scope", "sampleapi.write")); var app = builder.Build(); @@ -1343,9 +1411,6 @@ app.UseAuthentication(); app.UseAuthorization(); ``` -> [!NOTE] -> Use the code above for applications that previously used `.AddCloudFoundryOAuth(builder.Configuration);`. - ### JWT Bearer Project file: @@ -1364,11 +1429,13 @@ Project file: appsettings.json: ```diff +{ - "$schema": "https://steeltoe.io/schema/v3/schema.json", ++ "$schema": "https://steeltoe.io/schema/v4/schema.json", - "Security": { - "Oauth2": { - "Client": { -- "AuthDomain": "http://localhost:8080/uaa", +- "AuthDomain": "http://localhost:8080", - "ClientId": "steeltoesamplesserver", - "ClientSecret": "server_secret", - "JwtKeyUrl": "http://localhost:8080/token_keys", @@ -1380,12 +1447,16 @@ appsettings.json: + "Authentication": { + "Schemes": { + "Bearer": { -+ "Authority": "http://localhost:8080/uaa", ++ "Authority": "http://localhost:8080", + "ClientId": "steeltoesamplesserver", + "ClientSecret": "server_secret", ++ "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", ++ "RequireHttpsMetadata": false, ++ "ValidAudiences": [ "sampleapi" ] + } + } + } +} ``` > [!NOTE] @@ -1395,22 +1466,23 @@ appsettings.json: Program.cs: ```diff -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.HttpOverrides; -using Steeltoe.Extensions.Configuration.CloudFoundry; +using Steeltoe.Configuration.CloudFoundry; ++using Steeltoe.Configuration.CloudFoundry.ServiceBindings; -using Steeltoe.Security.Authentication.CloudFoundry; +using Steeltoe.Security.Authentication.JwtBearer; var builder = WebApplication.CreateBuilder(args); builder.AddCloudFoundryConfiguration(); ++builder.Configuration.AddCloudFoundryServiceBindings(); -builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) +builder.Services.AddAuthentication() - .AddCloudFoundryJwtBearer(builder.Configuration); + .AddJwtBearer().ConfigureJwtBearerForCloudFoundry(); builder.Services.AddAuthorizationBuilder() - .AddPolicy("some-group", policy => policy.RequireClaim("scope", "some-group")); + .AddPolicy("sampleapi.read", policy => policy.RequireClaim("scope", "sampleapi.read")); var app = builder.Build(); @@ -1421,6 +1493,14 @@ var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); + +app.MapGet("/jwt", async httpContext => + { + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("JWT is valid and contains the required claim"); + }).RequireAuthorization("sampleapi.read"); +app.Run(); ``` ### Client Certificates (Mutual TLS) @@ -1443,12 +1523,15 @@ Program.cs (server-side): -using Steeltoe.Security.Authentication.CloudFoundry; +using Steeltoe.Security.Authorization.Certificate; +const string orgId = "a8fef16f-94c0-49e3-aa0b-ced7c3da6229"; +const string spaceId = "122b942a-d7b9-4839-b26e-836654b9785f"; + var builder = WebApplication.CreateBuilder(args); --builder.Configuration.AddCloudFoundryContainerIdentity(); -+builder.Configuration.AddAppInstanceIdentityCertificate(); +-builder.Configuration.AddCloudFoundryContainerIdentity(orgId, spaceId); ++builder.Configuration.AddAppInstanceIdentityCertificate(new Guid(orgId), new Guid(spaceId)); --builder.Services.AddCloudFoundryCertificateAuth(); +-builder.Services.AddCloudFoundryCertificateAuth(options => options.CertificateHeader = "X-Client-Cert"); +builder.Services.AddAuthentication().AddCertificate(); +builder.Services.AddAuthorizationBuilder().AddOrgAndSpacePolicies(); @@ -1456,29 +1539,23 @@ var app = builder.Build(); -app.UseCloudFoundryCertificateAuth(); +app.UseCertificateAuthorization(); -``` - -Secured endpoints: - -```diff --using Steeltoe.Security.Authentication.CloudFoundry; -+using Steeltoe.Security.Authorization.Certificate; - -- [Authorize(CloudFoundryDefaults.SameOrganizationAuthorizationPolicy)] -+ [Authorize(CertificateAuthorizationPolicies.SameOrg)] - [HttpGet] - public string SameOrg() - { - return "Certificate is valid and both client and server are in the same org"; - } +app.MapGet("/sameOrg", async httpContext => + { + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("Client and server identity certificates have matching Org values"); + }) +- .RequireAuthorization(CloudFoundryDefaults.SameOrganizationAuthorizationPolicy); ++ .RequireAuthorization(CertificateAuthorizationPolicies.SameOrg); -- [Authorize(CloudFoundryDefaults.SameSpaceAuthorizationPolicy)] -+ [Authorize(CertificateAuthorizationPolicies.SameSpace)] - [HttpGet] - public string SameSpace() - { - return "Certificate is valid and both client and server are in the same space"; - } +app.MapGet("/sameSpace", async httpContext => + { + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("Client and server identity certificates have matching Space values"); + }) +- .RequireAuthorization(CloudFoundryDefaults.SameSpaceAuthorizationPolicy); ++ .RequireAuthorization(CertificateAuthorizationPolicies.SameSpace); ``` Program.cs (client-side): @@ -1491,18 +1568,40 @@ Program.cs (client-side): -using Steeltoe.Security.Authentication.CloudFoundry; +using Steeltoe.Security.Authorization.Certificate; -var builder = WebApplication.CreateBuilder(args); +const string orgId = "a8fef16f-94c0-49e3-aa0b-ced7c3da6229"; +const string spaceId = "122b942a-d7b9-4839-b26e-836654b9785f"; --builder.Configuration.AddCloudFoundryContainerIdentity(); -+builder.Configuration.AddAppInstanceIdentityCertificate(); +var builder = WebApplication.CreateBuilder(args); --builder.Services.AddHttpClient("withCertificate", (services, client) => +-builder.Configuration.AddCloudFoundryContainerIdentity(orgId, spaceId); ++builder.Configuration.AddAppInstanceIdentityCertificate(new Guid(orgId), new Guid(spaceId)); +-builder.Services.AddCloudFoundryContainerIdentity(); +builder.Services +- .AddHttpClient((services, client) => -{ +- client.BaseAddress = new Uri("http://example-service/") - var options = services.GetRequiredService>(); - var b64 = Convert.ToBase64String(options.Value.Certificate.Export(X509ContentType.Cert)); - client.DefaultRequestHeaders.Add("X-Client-Cert", b64); -}); -+builder.Services.AddHttpClient("withCertificate").AddAppInstanceIdentityCertificate(); ++ .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("http://example-service/")) ++ .AddAppInstanceIdentityCertificate(); + +var app = builder.Build(); + +var pingClient = app.Services.GetRequiredService(); +string orgResponse = await pingClient.GetPingAsync("org"); +Console.WriteLine($"Org response: {orgResponse}"); +string spaceResponse = await pingClient.GetPingAsync("space"); +Console.WriteLine($"Space response: {spaceResponse}"); + +public class PingClient(HttpClient httpClient) +{ + public async Task GetPingAsync(string matchType) + { + return await httpClient.GetStringAsync($"same{matchType}"); + } +} ``` ### DataProtection Key Store using Redis/Valkey From 6d5a71c339a5671557196634b2ba9a650177d2fd Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 29 Aug 2025 10:59:54 -0500 Subject: [PATCH 13/18] Update docs/docs/v4/welcome/migrate-quick-steps.md Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- docs/docs/v4/welcome/migrate-quick-steps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index f37ce890..2782ca6a 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1255,7 +1255,7 @@ Use [CredHub Service Broker](https://techdocs.broadcom.com/us/en/vmware-tanzu/pl OAuth support has been removed from Steeltoe in v4. Use OpenID Connect instead. -Consider migrating to OpenID Connect on Steeltoe v3 before moving to v4. +Before migrating to Steeltoe v4, apply the following changes to migrate from OAuth to OpenID Connect using Steeltoe v3. appsettings.json: From 9ff6e4765bc9ef3c89f49f7a696149e00d160eae Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 29 Aug 2025 11:30:44 -0500 Subject: [PATCH 14/18] config value sorting, use a server-port instead of potentially implied discovery, add note about cert header --- docs/docs/v4/welcome/migrate-quick-steps.md | 47 +++++++++++++++------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 2782ca6a..71fc2281 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1267,12 +1267,12 @@ appsettings.json: "Client": { - "AuthDomain": "http://localhost:8080", + "Authority": "http://localhost:8080/uaa", - "CallbackPath": "/signin-oidc", - "ClientId": "steeltoesamplesclient", - "ClientSecret": "client_secret", + "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", + "RequireHttpsMetadata": false, -+ "AdditionalScopes": "sampleapi.read" ++ "AdditionalScopes": "sampleapi.read", + "CallbackPath": "/signin-oidc", + "ClientId": "steeltoesamplesclient", + "ClientSecret": "client_secret" } } } @@ -1345,9 +1345,9 @@ appsettings.json: - "ClientId": "steeltoesamplesclient", - "ClientSecret": "client_secret", - "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", -- "AdditionalScopes": "sampleapi.read", +- "RequireHttpsMetadata": false, - "SaveTokens": true, -- "RequireHttpsMetadata": false +- "AdditionalScopes": "sampleapi.read" - } - } - } @@ -1531,9 +1531,9 @@ var builder = WebApplication.CreateBuilder(args); -builder.Configuration.AddCloudFoundryContainerIdentity(orgId, spaceId); +builder.Configuration.AddAppInstanceIdentityCertificate(new Guid(orgId), new Guid(spaceId)); --builder.Services.AddCloudFoundryCertificateAuth(options => options.CertificateHeader = "X-Client-Cert"); +-builder.Services.AddCloudFoundryCertificateAuth(options => options.CertificateHeader = "X-Forwarded-Client-Cert"); +builder.Services.AddAuthentication().AddCertificate(); -+builder.Services.AddAuthorizationBuilder().AddOrgAndSpacePolicies(); ++builder.Services.AddAuthorizationBuilder().AddOrgAndSpacePolicies("X-Forwarded-Client-Cert"); var app = builder.Build(); @@ -1558,6 +1558,24 @@ app.MapGet("/sameSpace", async httpContext => + .RequireAuthorization(CertificateAuthorizationPolicies.SameSpace); ``` +> [!NOTE] +> Prior to Steeltoe 3.3.0, Steeltoe Certificate Auth used the header `X-Forwarded-Client-Cert`, which was not configurable. +> The code shown above is provided for compatibility between the versions. The preferred header name is `X-Client-Cert`. +> In Steeltoe 4.0, the default header is `X-Client-Cert`, so the parameter can be omitted if cross-compatibility is not required. + +launchsettings.json (server-side): + +```diff +{ + "profiles": { + "http": { + "commandName": "Project", + "applicationUrl": "https://+:7107" // bind to all host names and IP addresses + } + } +} +``` + Program.cs (client-side): ```diff @@ -1579,13 +1597,13 @@ var builder = WebApplication.CreateBuilder(args); builder.Services - .AddHttpClient((services, client) => -{ -- client.BaseAddress = new Uri("http://example-service/") +- client.BaseAddress = new Uri("https://localhost:7107") - var options = services.GetRequiredService>(); - var b64 = Convert.ToBase64String(options.Value.Certificate.Export(X509ContentType.Cert)); -- client.DefaultRequestHeaders.Add("X-Client-Cert", b64); +- client.DefaultRequestHeaders.Add("X-Forwarded-Client-Cert", b64); -}); -+ .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("http://example-service/")) -+ .AddAppInstanceIdentityCertificate(); ++ .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("https://localhost:7107")) ++ .AddAppInstanceIdentityCertificate("X-Forwarded-Client-Cert"); var app = builder.Build(); @@ -1604,6 +1622,11 @@ public class PingClient(HttpClient httpClient) } ``` +> [!NOTE] +> Prior to Steeltoe 3.3.0, Steeltoe Certificate Auth used the header `X-Forwarded-Client-Cert`, which was not configurable. +> The code shown above is provided for compatibility between the versions. The preferred header name is `X-Client-Cert`. +> In Steeltoe 4.0, the default header is `X-Client-Cert`, so the parameter can be omitted if cross-compatibility is not required. + ### DataProtection Key Store using Redis/Valkey ```diff From b399f9cf503c1b027f14ede1ade5b46e20aa7af0 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 2 Sep 2025 10:10:05 -0500 Subject: [PATCH 15/18] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- docs/docs/v4/welcome/migrate-quick-steps.md | 43 ++++++++++----------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 553e1572..3f2babbf 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1498,7 +1498,7 @@ app.MapGet("/jwt", async httpContext => { httpContext.Response.StatusCode = 200; httpContext.Response.ContentType = "text/plain"; - await httpContext.Response.WriteAsync("JWT is valid and contains the required claim"); + await httpContext.Response.WriteAsync("JWT is valid and contains the required claim."); }).RequireAuthorization("sampleapi.read"); app.Run(); ``` @@ -1527,10 +1527,8 @@ const string orgId = "a8fef16f-94c0-49e3-aa0b-ced7c3da6229"; const string spaceId = "122b942a-d7b9-4839-b26e-836654b9785f"; var builder = WebApplication.CreateBuilder(args); - -builder.Configuration.AddCloudFoundryContainerIdentity(orgId, spaceId); +builder.Configuration.AddAppInstanceIdentityCertificate(new Guid(orgId), new Guid(spaceId)); - -builder.Services.AddCloudFoundryCertificateAuth(options => options.CertificateHeader = "X-Forwarded-Client-Cert"); +builder.Services.AddAuthentication().AddCertificate(); +builder.Services.AddAuthorizationBuilder().AddOrgAndSpacePolicies("X-Forwarded-Client-Cert"); @@ -1539,20 +1537,19 @@ var app = builder.Build(); -app.UseCloudFoundryCertificateAuth(); +app.UseCertificateAuthorization(); -app.MapGet("/sameOrg", async httpContext => +app.MapGet("/test-same-org", async httpContext => { httpContext.Response.StatusCode = 200; httpContext.Response.ContentType = "text/plain"; - await httpContext.Response.WriteAsync("Client and server identity certificates have matching Org values"); + await httpContext.Response.WriteAsync("Client and server identity certificates have matching Org values."); }) - .RequireAuthorization(CloudFoundryDefaults.SameOrganizationAuthorizationPolicy); + .RequireAuthorization(CertificateAuthorizationPolicies.SameOrg); - -app.MapGet("/sameSpace", async httpContext => +app.MapGet("/test-same-space", async httpContext => { httpContext.Response.StatusCode = 200; httpContext.Response.ContentType = "text/plain"; - await httpContext.Response.WriteAsync("Client and server identity certificates have matching Space values"); + await httpContext.Response.WriteAsync("Client and server identity certificates have matching Space values."); }) - .RequireAuthorization(CloudFoundryDefaults.SameSpaceAuthorizationPolicy); + .RequireAuthorization(CertificateAuthorizationPolicies.SameSpace); @@ -1570,7 +1567,7 @@ launchsettings.json (server-side): "profiles": { "http": { "commandName": "Project", - "applicationUrl": "https://+:7107" // bind to all host names and IP addresses + "applicationUrl": "https://localhost:7107" } } } @@ -1595,29 +1592,29 @@ var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddAppInstanceIdentityCertificate(new Guid(orgId), new Guid(spaceId)); -builder.Services.AddCloudFoundryContainerIdentity(); builder.Services -- .AddHttpClient((services, client) => --{ -- client.BaseAddress = new Uri("https://localhost:7107") -- var options = services.GetRequiredService>(); -- var b64 = Convert.ToBase64String(options.Value.Certificate.Export(X509ContentType.Cert)); -- client.DefaultRequestHeaders.Add("X-Forwarded-Client-Cert", b64); --}); -+ .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("https://localhost:7107")) +- .AddHttpClient((services, client) => +- { +- client.BaseAddress = new Uri("https://localhost:7107"); +- var options = services.GetRequiredService>(); +- var b64 = Convert.ToBase64String(options.Value.Certificate.Export(X509ContentType.Cert)); +- client.DefaultRequestHeaders.Add("X-Forwarded-Client-Cert", b64); +- }); ++ .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("https://localhost:7107")) + .AddAppInstanceIdentityCertificate("X-Forwarded-Client-Cert"); var app = builder.Build(); -var pingClient = app.Services.GetRequiredService(); -string orgResponse = await pingClient.GetPingAsync("org"); +var testClient = app.Services.GetRequiredService(); +string orgResponse = await testClient.GetAsync("/test-same-org"); Console.WriteLine($"Org response: {orgResponse}"); -string spaceResponse = await pingClient.GetPingAsync("space"); +string spaceResponse = await testClient.GetAsync("/test-same-space"); Console.WriteLine($"Space response: {spaceResponse}"); -public class PingClient(HttpClient httpClient) +public class TestClient(HttpClient httpClient) { - public async Task GetPingAsync(string matchType) + public async Task GetAsync(string requestPath) { - return await httpClient.GetStringAsync($"same{matchType}"); + return await httpClient.GetStringAsync(requestPath); } } ``` From 4c9e7f2e936844c95988c33d6fdbfb2b19847d7e Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 2 Sep 2025 10:11:03 -0500 Subject: [PATCH 16/18] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- docs/docs/v4/welcome/migrate-quick-steps.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 3f2babbf..2670d062 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1477,7 +1477,6 @@ var builder = WebApplication.CreateBuilder(args); builder.AddCloudFoundryConfiguration(); +builder.Configuration.AddCloudFoundryServiceBindings(); - builder.Services.AddAuthentication() - .AddCloudFoundryJwtBearer(builder.Configuration); + .AddJwtBearer().ConfigureJwtBearerForCloudFoundry(); @@ -1494,7 +1493,7 @@ var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); -app.MapGet("/jwt", async httpContext => +app.MapGet("/test-jwt", async httpContext => { httpContext.Response.StatusCode = 200; httpContext.Response.ContentType = "text/plain"; From f400abee93af1ce780e29e4f92d819d3be5a5bf2 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 2 Sep 2025 11:43:05 -0500 Subject: [PATCH 17/18] add minimal auth endpoint, remove savetokens, re-order things - also remove JwtKeyUrl, which now matches default with the updated UAA path from new image --- docs/docs/v4/welcome/migrate-quick-steps.md | 98 +++++++++++---------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 2670d062..8bc19e5c 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1266,7 +1266,7 @@ appsettings.json: "Oauth2": { "Client": { - "AuthDomain": "http://localhost:8080", -+ "Authority": "http://localhost:8080/uaa", ++ "Authority": "http://localhost:8080", + "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", + "RequireHttpsMetadata": false, + "AdditionalScopes": "sampleapi.read", @@ -1293,16 +1293,15 @@ using Steeltoe.Security.Authentication.CloudFoundry; var builder = WebApplication.CreateBuilder(args); builder.AddCloudFoundryConfiguration(); builder.Services.AddAuthentication(options => -{ - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; -}) + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; + }) .AddCookie(options => options.AccessDeniedPath = new PathString("/Home/AccessDenied")) - .AddCloudFoundryOAuth(builder.Configuration); + .AddCloudFoundryOpenIdConnect(builder.Configuration); builder.Services.AddAuthorizationBuilder() - .AddPolicy("read", policy => policy.RequireClaim("scope", "sampleapi.read")) - .AddPolicy("write", policy => policy.RequireClaim("scope", "sampleapi.write")); + .AddPolicy("read", policy => policy.RequireClaim("scope", "sampleapi.read")); var app = builder.Build(); @@ -1314,6 +1313,12 @@ app.UseForwardedHeaders(new ForwardedHeadersOptions app.UseAuthentication(); app.UseAuthorization(); +app.MapGet("/test-auth", async httpContext => + { + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("You are logged in and carry the required claim."); + }).RequireAuthorization("read"); ``` ### OpenID Connect @@ -1340,28 +1345,26 @@ appsettings.json: - "Security": { - "Oauth2": { - "Client": { -- "Authority": "http://localhost:8080/uaa", -- "CallbackPath": "/signin-oidc", -- "ClientId": "steeltoesamplesclient", -- "ClientSecret": "client_secret", +- "Authority": "http://localhost:8080", - "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", - "RequireHttpsMetadata": false, -- "SaveTokens": true, -- "AdditionalScopes": "sampleapi.read" +- "AdditionalScopes": "sampleapi.read", +- "CallbackPath": "/signin-oidc", +- "ClientId": "steeltoesamplesclient", +- "ClientSecret": "client_secret" - } - } - } + "Authentication": { + "Schemes": { + "OpenIdConnect": { -+ "Authority": "http://localhost:8080/uaa", -+ "CallbackPath": "/signin-oidc", -+ "ClientId": "steeltoesamplesclient", -+ "ClientSecret": "client_secret", ++ "Authority": "http://localhost:8080", + "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", + "RequireHttpsMetadata": false, -+ "SaveTokens": true, -+ "Scope": [ "openid", "sampleapi.read" ] ++ "Scope": [ "openid", "sampleapi.read" ], ++ "CallbackPath": "/signin-oidc", ++ "ClientId": "steeltoesamplesclient", ++ "ClientSecret": "client_secret" + } + } + } @@ -1388,17 +1391,16 @@ var builder = WebApplication.CreateBuilder(args); builder.AddCloudFoundryConfiguration(); +builder.Configuration.AddCloudFoundryServiceBindings(); builder.Services.AddAuthentication(options => -{ - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; -- options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; -+ options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; -}) + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; +- options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; ++ options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) .AddCookie(options => options.AccessDeniedPath = new PathString("/Home/AccessDenied")) - .AddCloudFoundryOpenIdConnect(builder.Configuration); + .AddOpenIdConnect().ConfigureOpenIdConnectForCloudFoundry(); builder.Services.AddAuthorizationBuilder() - .AddPolicy("read", policy => policy.RequireClaim("scope", "sampleapi.read")) - .AddPolicy("write", policy => policy.RequireClaim("scope", "sampleapi.write")); + .AddPolicy("read", policy => policy.RequireClaim("scope", "sampleapi.read")); var app = builder.Build(); @@ -1409,6 +1411,13 @@ var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); + +app.MapGet("/test-auth", async httpContext => + { + httpContext.Response.StatusCode = 200; + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("You are logged in and carry the required claim."); + }).RequireAuthorization("read"); ``` ### JWT Bearer @@ -1436,11 +1445,10 @@ appsettings.json: - "Oauth2": { - "Client": { - "AuthDomain": "http://localhost:8080", -- "ClientId": "steeltoesamplesserver", -- "ClientSecret": "server_secret", -- "JwtKeyUrl": "http://localhost:8080/token_keys", - "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", -- "RequireHttpsMetadata": false +- "RequireHttpsMetadata": false, +- "ClientId": "steeltoesamplesserver", +- "ClientSecret": "server_secret" - } - } - } @@ -1448,10 +1456,10 @@ appsettings.json: + "Schemes": { + "Bearer": { + "Authority": "http://localhost:8080", -+ "ClientId": "steeltoesamplesserver", -+ "ClientSecret": "server_secret", + "MetadataAddress": "http://localhost:8080/.well-known/openid-configuration", + "RequireHttpsMetadata": false, ++ "ClientId": "steeltoesamplesserver", ++ "ClientSecret": "server_secret", + "ValidAudiences": [ "sampleapi" ] + } + } @@ -1515,6 +1523,19 @@ Project file: ``` +launchsettings.json (server-side): + +```diff +{ + "profiles": { + "http": { + "commandName": "Project", + "applicationUrl": "https://localhost:7107" + } + } +} +``` + Program.cs (server-side): ```diff @@ -1559,19 +1580,6 @@ app.MapGet("/test-same-space", async httpContext => > The code shown above is provided for compatibility between the versions. The preferred header name is `X-Client-Cert`. > In Steeltoe 4.0, the default header is `X-Client-Cert`, so the parameter can be omitted if cross-compatibility is not required. -launchsettings.json (server-side): - -```diff -{ - "profiles": { - "http": { - "commandName": "Project", - "applicationUrl": "https://localhost:7107" - } - } -} -``` - Program.cs (client-side): ```diff From 2fc2ec7da405fc974a54c2ce2aa4ca397a91ac01 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 3 Sep 2025 15:45:47 -0500 Subject: [PATCH 18/18] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- docs/docs/v4/welcome/migrate-quick-steps.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md index 8bc19e5c..3a4a5406 100644 --- a/docs/docs/v4/welcome/migrate-quick-steps.md +++ b/docs/docs/v4/welcome/migrate-quick-steps.md @@ -1319,6 +1319,8 @@ app.MapGet("/test-auth", async httpContext => httpContext.Response.ContentType = "text/plain"; await httpContext.Response.WriteAsync("You are logged in and carry the required claim."); }).RequireAuthorization("read"); + +app.Run(); ``` ### OpenID Connect @@ -1418,6 +1420,8 @@ app.MapGet("/test-auth", async httpContext => httpContext.Response.ContentType = "text/plain"; await httpContext.Response.WriteAsync("You are logged in and carry the required claim."); }).RequireAuthorization("read"); + +app.Run(); ``` ### JWT Bearer @@ -1507,6 +1511,7 @@ app.MapGet("/test-jwt", async httpContext => httpContext.Response.ContentType = "text/plain"; await httpContext.Response.WriteAsync("JWT is valid and contains the required claim."); }).RequireAuthorization("sampleapi.read"); + app.Run(); ``` @@ -1557,6 +1562,7 @@ var app = builder.Build(); -app.UseCloudFoundryCertificateAuth(); +app.UseCertificateAuthorization(); + app.MapGet("/test-same-org", async httpContext => { httpContext.Response.StatusCode = 200; @@ -1573,6 +1579,8 @@ app.MapGet("/test-same-space", async httpContext => }) - .RequireAuthorization(CloudFoundryDefaults.SameSpaceAuthorizationPolicy); + .RequireAuthorization(CertificateAuthorizationPolicies.SameSpace); + +app.Run(); ``` > [!NOTE]