-
-
Notifications
You must be signed in to change notification settings - Fork 305
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
unsupported_token_type "The specified token cannot be introspected" #347
Comments
Hey @Justincale, Thanks for sponsoring the project, much appreciated! ❤️ I don't see any obvious mistake in the code you shared, so the issue is probably elsewhere.
YARP is not a requirement, but it has become the de facto "standard" for implementing the backend-for-frontend pattern as most of its competitors are no longer actively developed (I can't blame them, it's hard to compete against a Microsoft-funded project 🤣). Any other proxy solution should work equally well.
It's indeed one of the most common mistakes. Another reason could be that Oqtane generates its own "JWT access token" instead of sending the original access token generated by OpenIddict, which cannot work since OpenIddict requires a token with specific characteristics (e.g it must have a I'm not familiar with Oqtane so I'll need to spend some time taking a look at the source code. In the meantime, that would likely help if you could share the ASP.NET Core logs (make sure you lower the default log level to Trace to ensure the low-level messages logged by OpenIddict are captured).
Merry Christmas! 🎄 🎅🏻 🎁 All the best! |
Hi Kevin, Thanks for the prompt response. Please find attached a trace log of a successful authorization_code flow and subsequent failed call to the API. As per your suggestions, i have had a look at the oqtane source and can see only one place where the access_token is being set. This occurs after a successful external login via the authorization_code flow and happens here: https://github.com/oqtane/oqtane.framework/blob/d976cc6c19ee566fe4c1d6d590bdc05478591ef2/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs#L231 If i look at the raw data returned in context.SecurityToken.RawData i can see that it does, in fact, have a type of "jwt", not "jwt+at" which is confusing as i can't see anywhere else in the Oqtane source that does anything with this token. Some points:
Thanks again for your help. |
Hey Justin,
Thanks! Unfortunately, you forgot to lower the default log level and there's no Debug/Trace logs in that file (you need to change the log level to Trace to be able to capture the tokens).
OpenIddict only uses the generic I took a look and it seems Oqtane indeed produces its own tokens meant to be used by "downstream APIs":
To be honest, it's an extremely unusual approach: in the classical BFF pattern, the access token sent to the "downstream APIs" is always an access token the authorization server itself produced, not an arbitrary token minted by the BFF proxy. Since you configured your resource server/API to use introspection, the access token generated by Oqtane is sent to the authorization server, that rejects it because it didn't issue it (which is really the only acceptable outcome in this situation 😄)
That scheme is used by ASP.NET Core Identity for its "main authentication cookie", which is something OpenIddict also leverages when you use it in combination with Identity (it's an extremely common case). The root cause here is how Oqtane designed things (fun fact, I remember the author contacted me a while ago as he was interested in using OpenIddict in this project: it seems he opted for a more... creative option 😄). I can see two options to solve that:
I've never used Oqtane, so it's hard for me to offer any solid advice, unfortunately. Maybe you should cross-post this thread in case its author would have more information to share? All the best. |
hi Kevin, Appologies re the log file, i am using serilog which requires a log level of Verbose. Hopefully this file will shed more light on the situation. I took a look and it seems Oqtane indeed produces its own tokens meant to be used by "downstream APIs": identity.AddClaim(new Claim("access_token", context.AccessToken)); Retrieve an access token issued by OpenIddict using the standard OIDC code flow, store it in the authentication cookie and attach it to the API requests sent by the BFF proxy without relying on Oqtane (in the Dantooine sample you mentioned, we use YARP, but you can opt for a different library if you prefer) Okay, so this is where my lack of understanding comes in. In my scenraio, am i required to use a BFF proxy? At the moment, the token i retrieve from openiddict is being attached to the authorization header of a standard HttpClient which sends the request to the API. This is how i had previously implemented Duende Identity Server. It is worth noting that the current implementation/settings used in Oqtane are the same i was using successfully with Duende Identity Server. |
Hey,
Haha, no worries 😄 Thanks for providing the new logs, that helped a lot!
It's worth noting that the OpenIddict validation handler has much stricter validation rules than the MSFT JWT bearer handler that IdentityServer uses, specially regarding the type of token received: it's very likely you were already using an identity token, but the JWT bearer handler wasn't complaining because it wasn't configured to check the token type. Now that you've migrated to OpenIddict and its validation handler, using "an identity token as an access token" is no longer possible due to OpenIddict's stricter rules.
There are basically two options for using OAuth 2.0/OIDC with a SPA application:
The issue with Blazor is that it heavily blurs the line between what happens server-side and client-side, depending the Blazor model you choose... |
Thanks Kevin, if I am understanding you correctly, I need to implement bff in a blazor server configuration which will, as you say, do the dance between openiddict and the api, resolving my id token for a token the openiddict validator can accept? If so, I don’t mind forcing my clients to use blazor server as it is the best fit for my system anyway, but I think I’ll need to start a conversation with the Oqtane dev’s and see what there thoughts are in relation to implementing bff for external api calls. I’m not sure they will want to lock the Oqtane code base into a specific BFF framework and I’m not sure I will be able to implement my own within a custom Oqtane module due to the way Oqtane registers services at startup (although I could be wrong). Anyhow, if you could confirm re the bff dance, or let me know if you think there are any quick workarounds I could use to temporarily get past this obstacle, that would be great. And thanks, as always, for your help. |
Hey @Justincale, Happy New Year! 🎉
If you decide to opt for the BFF pattern, yes, except you need to use access (and refresh) tokens, not identity tokens.
FWIW, I emailed Shaun Walker 2 days ago to let him know about this thread but I haven't received a reply yet, sadly.
I guess the quickest workaround would simply be to update your code that sends the identity token (which isn't a legal operation) to send the access token generated by OpenIddict instead. 👇🏻
My pleasure! Thanks for sponsoring the project 😃 |
Hi Kevin, I am going to close this off for now. I have created a thread over at Oqtane which i have tagged you in: oqtane/oqtane.framework#4964. I'll see what Shaun Walker comes back with as i believe this has more to do with the Oqtane implementation of oidc than openiddict. I will start another thread if needed. Thanks again for your help. |
Hey @Justincale,
No problem. Feel free to re-open this one if it's easier for you 👍🏻 All the best. |
Hi @kevinchalet, I just wanted to let you know that the issue has been tracked down to Oqtane defaulting SaveTokens = false. We are in discussion to change this so a default installation has SaveTokens = true. This solves most of the issues I was having, I can work around the rest. Thanks again for everything, great work by the way. 🙂 |
Hey @Justincale, Thanks for letting me know!
That's interesting (and a bit surprising). Now, I'm wondering: how was your code able to retrieve the identity token stored in the authentication cookie (and incorrectly used as an "access token" in API calls) if it wasn't stored in the first place? 🫨
It's worth noting that this option will store all the tokens returned by the authorization server (including identity tokens), which will make authentication cookies much larger. Some browsers (e.g Safari) are known to enforce extremely strict per-domain limits, which may result in authentication issues if parts of the authentication cookie's chunks are discarded. Similarly, server stacks like IIS have maximal lengths allowed for request headers (which includes cookies). Definitely something to keep in mind before making that the default value 😃
Thanks for your kind words! ❤️ All the best. |
That's interesting (and a bit surprising). Now, I'm wondering: how was your code able to retrieve the identity token stored in the authentication cookie (and incorrectly used as an "access token" in API calls) if it wasn't stored in the first place? This is what was confusing to me also. This is what i have pieced together:
So, when i started migrating my application into Oqtane, i was using duende as an ID Server. The AuthozationCode being sent to my API obviously wasn't working as it was issued by Oqtane, but i found that if i sent the "access_token" from step 1 instead, i could jig my API code to accept it. Then, after moving from Duende to openiddict, my api calls failed due to the "access_token" not being an access_token at all. It seems from the above, and what Shaun over at Oqtane has said, that the whole thing in Oqtane has been designed to allow a specific Oqtane client to make downstream calls to a specific API which has a relationship with the client (ie: the API should only expect tokens from a single, well know, Oqtane client). The solution so far: Set SaveTokens = true and fire up my own HttpClient which sends the actual access_token from httpContext.GetTokenAsync("access_token") to my API. This is working, i thought, until........ It's worth noting that this option will store all the tokens returned by the authorization server (including identity tokens), which will make authentication cookies much larger. Some browsers (e.g Safari) are known to enforce extremely strict per-domain limits, which may result in authentication issues if parts of the authentication cookie's chunks are discarded. Similarly, server stacks like IIS have maximal lengths allowed for request headers (which includes cookies). Okay, i'm starting to get the picture here re your previous post re the dantooine AuthenticationController. I think Oqtane is going to need some mod's in order to accommodate what i am trying to achieve. Let me go back to Shaun over at Oqtane and see what his thoughts are in relation to how we move forward. Thanks @kevinchalet :) |
Confirm you've already contributed to this project or that you sponsor it
Version
V6
Question
Hi, I have been using IdentityServer4 for my open id requirements but have fallen victim to their new licensing (which i can't afford) so am looking as openiddict as an alternative. My requirement is that i have a client (or clients) that utilizes open id as a SSO solution. Each client must be able to retrieve tokens from the id server via either client_credentials, or authorization_code flows, which need to then be passed on to an external API. I have partially had some success by utilizing code from the Zirku, and Dantooine sample projects in as mush as if i retrieve a token via the client_credentials flow, i am able to pass that token on to the api.
When using the authorization_code flow, i am able to login to the client, but the access_token passed to the API gets rejected: When using introspection on the API side of things (as in the example below), i receive "unsupported_token_type" "The specified token cannot be introspected.". When attempting to use an EncryptionKey i receive "The specified token is not of the expected type."
My openiddict configuration is as follows:
Client Registration
API Registration
Scope Registration
OPENIDDICT SERVER STARTUP (BASED ON DANTOOINE)
OPENIDDICT AUTHORIZATION CONTROLLER (authorize and exchange endpoints)
[HttpGet("
/connect/authorize")]/connect/authorize")][HttpPost("
[IgnoreAntiforgeryToken]
public async Task Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
}
API STARTUP CONFIGURATION (.AddAuthorization excluded for brevity)
builder.Services.AddDbContext(options =>
options.EnableSensitiveDataLogging(true)
.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
sql => sql.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
.MigrationsAssembly(migrationsAssembly)
.UseNetTopologySuite()
.EnableRetryOnFailure()));
// Register the OpenIddict validation components.
builder.Services.AddOpenIddict()
.AddValidation(options =>
{
// Note: the validation handler uses OpenID Connect discovery
// to retrieve the address of the introspection endpoint.
options.SetIssuer("https://localhost:44719/");
options.AddAudiences("resource_server_1", "plushtixClient");
});
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
Some things to note
My client is based on the Oqtane framework, which utilizes standard AspNetcore Authorization and authentication, and calls ChallengeResult to kick start the authorization_code flow.
It seems to me like the token being sent to the API is perhaps an id_token, not an access_token?
I have been down so many rabbit holes my head is spinning, but perhaps my issue is due to not using YARP? Is this a requirement in relation to my setup as I'm not sure i can implement it within the Oqtane framework.
I have always struggled with openid and oauth so please be gentle.
Any help would be much appreciated as this is driving my nuts. oh, and happy xmas. :)
The text was updated successfully, but these errors were encountered: