Skip to content

Commit a682d81

Browse files
committed
fix: address Copilot review issues in OpenIddict JWKS demo app and CLI command
1 parent 2c5b124 commit a682d81

4 files changed

Lines changed: 73 additions & 41 deletions

File tree

framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateJwksCommand.cs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.Security.Cryptography;
44
using System.Text;
5+
using System.Text.Json;
56
using System.Threading.Tasks;
67
using Microsoft.Extensions.Logging;
78
using Microsoft.Extensions.Logging.Abstractions;
@@ -21,7 +22,7 @@ public GenerateJwksCommand()
2122
Logger = NullLogger<GenerateJwksCommand>.Instance;
2223
}
2324

24-
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
25+
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
2526
{
2627
var outputDir = commandLineArgs.Options.GetOrNull("output", "o")
2728
?? Directory.GetCurrentDirectory();
@@ -33,13 +34,13 @@ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
3334
if (!int.TryParse(keySizeStr, out var keySize) || (keySize != 2048 && keySize != 4096))
3435
{
3536
Logger.LogError("Invalid key size '{0}'. Supported values: 2048, 4096.", keySizeStr);
36-
return;
37+
return Task.CompletedTask;
3738
}
3839

3940
if (!IsValidAlgorithm(alg))
4041
{
4142
Logger.LogError("Invalid algorithm '{0}'. Supported values: RS256, RS384, RS512, PS256, PS384, PS512.", alg);
42-
return;
43+
return Task.CompletedTask;
4344
}
4445

4546
if (!Directory.Exists(outputDir))
@@ -49,16 +50,17 @@ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
4950

5051
Logger.LogInformation("Generating RSA {0}-bit key pair (algorithm: {1})...", keySize, alg);
5152

52-
using var rsa = RSA.Create(keySize);
53+
using var rsa = RSA.Create();
54+
rsa.KeySize = keySize;
5355

5456
var jwksJson = BuildJwksJson(rsa, alg, kid);
5557
var privateKeyPem = ExportPrivateKeyPem(rsa);
5658

5759
var jwksFilePath = Path.Combine(outputDir, $"{filePrefix}.json");
5860
var privateKeyFilePath = Path.Combine(outputDir, $"{filePrefix}-private.pem");
5961

60-
await File.WriteAllTextAsync(jwksFilePath, jwksJson, Encoding.UTF8);
61-
await File.WriteAllTextAsync(privateKeyFilePath, privateKeyPem, Encoding.UTF8);
62+
File.WriteAllText(jwksFilePath, jwksJson, Encoding.UTF8);
63+
File.WriteAllText(privateKeyFilePath, privateKeyPem, Encoding.UTF8);
6264

6365
Logger.LogInformation("");
6466
Logger.LogInformation("Generated files:");
@@ -72,7 +74,7 @@ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
7274
Logger.LogInformation("IMPORTANT: Keep the private key file safe. Never share it or commit it to source control.");
7375
Logger.LogInformation(" The JWKS file contains only the public key and is safe to share.");
7476

75-
await Task.CompletedTask;
77+
return Task.CompletedTask;
7678
}
7779

7880
private static string BuildJwksJson(RSA rsa, string alg, string kid)
@@ -82,27 +84,34 @@ private static string BuildJwksJson(RSA rsa, string alg, string kid)
8284
var n = Base64UrlEncode(parameters.Modulus);
8385
var e = Base64UrlEncode(parameters.Exponent);
8486

85-
var sb = new StringBuilder();
86-
sb.AppendLine("{");
87-
sb.AppendLine(" \"keys\": [");
88-
sb.AppendLine(" {");
89-
sb.AppendLine(" \"kty\": \"RSA\",");
90-
sb.AppendLine(" \"use\": \"sig\",");
91-
sb.AppendLine($" \"kid\": \"{kid}\",");
92-
sb.AppendLine($" \"alg\": \"{alg}\",");
93-
sb.AppendLine($" \"n\": \"{n}\",");
94-
sb.AppendLine($" \"e\": \"{e}\"");
95-
sb.AppendLine(" }");
96-
sb.AppendLine(" ]");
97-
sb.Append("}");
98-
99-
return sb.ToString();
87+
using var stream = new System.IO.MemoryStream();
88+
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
89+
90+
writer.WriteStartObject();
91+
writer.WriteStartArray("keys");
92+
writer.WriteStartObject();
93+
writer.WriteString("kty", "RSA");
94+
writer.WriteString("use", "sig");
95+
writer.WriteString("kid", kid);
96+
writer.WriteString("alg", alg);
97+
writer.WriteString("n", n);
98+
writer.WriteString("e", e);
99+
writer.WriteEndObject();
100+
writer.WriteEndArray();
101+
writer.WriteEndObject();
102+
writer.Flush();
103+
104+
return Encoding.UTF8.GetString(stream.ToArray());
100105
}
101106

102107
private static string ExportPrivateKeyPem(RSA rsa)
103108
{
104109
#if NET5_0_OR_GREATER
105110
return rsa.ExportPkcs8PrivateKeyPem();
111+
#elif NETSTANDARD2_0
112+
// RSA.ExportPkcs8PrivateKey() was introduced in .NET Standard 2.1.
113+
// The ABP CLI always runs on .NET 5+, so this path is never reached at runtime.
114+
throw new PlatformNotSupportedException("Private key export requires .NET Standard 2.1 or later.");
106115
#else
107116
var privateKeyBytes = rsa.ExportPkcs8PrivateKey();
108117
var base64 = Convert.ToBase64String(privateKeyBytes, Base64FormattingOptions.InsertLineBreaks);
@@ -120,7 +129,8 @@ private static string Base64UrlEncode(byte[] input)
120129

121130
private static bool IsValidAlgorithm(string alg)
122131
{
123-
return alg is "RS256" or "RS384" or "RS512" or "PS256" or "PS384" or "PS512";
132+
return alg == "RS256" || alg == "RS384" || alg == "RS512" ||
133+
alg == "PS256" || alg == "PS384" || alg == "PS512";
124134
}
125135

126136
public string GetUsageInfo()

modules/openiddict/app/OpenIddict.Demo.Client.Console/OpenIddict.Demo.Client.Console.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<!-- jwks-private.pem is the private key for the AbpConsoleAppWithJwks client, used to sign JWT client assertions.
1818
The corresponding public key (jwks.json) is registered on the server side (OpenIddict.Demo.Server).
1919
Both files originate from the parent app/ directory. -->
20-
<None Include="..\jwks-private.pem">
20+
<None Include="..\jwks-private.pem" Condition="Exists('..\jwks-private.pem')">
2121
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2222
</None>
2323
</ItemGroup>

modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,20 @@
215215
using var rsaKey = RSA.Create();
216216
rsaKey.ImportFromPem(await File.ReadAllTextAsync(privateKeyPath));
217217

218-
// The kid must match the "kid" field in the JWKS registered on the server.
219-
var signingKey = new RsaSecurityKey(rsaKey) { KeyId = "6444499c0f3e43c98db72bb85db5edee" };
218+
// Read the kid dynamically from the JWKS file so it stays in sync with the server-registered JWKS.
219+
string? signingKid = null;
220+
var jwksForKidPath = Path.Combine(AppContext.BaseDirectory, "jwks.json");
221+
if (File.Exists(jwksForKidPath))
222+
{
223+
using var jwksDoc = JsonDocument.Parse(await File.ReadAllTextAsync(jwksForKidPath));
224+
if (jwksDoc.RootElement.TryGetProperty("keys", out var keysElem) &&
225+
keysElem.GetArrayLength() > 0 &&
226+
keysElem[0].TryGetProperty("kid", out var kidElem))
227+
{
228+
signingKid = kidElem.GetString();
229+
}
230+
}
231+
var signingKey = new RsaSecurityKey(rsaKey) { KeyId = signingKid };
220232
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256);
221233

222234
var now = DateTime.UtcNow;

modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -167,23 +167,33 @@ await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor
167167
// and used by OpenIddict.Demo.Client.Console to sign JWT client assertions.
168168
// Both files are generated with: abp generate-jwks
169169
var jwksPath = Path.Combine(AppContext.BaseDirectory, "jwks.json");
170-
var jwks = new JsonWebKeySet(await File.ReadAllTextAsync(jwksPath));
171-
172-
await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor
170+
if (!File.Exists(jwksPath))
173171
{
174-
ApplicationType = OpenIddictConstants.ApplicationTypes.Web,
175-
ClientId = "AbpConsoleAppWithJwks",
176-
ClientType = OpenIddictConstants.ClientTypes.Confidential,
177-
DisplayName = "Abp Console App (private_key_jwt)",
178-
JsonWebKeySet = jwks,
179-
Permissions =
172+
Console.WriteLine(
173+
$"[OpenIddict] WARNING: JWKS file not found at '{jwksPath}'. " +
174+
"Skipping creation of the 'AbpConsoleAppWithJwks' client. " +
175+
"Run 'abp generate-jwks' in the app/ directory to generate the key pair.");
176+
}
177+
else
178+
{
179+
var jwks = new JsonWebKeySet(await File.ReadAllTextAsync(jwksPath));
180+
181+
await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor
180182
{
181-
OpenIddictConstants.Permissions.Endpoints.Token,
182-
OpenIddictConstants.Permissions.Endpoints.Introspection,
183-
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
184-
OpenIddictConstants.Permissions.Prefixes.Scope + "AbpAPI"
185-
}
186-
});
183+
ApplicationType = OpenIddictConstants.ApplicationTypes.Web,
184+
ClientId = "AbpConsoleAppWithJwks",
185+
ClientType = OpenIddictConstants.ClientTypes.Confidential,
186+
DisplayName = "Abp Console App (private_key_jwt)",
187+
JsonWebKeySet = jwks,
188+
Permissions =
189+
{
190+
OpenIddictConstants.Permissions.Endpoints.Token,
191+
OpenIddictConstants.Permissions.Endpoints.Introspection,
192+
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
193+
OpenIddictConstants.Permissions.Prefixes.Scope + "AbpAPI"
194+
}
195+
});
196+
}
187197
}
188198

189199
if (await _applicationManager.FindByClientIdAsync("Swagger") == null)

0 commit comments

Comments
 (0)