Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SigningKey parameter to JWT Token generator (fixes #913) #914

Merged
merged 3 commits into from
Oct 18, 2024

Conversation

congiuluc
Copy link
Contributor

The JWT Token generated by dev-proxy cannot be validated into an ASP.NET Core Web API.
Adding the signing key is possibile to validate the generated JWT token for a specific user and scope for a endpoint that requires authentication and authorization

Copy link
Collaborator

@waldekmastykarz waldekmastykarz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Before we continue, let's also update the API, where we expose the ability to create JWT, with the ability to pass the signing key too:

public IActionResult CreateJwtToken([FromBody] JwtOptions jwtOptions)
.

@waldekmastykarz
Copy link
Collaborator

I'll mark the PR as draft for now, and please mark it as ready for review when you update it.

@waldekmastykarz waldekmastykarz marked this pull request as draft October 16, 2024 06:30
@congiulucms
Copy link
Contributor

Thanks for the PR! Before we continue, let's also update the API, where we expose the ability to create JWT, with the ability to pass the signing key too:

public IActionResult CreateJwtToken([FromBody] JwtOptions jwtOptions)

.

Sorry @waldekmastykarz i don't undestand the API Update you refer, the SigningKey value is inside jwtOptions class so for action CreateJwtToken you can pass the SigningKey value directly in the body. Do you mean to pass the key differently?

@waldekmastykarz
Copy link
Collaborator

My bad, sorry! I totally missed that you've updated it already. I'll proceed with the review. Sorry for trouble.

@waldekmastykarz waldekmastykarz marked this pull request as ready for review October 16, 2024 15:06
Copy link
Collaborator

@waldekmastykarz waldekmastykarz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to extend the logic of creating the token using the API. Right now, if you specify a key that's too short, proxy crashes with an exception.

Repro:

POST http://localhost:8897/proxy/createJwtToken
Content-Type: application/json

{
    "name": "Dev Proxy",
    "audiences": [
        "https://myserver.com"
    ],
    "issuer": "dev-proxy",
    "roles": [
        "admin"
    ],
    "scopes": [
        "Post.Read",
        "Post.Write"
    ],
    "claims": {
        "claim1": "value",
        "claim2": "value"
    },
    "validFor": 60,
    "signingKey": "mySecret"
}

Expected:

400 Bad Request
The specified signing key is too short. A signing key must be at least 32 characters.

Current:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HN7EFMMH5MCK", Request id "0HN7EFMMH5MCK:00000001": An unhandled exception was thrown by the application.
      System.ArgumentOutOfRangeException: IDX10653: The encryption algorithm 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256' requires a key size of at least '128' bits. Key '[PII of type 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]', is of size: '64'. (Parameter 'key')
         at Microsoft.IdentityModel.Tokens.SymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm, Boolean cacheProvider)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
         at Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateEncodedSignature(String input, SigningCredentials signingCredentials)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityTokenPrivate(String issuer, String audience, IList`1 audiences, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, IDictionary`2 claimCollection, String tokenType, IDictionary`2 additionalHeaderClaims, IDictionary`2 additionalInnerHeaderClaims)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityTokenPrivate(String issuer, String audience, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, IDictionary`2 claimCollection, String tokenType, IDictionary`2 additionalHeaderClaims, IDictionary`2 additionalInnerHeaderClaims)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityToken(String issuer, String audience, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials)
         at Microsoft.DevProxy.Jwt.JwtIssuer.CreateSecurityToken(JwtCreatorOptions options) in /Users/waldek/github/microsoft/dev-proxy/dev-proxy/Jwt/JwtIssuer.cs:line 66
         at Microsoft.DevProxy.Jwt.JwtTokenGenerator.CreateToken(JwtOptions jwtOptions) in /Users/waldek/github/microsoft/dev-proxy/dev-proxy/Jwt/JwtTokenGenerator.cs:line 20
         at Microsoft.DevProxy.ApiControllers.ProxyController.CreateJwtToken(JwtOptions jwtOptions) in /Users/waldek/github/microsoft/dev-proxy/dev-proxy/ApiControllers/ProxyController.cs:line 58
         at lambda_method2(Closure, Object, Object[])
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

dev-proxy/ProxyHost.cs Outdated Show resolved Hide resolved
@waldekmastykarz waldekmastykarz marked this pull request as draft October 17, 2024 07:35
@congiulucms
Copy link
Contributor

We need to extend the logic of creating the token using the API. Right now, if you specify a key that's too short, proxy crashes with an exception.

Repro:

POST http://localhost:8897/proxy/createJwtToken
Content-Type: application/json

{
    "name": "Dev Proxy",
    "audiences": [
        "https://myserver.com"
    ],
    "issuer": "dev-proxy",
    "roles": [
        "admin"
    ],
    "scopes": [
        "Post.Read",
        "Post.Write"
    ],
    "claims": {
        "claim1": "value",
        "claim2": "value"
    },
    "validFor": 60,
    "signingKey": "mySecret"
}

Expected:

400 Bad Request
The specified signing key is too short. A signing key must be at least 32 characters.

Current:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HN7EFMMH5MCK", Request id "0HN7EFMMH5MCK:00000001": An unhandled exception was thrown by the application.
      System.ArgumentOutOfRangeException: IDX10653: The encryption algorithm 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256' requires a key size of at least '128' bits. Key '[PII of type 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]', is of size: '64'. (Parameter 'key')
         at Microsoft.IdentityModel.Tokens.SymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm, Boolean cacheProvider)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
         at Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateEncodedSignature(String input, SigningCredentials signingCredentials)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityTokenPrivate(String issuer, String audience, IList`1 audiences, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, IDictionary`2 claimCollection, String tokenType, IDictionary`2 additionalHeaderClaims, IDictionary`2 additionalInnerHeaderClaims)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityTokenPrivate(String issuer, String audience, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, IDictionary`2 claimCollection, String tokenType, IDictionary`2 additionalHeaderClaims, IDictionary`2 additionalInnerHeaderClaims)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityToken(String issuer, String audience, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials)
         at Microsoft.DevProxy.Jwt.JwtIssuer.CreateSecurityToken(JwtCreatorOptions options) in /Users/waldek/github/microsoft/dev-proxy/dev-proxy/Jwt/JwtIssuer.cs:line 66
         at Microsoft.DevProxy.Jwt.JwtTokenGenerator.CreateToken(JwtOptions jwtOptions) in /Users/waldek/github/microsoft/dev-proxy/dev-proxy/Jwt/JwtTokenGenerator.cs:line 20
         at Microsoft.DevProxy.ApiControllers.ProxyController.CreateJwtToken(JwtOptions jwtOptions) in /Users/waldek/github/microsoft/dev-proxy/dev-proxy/ApiControllers/ProxyController.cs:line 58
         at lambda_method2(Closure, Object, Object[])
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

Ok in case "signingKey":"" same error or goes ahead and generates a random key?

@waldekmastykarz
Copy link
Collaborator

Ok in case "signingKey":"" same error or goes ahead and generates a random key?

Let's use a random key if the user hasn't specified any key. If they specified an empty value, the API should respond with 400 Bad Request.

@waldekmastykarz waldekmastykarz marked this pull request as ready for review October 18, 2024 07:47
Copy link
Collaborator

@waldekmastykarz waldekmastykarz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works like a charm! 👏

@waldekmastykarz waldekmastykarz merged commit 3f7ec21 into microsoft:main Oct 18, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants