microsoft / dev-proxy

Dev Proxy is an API simulator that helps you effortlessly test your app beyond the happy path.
https://aka.ms/devproxy
MIT License
534 stars 62 forks source link

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

Closed congiuluc closed 1 month ago

congiuluc commented 1 month ago

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

waldekmastykarz commented 1 month ago

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

congiulucms commented 1 month ago

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:

https://github.com/microsoft/dev-proxy/blob/525d4f44722fe6017f5b09814c2ea9413518adb4/dev-proxy/ApiControllers/ProxyController.cs#L56

.

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 commented 1 month ago

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

congiulucms commented 1 month ago

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 commented 1 month ago

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.