Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Tokens are too big - Not understand why #2239

Closed
1 task done
lufo88ita opened this issue Dec 23, 2024 · 9 comments
Closed
1 task done

Tokens are too big - Not understand why #2239

lufo88ita opened this issue Dec 23, 2024 · 9 comments
Labels

Comments

@lufo88ita
Copy link

lufo88ita commented Dec 23, 2024

Confirm you've already contributed to this project or that you sponsor it

  • I confirm I'm a sponsor or a contributor

Version

5.8.0

Question

Hi Kevin,

as a reminder:

My company sponsered you through the account SCP-srl, I'm a member of it.
https://github.com/orgs/SCP-srl/people

I need an advice to reduce the size of the tokens (access token, id token, refresh token): they are long more than 4000 characters and the server refuse to process a token that big.
I put in the tokens about 15 claims (max 20 characters for claim) so I don't think that is the problem. I tried to leave only one claim (email) and the tokens size is over 3000 characters still.

So I guess, but I am not sure, the problem is in the encryption.

In dev environment we use this:

options.AddDevelopmentEncryptionCertificate();
options.AddDevelopmentSigningCertificate();

While on the staging machine (the software is not in production yet :-) ) we use a RSA 2048 bit certificate.

Any advice? Is the correct way of encrypt the tokens? We miss somthing we can use to reduce the size?

Happy Holydays :-D

@kevinchalet
Copy link
Member

kevinchalet commented Dec 23, 2024

Hey @lufo88ita,

Hope you're doing well! 😃

I put in the tokens about 15 claims (max 20 characters for claim) so I don't think that is the problem. I tried to leave only one claim (email) and the tokens size is over 3000 characters still.

3K characters sounds a bit high: I just tested that using the console+ASP.NET Core server sample sandboxes you can find in this repo and I'm getting a 2.1K character-long access token with encryption enabled, 1.1K characters with encryption disabled (JWE definitely adds some overhead 😄).

I need an advice to reduce the size of the tokens (access token, id token, refresh token): they are long more than 4000 characters and the server refuse to process a token that big.

There's sadly no miracle solution, but here's a few remarks:

  • Using ECDSA signing keys (e.g P256) instead of RSA signing certificates: ECDSA produces much shorter signatures, which will massively reduce the size of a token (e.g with the same sandbox, I'm getting a 1.7K character-long access token with encryption enabled, 840 characters with encryption disabled). You can register an ephemeral ECDSA signing key by calling options.AddEphemeralSigningKey(SecurityAlgorithms.EcdsaSha256).

  • Alternatively, you can also register a "symmetric signing key" that OpenIddict will favor when selecting the signing credentials used to protect access tokens: it will use HMAC-SHA, which will also produce shorter "signatures" than RSA but it's not an approach you can easily use when you have third-party resource servers (since giving them the symmetric key would allow them to produce their own tokens 😅), unless you're forcing them to use introspection instead of performing local access token validation.

  • Using AES-KW instead of RSA+AES encryption doesn't seem to make tokens smaller (they were even a bit larger during my tests).

  • Using ASP.NET Core Data Protection instead of JWT: DP uses its own binary format and exclusively uses symmetric encryption and "signing" (HMAC, to be exact), which helps produce shorter tokens (e.g I'm getting ~980 character-long tokens with the same sample). It's a good option if you only have first-party resource servers, but it's less convenient if you also have third-party resource servers, as they will have to use introspection in that case too. More information here: https://documentation.openiddict.com/integrations/aspnet-core-data-protection.

  • Using reference tokens (enabled by default for authorization codes, but opt-in for refresh and access tokens): it will store the token payload in the database and return a 256-bit reference identifier encoded as a 43-character-long base64url string. For access tokens, it means your resource servers need to either have a direct access to the same database used by the authorization server or use token introspection. It also adds a bit of latency, since a DB call or introspection request is needed for each HTTP request containing a token to validate. More information here: https://documentation.openiddict.com/configuration/token-storage.

  • Most servers allow tweaking the length of headers, so you could consider increasing these limits to support longer tokens.

  • The OpenIddict validation handler extracts access tokens from the standard Authorization header, but it also automatically extracts them from the access_token query string parameter or from the access_token form parameter when using formURL encoding. The last option is generally less constrained in terms of size limits 😃

Hope it will help 😃

Happy Holydays :-D

Thanks! Merry Christmas to you and your teammates and thanks again for sponsoring the project! 🎄 🎁

@lufo88ita
Copy link
Author

lufo88ita commented Dec 24, 2024

That's exactly what I need. I will test it when I return in office. I will close the ticket, I already get all the information I need!

unless you're forcing them to use introspection

We already force them to use introspection, so this is not a problem. ;-)

Many thanks!

P.S. EDIT For missing quote

@kevinchalet
Copy link
Member

Many thanks!

My pleasure. Feel free to reopen if you need additional details when returning to the office 😃

All the best.

@lufo88ita lufo88ita reopened this Jan 2, 2025
@lufo88ita
Copy link
Author

lufo88ita commented Jan 2, 2025

My pleasure. Feel free to reopen if you need additional details when returning to the office 😃

Unfortunately I must reopen the issue, because I forgot a point.

I tried in development with this approach

Using ECDSA signing keys (e.g P256) instead of RSA signing certificates: ECDSA produces much shorter signatures,

I use also a symmetric key for the encryption.

Consider the tokens contains the following claims:
sub: a guid string
name
surname
email
email confirmed: a boolean
VAT code
Country (ISO 3166-1 alpha-2)
Country-code (a numeric. Don't ask me why I have to manage two different code 👎 )
Roles: array, up to 4 string of twelve characters each

The approach reduce the size of the idtoken (~800 characters, from 1.1k -300 characters), reduce the size of the accesstoken (~1.7k, from 2.1k -400 characters), reduce a lot the size of the refresh token, but it remain a bit large (~2.2k, from 3.2k, -1000 (!) characters).

But it doesn't matter if these size are what you expect, just need a confirmation that the size of the refresh token is not wrong. Is it correct that is the biggest one of the three?

Also: I use 8kb as max header size as empirical value. Is it safe to have these sizes for the tokens?

@kevinchalet
Copy link
Member

Happy New Year! 🎉

Is it correct that is the biggest one of the three?

Yes: refresh tokens contain all the claims that you originally added when calling return SignIn(...), so they are typically larger than the other tokens. You can also enable reference refresh tokens in the server options if you prefer having fixed-size refresh tokens.

Also: I use 8kb as max header size as empirical value. Is it safe to have these sizes for the tokens?

Refresh tokens are never used in headers (only in request forms/JSON responses), so a specific header size limit will have no impact. Request form limits are generally high enough not to require any specific tweak, even when having large refresh tokens.

@lufo88ita
Copy link
Author

Happy New Year! 🎉

Happy new year too :-D

Yes: refresh tokens contain all the claims that you originally added when calling return SignIn(...), so they are typically larger than the other tokens. You can also enable reference refresh tokens in the server options if you prefer having fixed-size refresh tokens.

Ok, I understand now.

Refresh tokens are never used in headers (only in request forms/JSON responses), so a specific header size limit will have no impact. Request form limits are generally high enough not to require any specific tweak, even when having large refresh tokens.

Apologize me, I know refresh token are used only in the body of a POST. I refer to the accesstoken, but you answer to this as well :-D Thanks.

Now: maybe I found a problem with a dependency you use (also in 6.0.0):
In openiddict when I call
AddSigningCertificate() //thumbprint or directly the certificate

inside is called
AddSigningKey(new X509SecurityKey(certificate))

but the class Microsoft.IdentityModel.Tokens.X509SecurityKey accept only RSA key as described in this issue :-(
AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet#1943

Inside the class at Row 49:

_privateKey = Certificate.GetRSAPrivateKey();

Row 72 as well:
_publicKey = Certificate.GetRSAPublicKey();

So if I use a certificate with ECDSA it does not populate the private/public key and this implicate that we cannot use a certificate with ecdsa.
I ask in their repository if they plan to merge a commit:
AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet#2377

Just to be explicit: this is not a problem with openiddict, it's a problem with a dependency!!!!

I guess the only alternative (for now) is using a symmetrical key to encrypt and sign the tokens (as alternative to RSA)

@kevinchalet
Copy link
Member

Just to be explicit: this is not a problem with openiddict, it's a problem with a dependency!!!!

Yeah, not sure why IdentityModel still doesn't support ECDSA certificates or why that PR isn't merged yet... it's really a long-time demand.

That said, you don't have to use certificates: OpenIddict also supports "raw" RSA or ECDSA keys (i.e that are not extracted from a X.509 certificate). Working with such keys wasn't very convenient in previous .NET versions, but recent .NET releases offer very useful APIs to import/export ECDSA keys using standard formats (e.g PEM).

You can easily generate an ECDSA key and export the private key using the PEM format:

using var algorithm = ECDsa.Create(ECCurve.NamedCurves.nistP521);
var pem = algorithm.ExportECPrivateKeyPem();

And you can easily re-import it and inject it in OpenIddict's configuration:

var algorithm = ECDsa.Create(); // Note: do not dispose the instance.
algorithm.ImportFromPem(pem);
options.AddSigningKey(new ECDsaSecurityKey(algorithm));

Cheers.

@lufo88ita
Copy link
Author

Ok, all works like a charm.
Many thanks :-)

Regards,

@kevinchalet
Copy link
Member

Many thanks :-)

My pleasure! Thanks again for sponsoring the project! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants
@kevinchalet @lufo88ita and others