How to customize the authorization error produced by OpenIddict?












0














I'm using OpenIddict for auth in a .NET Core 2 API. Client side I'm relying on any API errors to follow a custom scheme. However, when e.g. a refresh token has been outdated, I can't seem to find out how to customize the error sent back.



The /token endpoint is never reached, so the error is not under "my control".



The result of the request is a status code 400, with the following JSON:



{"error":"invalid_grant","error_description":"The specified refresh token is no longer valid."}


I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.



How can I properly customize the error or intercept to change it? Thanks!










share|improve this question



























    0














    I'm using OpenIddict for auth in a .NET Core 2 API. Client side I'm relying on any API errors to follow a custom scheme. However, when e.g. a refresh token has been outdated, I can't seem to find out how to customize the error sent back.



    The /token endpoint is never reached, so the error is not under "my control".



    The result of the request is a status code 400, with the following JSON:



    {"error":"invalid_grant","error_description":"The specified refresh token is no longer valid."}


    I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.



    How can I properly customize the error or intercept to change it? Thanks!










    share|improve this question

























      0












      0








      0







      I'm using OpenIddict for auth in a .NET Core 2 API. Client side I'm relying on any API errors to follow a custom scheme. However, when e.g. a refresh token has been outdated, I can't seem to find out how to customize the error sent back.



      The /token endpoint is never reached, so the error is not under "my control".



      The result of the request is a status code 400, with the following JSON:



      {"error":"invalid_grant","error_description":"The specified refresh token is no longer valid."}


      I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.



      How can I properly customize the error or intercept to change it? Thanks!










      share|improve this question













      I'm using OpenIddict for auth in a .NET Core 2 API. Client side I'm relying on any API errors to follow a custom scheme. However, when e.g. a refresh token has been outdated, I can't seem to find out how to customize the error sent back.



      The /token endpoint is never reached, so the error is not under "my control".



      The result of the request is a status code 400, with the following JSON:



      {"error":"invalid_grant","error_description":"The specified refresh token is no longer valid."}


      I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.



      How can I properly customize the error or intercept to change it? Thanks!







      asp.net-core openiddict






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 12 '18 at 12:04









      Magnus Andersson

      7518




      7518
























          2 Answers
          2






          active

          oldest

          votes


















          1














          You can use OpenIddict's event model to customize the token response payloads before they are written to the response stream. Here's an example:



          MyApplyTokenResponseHandler.cs



          public class MyApplyTokenResponseHandler : IOpenIddictServerEventHandler<OpenIddictServerEvents.ApplyTokenResponse>
          {
          public Task<OpenIddictServerEventState> HandleAsync(OpenIddictServerEvents.ApplyTokenResponse notification)
          {
          var response = notification.Context.Response;
          if (string.Equals(response.Error, OpenIddictConstants.Errors.InvalidGrant, StringComparison.Ordinal) &&
          !string.IsNullOrEmpty(response.ErrorDescription))
          {
          response.ErrorDescription = "Your customized error";
          }

          return Task.FromResult(OpenIddictServerEventState.Unhandled);
          }
          }


          Startup.cs



          services.AddOpenIddict()
          .AddCore(options =>
          {
          // ...
          })

          .AddServer(options =>
          {
          // ...
          options.AddEventHandler<MyApplyTokenResponseHandler>();
          })

          .AddValidation();





          share|improve this answer





















          • Great info, this allows me to customize the error description string. However, if I want to write my own raw json object to the response stream, how would I do that properly?
            – Magnus Andersson
            Nov 14 '18 at 16:55








          • 2




            You can either simply add your JSON items to the response object (e.g response["my_json_array"] = JArray.FromObject(new { 42 })) or completely take control of the response rendering by writing yourself the response to the output stream (notification.Context.HttpContext.Response.Body) and calling notification.Context.HandleResponse() to inform OpenIddict you don't want it to apply its default logic.
            – Pinpoint
            Nov 14 '18 at 20:25












          • WOW, a nice solution !
            – itminus
            Nov 15 '18 at 7:52



















          1















          The /token endpoint is never reached, so the error is not under "my control".




          In fact ,the /token is reached, and the parameter of grant_type equals refresh_token. But the rejection logic when refresh token expired is not processed by us. It is some kind of "hardcoded" in source code :



          if (token == null)
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }

          if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
          {
          if (!await TryRedeemTokenAsync(token))
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }
          }


          The context.Reject here comes from the assembly AspNet.Security.OpenIdConnect.Server.



          For more details, see source code on GitHub .




          I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.




          I've tried and I'm pretty sure we can use a custom middleware to catch all status codes. The key point is to detect the status code after the next() invocation:



          app.Use(async(context , next )=>{

          // passby all other end points
          if(! context.Request.Path.StartsWithSegments("/connect/token")){
          await next();
          return;
          }

          // since we might want to detect the Response.Body, I add some stream here .
          // if you only want to detect the status code , there's no need to use these streams
          Stream originalStream = context.Response.Body;
          var hijackedStream = new MemoryStream();
          context.Response.Body = hijackedStream;
          hijackedStream.Seek(0,SeekOrigin.Begin);

          await next();

          // if status code not 400 , pass by
          if(context.Response.StatusCode != 400){
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          return;
          }

          // read and custom the stream
          hijackedStream.Seek(0,SeekOrigin.Begin);
          using (StreamReader sr = new StreamReader(hijackedStream))
          {
          var raw= sr.ReadToEnd();
          if(raw.Contains("The specified refresh token is no longer valid.")){
          // custom your own response
          context.Response.StatusCode = 401;
          // ...
          //context.Response.Body = ... /
          }else{
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          }
          }
          });

          // helper to make the copy easy
          private async Task CopyStreamToResponseBody(HttpContext context,Stream newStream, Stream originalStream){

          newStream.Seek(0,SeekOrigin.Begin);
          await newStream.CopyToAsync(originalStream);
          context.Response.ContentLength =originalStream.Length;
          context.Response.Body = originalStream;
          }





          share|improve this answer























          • The reason I consider the /token endpoint not reached is that the constructor of the controller which the token endpoint resides in never gets called. Same goes (naturally) for the actual implementation at the /token route in that controller. However, I'll be testing your custom middleware shortly to confirm that the workaround is working.
            – Magnus Andersson
            Nov 13 '18 at 16:49











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53261820%2fhow-to-customize-the-authorization-error-produced-by-openiddict%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          1














          You can use OpenIddict's event model to customize the token response payloads before they are written to the response stream. Here's an example:



          MyApplyTokenResponseHandler.cs



          public class MyApplyTokenResponseHandler : IOpenIddictServerEventHandler<OpenIddictServerEvents.ApplyTokenResponse>
          {
          public Task<OpenIddictServerEventState> HandleAsync(OpenIddictServerEvents.ApplyTokenResponse notification)
          {
          var response = notification.Context.Response;
          if (string.Equals(response.Error, OpenIddictConstants.Errors.InvalidGrant, StringComparison.Ordinal) &&
          !string.IsNullOrEmpty(response.ErrorDescription))
          {
          response.ErrorDescription = "Your customized error";
          }

          return Task.FromResult(OpenIddictServerEventState.Unhandled);
          }
          }


          Startup.cs



          services.AddOpenIddict()
          .AddCore(options =>
          {
          // ...
          })

          .AddServer(options =>
          {
          // ...
          options.AddEventHandler<MyApplyTokenResponseHandler>();
          })

          .AddValidation();





          share|improve this answer





















          • Great info, this allows me to customize the error description string. However, if I want to write my own raw json object to the response stream, how would I do that properly?
            – Magnus Andersson
            Nov 14 '18 at 16:55








          • 2




            You can either simply add your JSON items to the response object (e.g response["my_json_array"] = JArray.FromObject(new { 42 })) or completely take control of the response rendering by writing yourself the response to the output stream (notification.Context.HttpContext.Response.Body) and calling notification.Context.HandleResponse() to inform OpenIddict you don't want it to apply its default logic.
            – Pinpoint
            Nov 14 '18 at 20:25












          • WOW, a nice solution !
            – itminus
            Nov 15 '18 at 7:52
















          1














          You can use OpenIddict's event model to customize the token response payloads before they are written to the response stream. Here's an example:



          MyApplyTokenResponseHandler.cs



          public class MyApplyTokenResponseHandler : IOpenIddictServerEventHandler<OpenIddictServerEvents.ApplyTokenResponse>
          {
          public Task<OpenIddictServerEventState> HandleAsync(OpenIddictServerEvents.ApplyTokenResponse notification)
          {
          var response = notification.Context.Response;
          if (string.Equals(response.Error, OpenIddictConstants.Errors.InvalidGrant, StringComparison.Ordinal) &&
          !string.IsNullOrEmpty(response.ErrorDescription))
          {
          response.ErrorDescription = "Your customized error";
          }

          return Task.FromResult(OpenIddictServerEventState.Unhandled);
          }
          }


          Startup.cs



          services.AddOpenIddict()
          .AddCore(options =>
          {
          // ...
          })

          .AddServer(options =>
          {
          // ...
          options.AddEventHandler<MyApplyTokenResponseHandler>();
          })

          .AddValidation();





          share|improve this answer





















          • Great info, this allows me to customize the error description string. However, if I want to write my own raw json object to the response stream, how would I do that properly?
            – Magnus Andersson
            Nov 14 '18 at 16:55








          • 2




            You can either simply add your JSON items to the response object (e.g response["my_json_array"] = JArray.FromObject(new { 42 })) or completely take control of the response rendering by writing yourself the response to the output stream (notification.Context.HttpContext.Response.Body) and calling notification.Context.HandleResponse() to inform OpenIddict you don't want it to apply its default logic.
            – Pinpoint
            Nov 14 '18 at 20:25












          • WOW, a nice solution !
            – itminus
            Nov 15 '18 at 7:52














          1












          1








          1






          You can use OpenIddict's event model to customize the token response payloads before they are written to the response stream. Here's an example:



          MyApplyTokenResponseHandler.cs



          public class MyApplyTokenResponseHandler : IOpenIddictServerEventHandler<OpenIddictServerEvents.ApplyTokenResponse>
          {
          public Task<OpenIddictServerEventState> HandleAsync(OpenIddictServerEvents.ApplyTokenResponse notification)
          {
          var response = notification.Context.Response;
          if (string.Equals(response.Error, OpenIddictConstants.Errors.InvalidGrant, StringComparison.Ordinal) &&
          !string.IsNullOrEmpty(response.ErrorDescription))
          {
          response.ErrorDescription = "Your customized error";
          }

          return Task.FromResult(OpenIddictServerEventState.Unhandled);
          }
          }


          Startup.cs



          services.AddOpenIddict()
          .AddCore(options =>
          {
          // ...
          })

          .AddServer(options =>
          {
          // ...
          options.AddEventHandler<MyApplyTokenResponseHandler>();
          })

          .AddValidation();





          share|improve this answer












          You can use OpenIddict's event model to customize the token response payloads before they are written to the response stream. Here's an example:



          MyApplyTokenResponseHandler.cs



          public class MyApplyTokenResponseHandler : IOpenIddictServerEventHandler<OpenIddictServerEvents.ApplyTokenResponse>
          {
          public Task<OpenIddictServerEventState> HandleAsync(OpenIddictServerEvents.ApplyTokenResponse notification)
          {
          var response = notification.Context.Response;
          if (string.Equals(response.Error, OpenIddictConstants.Errors.InvalidGrant, StringComparison.Ordinal) &&
          !string.IsNullOrEmpty(response.ErrorDescription))
          {
          response.ErrorDescription = "Your customized error";
          }

          return Task.FromResult(OpenIddictServerEventState.Unhandled);
          }
          }


          Startup.cs



          services.AddOpenIddict()
          .AddCore(options =>
          {
          // ...
          })

          .AddServer(options =>
          {
          // ...
          options.AddEventHandler<MyApplyTokenResponseHandler>();
          })

          .AddValidation();






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 14 '18 at 14:10









          Pinpoint

          23.3k36997




          23.3k36997












          • Great info, this allows me to customize the error description string. However, if I want to write my own raw json object to the response stream, how would I do that properly?
            – Magnus Andersson
            Nov 14 '18 at 16:55








          • 2




            You can either simply add your JSON items to the response object (e.g response["my_json_array"] = JArray.FromObject(new { 42 })) or completely take control of the response rendering by writing yourself the response to the output stream (notification.Context.HttpContext.Response.Body) and calling notification.Context.HandleResponse() to inform OpenIddict you don't want it to apply its default logic.
            – Pinpoint
            Nov 14 '18 at 20:25












          • WOW, a nice solution !
            – itminus
            Nov 15 '18 at 7:52


















          • Great info, this allows me to customize the error description string. However, if I want to write my own raw json object to the response stream, how would I do that properly?
            – Magnus Andersson
            Nov 14 '18 at 16:55








          • 2




            You can either simply add your JSON items to the response object (e.g response["my_json_array"] = JArray.FromObject(new { 42 })) or completely take control of the response rendering by writing yourself the response to the output stream (notification.Context.HttpContext.Response.Body) and calling notification.Context.HandleResponse() to inform OpenIddict you don't want it to apply its default logic.
            – Pinpoint
            Nov 14 '18 at 20:25












          • WOW, a nice solution !
            – itminus
            Nov 15 '18 at 7:52
















          Great info, this allows me to customize the error description string. However, if I want to write my own raw json object to the response stream, how would I do that properly?
          – Magnus Andersson
          Nov 14 '18 at 16:55






          Great info, this allows me to customize the error description string. However, if I want to write my own raw json object to the response stream, how would I do that properly?
          – Magnus Andersson
          Nov 14 '18 at 16:55






          2




          2




          You can either simply add your JSON items to the response object (e.g response["my_json_array"] = JArray.FromObject(new { 42 })) or completely take control of the response rendering by writing yourself the response to the output stream (notification.Context.HttpContext.Response.Body) and calling notification.Context.HandleResponse() to inform OpenIddict you don't want it to apply its default logic.
          – Pinpoint
          Nov 14 '18 at 20:25






          You can either simply add your JSON items to the response object (e.g response["my_json_array"] = JArray.FromObject(new { 42 })) or completely take control of the response rendering by writing yourself the response to the output stream (notification.Context.HttpContext.Response.Body) and calling notification.Context.HandleResponse() to inform OpenIddict you don't want it to apply its default logic.
          – Pinpoint
          Nov 14 '18 at 20:25














          WOW, a nice solution !
          – itminus
          Nov 15 '18 at 7:52




          WOW, a nice solution !
          – itminus
          Nov 15 '18 at 7:52













          1















          The /token endpoint is never reached, so the error is not under "my control".




          In fact ,the /token is reached, and the parameter of grant_type equals refresh_token. But the rejection logic when refresh token expired is not processed by us. It is some kind of "hardcoded" in source code :



          if (token == null)
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }

          if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
          {
          if (!await TryRedeemTokenAsync(token))
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }
          }


          The context.Reject here comes from the assembly AspNet.Security.OpenIdConnect.Server.



          For more details, see source code on GitHub .




          I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.




          I've tried and I'm pretty sure we can use a custom middleware to catch all status codes. The key point is to detect the status code after the next() invocation:



          app.Use(async(context , next )=>{

          // passby all other end points
          if(! context.Request.Path.StartsWithSegments("/connect/token")){
          await next();
          return;
          }

          // since we might want to detect the Response.Body, I add some stream here .
          // if you only want to detect the status code , there's no need to use these streams
          Stream originalStream = context.Response.Body;
          var hijackedStream = new MemoryStream();
          context.Response.Body = hijackedStream;
          hijackedStream.Seek(0,SeekOrigin.Begin);

          await next();

          // if status code not 400 , pass by
          if(context.Response.StatusCode != 400){
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          return;
          }

          // read and custom the stream
          hijackedStream.Seek(0,SeekOrigin.Begin);
          using (StreamReader sr = new StreamReader(hijackedStream))
          {
          var raw= sr.ReadToEnd();
          if(raw.Contains("The specified refresh token is no longer valid.")){
          // custom your own response
          context.Response.StatusCode = 401;
          // ...
          //context.Response.Body = ... /
          }else{
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          }
          }
          });

          // helper to make the copy easy
          private async Task CopyStreamToResponseBody(HttpContext context,Stream newStream, Stream originalStream){

          newStream.Seek(0,SeekOrigin.Begin);
          await newStream.CopyToAsync(originalStream);
          context.Response.ContentLength =originalStream.Length;
          context.Response.Body = originalStream;
          }





          share|improve this answer























          • The reason I consider the /token endpoint not reached is that the constructor of the controller which the token endpoint resides in never gets called. Same goes (naturally) for the actual implementation at the /token route in that controller. However, I'll be testing your custom middleware shortly to confirm that the workaround is working.
            – Magnus Andersson
            Nov 13 '18 at 16:49
















          1















          The /token endpoint is never reached, so the error is not under "my control".




          In fact ,the /token is reached, and the parameter of grant_type equals refresh_token. But the rejection logic when refresh token expired is not processed by us. It is some kind of "hardcoded" in source code :



          if (token == null)
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }

          if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
          {
          if (!await TryRedeemTokenAsync(token))
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }
          }


          The context.Reject here comes from the assembly AspNet.Security.OpenIdConnect.Server.



          For more details, see source code on GitHub .




          I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.




          I've tried and I'm pretty sure we can use a custom middleware to catch all status codes. The key point is to detect the status code after the next() invocation:



          app.Use(async(context , next )=>{

          // passby all other end points
          if(! context.Request.Path.StartsWithSegments("/connect/token")){
          await next();
          return;
          }

          // since we might want to detect the Response.Body, I add some stream here .
          // if you only want to detect the status code , there's no need to use these streams
          Stream originalStream = context.Response.Body;
          var hijackedStream = new MemoryStream();
          context.Response.Body = hijackedStream;
          hijackedStream.Seek(0,SeekOrigin.Begin);

          await next();

          // if status code not 400 , pass by
          if(context.Response.StatusCode != 400){
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          return;
          }

          // read and custom the stream
          hijackedStream.Seek(0,SeekOrigin.Begin);
          using (StreamReader sr = new StreamReader(hijackedStream))
          {
          var raw= sr.ReadToEnd();
          if(raw.Contains("The specified refresh token is no longer valid.")){
          // custom your own response
          context.Response.StatusCode = 401;
          // ...
          //context.Response.Body = ... /
          }else{
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          }
          }
          });

          // helper to make the copy easy
          private async Task CopyStreamToResponseBody(HttpContext context,Stream newStream, Stream originalStream){

          newStream.Seek(0,SeekOrigin.Begin);
          await newStream.CopyToAsync(originalStream);
          context.Response.ContentLength =originalStream.Length;
          context.Response.Body = originalStream;
          }





          share|improve this answer























          • The reason I consider the /token endpoint not reached is that the constructor of the controller which the token endpoint resides in never gets called. Same goes (naturally) for the actual implementation at the /token route in that controller. However, I'll be testing your custom middleware shortly to confirm that the workaround is working.
            – Magnus Andersson
            Nov 13 '18 at 16:49














          1












          1








          1







          The /token endpoint is never reached, so the error is not under "my control".




          In fact ,the /token is reached, and the parameter of grant_type equals refresh_token. But the rejection logic when refresh token expired is not processed by us. It is some kind of "hardcoded" in source code :



          if (token == null)
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }

          if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
          {
          if (!await TryRedeemTokenAsync(token))
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }
          }


          The context.Reject here comes from the assembly AspNet.Security.OpenIdConnect.Server.



          For more details, see source code on GitHub .




          I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.




          I've tried and I'm pretty sure we can use a custom middleware to catch all status codes. The key point is to detect the status code after the next() invocation:



          app.Use(async(context , next )=>{

          // passby all other end points
          if(! context.Request.Path.StartsWithSegments("/connect/token")){
          await next();
          return;
          }

          // since we might want to detect the Response.Body, I add some stream here .
          // if you only want to detect the status code , there's no need to use these streams
          Stream originalStream = context.Response.Body;
          var hijackedStream = new MemoryStream();
          context.Response.Body = hijackedStream;
          hijackedStream.Seek(0,SeekOrigin.Begin);

          await next();

          // if status code not 400 , pass by
          if(context.Response.StatusCode != 400){
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          return;
          }

          // read and custom the stream
          hijackedStream.Seek(0,SeekOrigin.Begin);
          using (StreamReader sr = new StreamReader(hijackedStream))
          {
          var raw= sr.ReadToEnd();
          if(raw.Contains("The specified refresh token is no longer valid.")){
          // custom your own response
          context.Response.StatusCode = 401;
          // ...
          //context.Response.Body = ... /
          }else{
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          }
          }
          });

          // helper to make the copy easy
          private async Task CopyStreamToResponseBody(HttpContext context,Stream newStream, Stream originalStream){

          newStream.Seek(0,SeekOrigin.Begin);
          await newStream.CopyToAsync(originalStream);
          context.Response.ContentLength =originalStream.Length;
          context.Response.Body = originalStream;
          }





          share|improve this answer















          The /token endpoint is never reached, so the error is not under "my control".




          In fact ,the /token is reached, and the parameter of grant_type equals refresh_token. But the rejection logic when refresh token expired is not processed by us. It is some kind of "hardcoded" in source code :



          if (token == null)
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }

          if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
          {
          if (!await TryRedeemTokenAsync(token))
          {
          context.Reject(
          error: OpenIddictConstants.Errors.InvalidGrant,
          description: context.Request.IsAuthorizationCodeGrantType() ?
          "The specified authorization code is no longer valid." :
          "The specified refresh token is no longer valid.");

          return;
          }
          }


          The context.Reject here comes from the assembly AspNet.Security.OpenIdConnect.Server.



          For more details, see source code on GitHub .




          I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.




          I've tried and I'm pretty sure we can use a custom middleware to catch all status codes. The key point is to detect the status code after the next() invocation:



          app.Use(async(context , next )=>{

          // passby all other end points
          if(! context.Request.Path.StartsWithSegments("/connect/token")){
          await next();
          return;
          }

          // since we might want to detect the Response.Body, I add some stream here .
          // if you only want to detect the status code , there's no need to use these streams
          Stream originalStream = context.Response.Body;
          var hijackedStream = new MemoryStream();
          context.Response.Body = hijackedStream;
          hijackedStream.Seek(0,SeekOrigin.Begin);

          await next();

          // if status code not 400 , pass by
          if(context.Response.StatusCode != 400){
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          return;
          }

          // read and custom the stream
          hijackedStream.Seek(0,SeekOrigin.Begin);
          using (StreamReader sr = new StreamReader(hijackedStream))
          {
          var raw= sr.ReadToEnd();
          if(raw.Contains("The specified refresh token is no longer valid.")){
          // custom your own response
          context.Response.StatusCode = 401;
          // ...
          //context.Response.Body = ... /
          }else{
          await CopyStreamToResponseBody(context,hijackedStream,originalStream);
          }
          }
          });

          // helper to make the copy easy
          private async Task CopyStreamToResponseBody(HttpContext context,Stream newStream, Stream originalStream){

          newStream.Seek(0,SeekOrigin.Begin);
          await newStream.CopyToAsync(originalStream);
          context.Response.ContentLength =originalStream.Length;
          context.Response.Body = originalStream;
          }






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 13 '18 at 10:55

























          answered Nov 13 '18 at 10:36









          itminus

          3,2011320




          3,2011320












          • The reason I consider the /token endpoint not reached is that the constructor of the controller which the token endpoint resides in never gets called. Same goes (naturally) for the actual implementation at the /token route in that controller. However, I'll be testing your custom middleware shortly to confirm that the workaround is working.
            – Magnus Andersson
            Nov 13 '18 at 16:49


















          • The reason I consider the /token endpoint not reached is that the constructor of the controller which the token endpoint resides in never gets called. Same goes (naturally) for the actual implementation at the /token route in that controller. However, I'll be testing your custom middleware shortly to confirm that the workaround is working.
            – Magnus Andersson
            Nov 13 '18 at 16:49
















          The reason I consider the /token endpoint not reached is that the constructor of the controller which the token endpoint resides in never gets called. Same goes (naturally) for the actual implementation at the /token route in that controller. However, I'll be testing your custom middleware shortly to confirm that the workaround is working.
          – Magnus Andersson
          Nov 13 '18 at 16:49




          The reason I consider the /token endpoint not reached is that the constructor of the controller which the token endpoint resides in never gets called. Same goes (naturally) for the actual implementation at the /token route in that controller. However, I'll be testing your custom middleware shortly to confirm that the workaround is working.
          – Magnus Andersson
          Nov 13 '18 at 16:49


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53261820%2fhow-to-customize-the-authorization-error-produced-by-openiddict%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          這個網誌中的熱門文章

          Xamarin.form Move up view when keyboard appear

          Post-Redirect-Get with Spring WebFlux and Thymeleaf

          Anylogic : not able to use stopDelay()