How to customize the authorization error produced by OpenIddict?
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
add a comment |
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
add a comment |
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
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
asp.net-core openiddict
asked Nov 12 '18 at 12:04
Magnus Andersson
7518
7518
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
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();
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 theresponse
object (e.gresponse["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 callingnotification.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
add a comment |
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;
}
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
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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();
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 theresponse
object (e.gresponse["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 callingnotification.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
add a comment |
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();
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 theresponse
object (e.gresponse["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 callingnotification.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
add a comment |
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();
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();
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 theresponse
object (e.gresponse["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 callingnotification.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
add a comment |
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 theresponse
object (e.gresponse["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 callingnotification.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
add a comment |
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;
}
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
add a comment |
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;
}
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
add a comment |
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;
}
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;
}
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
add a comment |
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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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