Skip to content

Commit 3f7ec21

Browse files
congiuluccongiulucmswaldekmastykarz
authored
Added SigningKey parameter to JWT Token generator (fixes #913) (#914)
* Added SigningKey parameter to JWT Token generator fixes #913 * Added BadRequest result if signing key lenght is lower then 32 or empty --------- Co-authored-by: Luca Congiu <luca.congiu@microsoft.com> Co-authored-by: Waldek Mastykarz <waldek@mastykarz.nl>
1 parent 181aaac commit 3f7ec21

File tree

6 files changed

+43
-10
lines changed

6 files changed

+43
-10
lines changed

dev-proxy/ApiControllers/ProxyController.cs

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ public void StopProxy()
5555
[HttpPost("createJwtToken")]
5656
public IActionResult CreateJwtToken([FromBody] JwtOptions jwtOptions)
5757
{
58+
if (jwtOptions.SigningKey != null && jwtOptions.SigningKey.Length < 32)
59+
{
60+
return BadRequest("The specified signing key is too short. A signing key must be at least 32 characters.");
61+
}
62+
5863
var token = JwtTokenGenerator.CreateToken(jwtOptions);
5964

6065
return Ok(new JwtInfo { Token = token });

dev-proxy/CommandHandlers/JwtBinder.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
using System.CommandLine.Binding;
77

88
namespace Microsoft.DevProxy.CommandHandlers
9-
{
10-
public class JwtBinder(Option<string> nameOption, Option<IEnumerable<string>> audiencesOption, Option<string> issuerOption, Option<IEnumerable<string>> rolesOption, Option<IEnumerable<string>> scopesOption, Option<Dictionary<string, string>> claimsOption, Option<double> validForOption) : BinderBase<JwtOptions>
9+
{
10+
public class JwtBinder(Option<string> nameOption, Option<IEnumerable<string>> audiencesOption, Option<string> issuerOption, Option<IEnumerable<string>> rolesOption, Option<IEnumerable<string>> scopesOption, Option<Dictionary<string, string>> claimsOption, Option<double> validForOption, Option<string> signingKeyOption) : BinderBase<JwtOptions>
1111
{
1212
private readonly Option<string> _nameOption = nameOption;
1313
private readonly Option<IEnumerable<string>> _audiencesOption = audiencesOption;
@@ -16,6 +16,7 @@ public class JwtBinder(Option<string> nameOption, Option<IEnumerable<string>> au
1616
private readonly Option<IEnumerable<string>> _scopesOption = scopesOption;
1717
private readonly Option<Dictionary<string, string>> _claimsOption = claimsOption;
1818
private readonly Option<double> _validForOption = validForOption;
19+
private readonly Option<string> _signingKeyOption = signingKeyOption;
1920

2021
protected override JwtOptions GetBoundValue(BindingContext bindingContext)
2122
{
@@ -27,7 +28,8 @@ protected override JwtOptions GetBoundValue(BindingContext bindingContext)
2728
Roles = bindingContext.ParseResult.GetValueForOption(_rolesOption),
2829
Scopes = bindingContext.ParseResult.GetValueForOption(_scopesOption),
2930
Claims = bindingContext.ParseResult.GetValueForOption(_claimsOption),
30-
ValidFor = bindingContext.ParseResult.GetValueForOption(_validForOption)
31+
ValidFor = bindingContext.ParseResult.GetValueForOption(_validForOption),
32+
SigningKey = bindingContext.ParseResult.GetValueForOption(_signingKeyOption)
3133
};
3234
}
3335
}

dev-proxy/Jwt/JwtCreatorOptions.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Security.Cryptography;
5+
46
namespace Microsoft.DevProxy.Jwt;
57

68
internal sealed record JwtCreatorOptions
@@ -14,6 +16,7 @@ internal sealed record JwtCreatorOptions
1416
public required IEnumerable<string> Roles { get; init; }
1517
public required IEnumerable<string> Scopes { get; init; }
1618
public required Dictionary<string, string> Claims { get; init; }
19+
public required string SigningKey { get; init; }
1720

1821
public static JwtCreatorOptions Create(JwtOptions options)
1922
{
@@ -27,7 +30,8 @@ public static JwtCreatorOptions Create(JwtOptions options)
2730
Scopes = options.Scopes ?? [],
2831
Claims = options.Claims ?? [],
2932
NotBefore = DateTime.UtcNow,
30-
ExpiresOn = DateTime.UtcNow.AddMinutes(options.ValidFor ?? 60)
33+
ExpiresOn = DateTime.UtcNow.AddMinutes(options.ValidFor ?? 60),
34+
SigningKey = (string.IsNullOrEmpty(options.SigningKey) ? RandomNumberGenerator.GetHexString(32) : options.SigningKey)
3135
};
3236
}
3337
}

dev-proxy/Jwt/JwtOptions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public class JwtOptions
1212
public IEnumerable<string>? Scopes { get; set; }
1313
public Dictionary<string, string>? Claims { get; set; }
1414
public double? ValidFor { get; set; }
15+
public string? SigningKey { get; set; }
1516
}

dev-proxy/Jwt/JwtTokenGenerator.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.IdentityModel.Tokens.Jwt;
5-
using System.Security.Cryptography;
5+
using System.Text;
66

77
namespace Microsoft.DevProxy.Jwt;
88

@@ -14,7 +14,7 @@ internal static string CreateToken(JwtOptions jwtOptions)
1414

1515
var jwtIssuer = new JwtIssuer(
1616
options.Issuer,
17-
RandomNumberGenerator.GetBytes(32)
17+
Encoding.UTF8.GetBytes(options.SigningKey)
1818
);
1919

2020
var jwtToken = jwtIssuer.CreateSecurityToken(options);

dev-proxy/ProxyHost.cs

+25-4
Original file line numberDiff line numberDiff line change
@@ -382,14 +382,15 @@ public RootCommand GetRootCommand(ILogger logger)
382382

383383
var jwtClaimsOption = new Option<Dictionary<string, string>>("--claims",
384384
description: "Claims to add to the token. Specify once for each claim in the format \"name:value\".",
385-
parseArgument: result => {
385+
parseArgument: result =>
386+
{
386387
var claims = new Dictionary<string, string>();
387388
foreach (var token in result.Tokens)
388389
{
389390
var claim = token.Value.Split(":");
390391

391392
if (claim.Length != 2)
392-
{
393+
{
393394
result.ErrorMessage = $"Invalid claim format: '{token.Value}'. Expected format is name:value.";
394395
return claims ?? [];
395396
}
@@ -416,7 +417,26 @@ public RootCommand GetRootCommand(ILogger logger)
416417
jwtValidForOption.AddAlias("-v");
417418
jwtCreateCommand.AddOption(jwtValidForOption);
418419

419-
jwtCreateCommand.SetHandler(
420+
var jwtSigningKeyOption = new Option<string>("--signing-key", "The signing key to sign the token. Minimum length is 32 characters.");
421+
jwtSigningKeyOption.AddAlias("-k");
422+
jwtSigningKeyOption.AddValidator(input =>
423+
{
424+
try
425+
{
426+
var value = input.GetValueForOption(jwtSigningKeyOption);
427+
if (string.IsNullOrWhiteSpace(value) || value.Length < 32)
428+
{
429+
input.ErrorMessage = $"Requires option '--{jwtSigningKeyOption.Name}' to be at least 32 characters";
430+
}
431+
}
432+
catch (InvalidOperationException ex)
433+
{
434+
input.ErrorMessage = ex.Message;
435+
}
436+
});
437+
jwtCreateCommand.AddOption(jwtSigningKeyOption);
438+
439+
jwtCreateCommand.SetHandler(
420440
JwtCommandHandler.GetToken,
421441
new JwtBinder(
422442
jwtNameOption,
@@ -425,7 +445,8 @@ public RootCommand GetRootCommand(ILogger logger)
425445
jwtRolesOption,
426446
jwtScopesOption,
427447
jwtClaimsOption,
428-
jwtValidForOption
448+
jwtValidForOption,
449+
jwtSigningKeyOption
429450
)
430451
);
431452
jwtCommand.Add(jwtCreateCommand);

0 commit comments

Comments
 (0)