![]() |
|
![]() |
| Why not just stick your auth token in the cache. It's supposed to expire anyways.
Back in the day we used memcached for our primary store for all sorts of ephemeral things. Including user sessions. |
![]() |
| This is a perfect example of "it depends" being the right answer.
Should a project use sessions or JWTs? One isn't right or wrong, it all depends on the context of the project. |
![]() |
| > Sharing the session across multiple services is its own distributed systems problem with numerous security implications to be aware of and bearer tokens might be a good alternative.
JWT makes it possible to distribute the same access token across multiple systems, but so do stateful tokens. The security implications when you're using JWT for this solution are much higher than with database tokens. Let's look at this for a moment: JWT Security Issues: - Inherent design issues (alg=none, algorithm confusion, weak ciphers like RSA) - Cannot be revoked. - Key rotation and distribution is necessary to keep token safe over a long period of time - Claim parsing needs to be standardized and enforced correctly in all services (otherwise impersonation is possible) Database Tokens Security Issues: - Timing Attacks against B-Tree indexes [1] - Giving direct access to the database to all microservices is risky The security issues with databases are ridiculously easy to solve: To prevent timing attacks, you can just use a hash table index, split the token into a search-key part and a constant-time-compare part or add an HMAC to your token. To prevent direct access to the database by all your microservices, you just wrap it with an API the verifies tokens. The JWT security issues are much harder to solve. To prevent misconfiguration or misuse and standardize the way claims are used across your organization, you probably need to write your own library or deploy an API gateway. To counter the lack of revocation support, you either need to use very short lived access tokens (so your refresh token DB will still get a lot of hits and you would still need to deal with all the scaling issues) or set up a distributed revocation service (not easy at all). Setting up seamless key rotation also requires additional infrastructure that is not part of the hundreds of JWT libraries out there. It's really easy to get a JWT solution that just works and scales easily, but if you really care about security — especially if you care about security! — JWTs are not necessarily easier than stateful tokens. They're probably harder. > Apart from that, Google and Facebook don't even use JWTs between the browser and backends after the initial login but actually do have some sort of distributed session concept last time I checked. Last time I checked (which was today for Google), neither Google, nor Facebook is using JWT for their access or refresh tokens. The only place I saw JWT with the ID Token in their Open ID Connect Flow, and they can't really avoid that even if their wanted, since this is mandated by the spec. Facebook and Google don't need JWT. Scaling and distributing a read-only token database to handle a large amount of traffic is easier — not harder! — for these companies. Stateless tokens can be useful for them in certain scenarios, but even then, if you're at Google or Facebook's scale, why would you opt for JWT over an in-house format that is smaller, faster and suffers from less vulnerabilities? [1] https://www.usenix.org/legacy/event/woot07/tech/full_papers/... |
![]() |
| It limits your ability to compartmentalize your infrastructure, establish security perimeters, and provide defense-in-depth against vulnerabilities in your dependencies. |
![]() |
| Don’t see how those prevent tokens from being misused? They just prevent anyone from issuing tokens as you. Not by themselves, but if you implement your server correctly. |
![]() |
| DPoP described in RFC9449 - you can see from the RFC number it's quite new. I don't think there's wide support for it, but at least Okta supports it[1] and I think Auth0 are also working on adding DPoP.
Is it good? I'm not a fan. To use DPoP safely (without replay attacks), you need to add server-side nonces ("nonce") and client-generated nonces ("jti", great and definitely not confusing terminology there). You need to make sure client-generated nonces are only used once, which requires setting up... wait for it... A database! And if you'll be using DPoP in a distributed manner, with access tokens then, well, a database shared across all services. And this is not an easy-to-scale read-oriented database like you'd have to use for stateful tokens. No, this is a database that requires an equal number of reads and writes (assuming you're not under a DDoS attack): for each DPoP validation, you'd need to read the nonce and then add it to the database. You'd also need to implement some sort of TTL mechanism to prevent the database from growing forever and implement strong rate limitation across all services to prevent very easy DDoS. It seems like the main driving motivation behind DPoP is to mitigate the cost of refresh tokens being exfiltrated from public clients using XSS attacks, but I believe it is too cumbersome to be used securely as a general mechanism for safe token delegation that prevents "pass-the-token" attacks. [1] https://developer.okta.com/docs/guides/dpop/nonoktaresources... |
![]() |
| DPoP is an OAuth extension that defends against token replay by sender constraining tokens. It is a new-ish spec, but support is pretty widespread already. It's used in a lot of European banking that has pretty strict security requirements, and it's supported by some of the big cloud identity providers as well as the OAuth framework I work on, IdentityServer. We have sample code and docs etc on our blog: https://blog.duendesoftware.com/posts/20230504_dpop/
|
![]() |
| User X’s web browser calls Server A which makes a web service request to Server B that needs to authenticate that user X is making the call.
What types of tokens do you suggest in each case? |
![]() |
| To pitch my own project, OpenPubkey[0], it is designed for exactly this use case. OpenPubkey let's you add a public key to an ID Token (JWT) without needing any change at the IDP.
1. Alice generates an ephemeral key pair (if she is using a browser she can generate the key pair as a "non-extractable key"[1]). 2. Alice gets ID Token issued by Google that commits to their public key, 3. Alice signs her API request data to Service A and sends her ID Token to Service A. 4. Service A checks the ID Token (JWT) is issued by Google and that the identity ([email protected]) is authorized to make this API call, then it extracts Alice's public key from the ID Token and verifies the signature on the data in the API call. Then it passes the signed data to Service B. 5. Service B verifies everything again including that the data is validly signed by Alice. Service B could then write this data and its cryptographic prominence into the database. Technically OpenPubkey uses a JWS, but it is a JWS composed of a JWT (ID Token) with additional signatures. OpenPubkey signed messages, like the ones passed via the API are also JWS. I'm working on a system where each service in the path adds their signatures to the signed message so you can cryptographically enforce that messages must pass through particular services and then check that at during the database write or read. Using signature aggregation, you don't get a linear increase in verification cost as the number of signatures increase. It doesn't seem to add much overhead to service meshes since they are already standing up and tearing down mTLS tunnels. The main question to me is how much autonomy do you want to give to your services. There are cases in which you want services to query each other without those services having to prove that the call originated from a specific authorized user. [0]: https://github.com/openpubkey/openpubkey [1]: https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey/e... |
![]() |
| Side question, anyone knows where the phrase "used in anger" comes from? I know it means using something in production but where does it come from?
Is it about battlefield and such? |
![]() |
| Here's the thing: neither Google, nor Facebook is using JWT for their access or refresh tokens. The only place they use JWT, as far as I know, is for the ID Token in their Open ID Connect Flow, since this is a mandatory (and terribly misguided) part of the spec.
I keep seeing the idea that JWT was designed for Google or Facebook scale being repeated over and over, but the reality is that neither company uses it. Last time I checked, both used rather short access and refresh tokens and it appears that at least the refresh token (if not both) is stateful. Implementing stateful tokens on a global scale and sharing them across hundreds of service is HARD, but it's easier when you are Google or Facebook, and you've got enough resources to throw at this trouble. And if you do need to implement a stateful token, you've got every reason to choose your own format. Your applications are using your own authentication libraries and infrastructure (e.g. API gateways), so you don't have to worry about complicating their life with a non-standard format. The upside for using your own stateless format is that you avoid all the design issues with JWT (alg=none, algorithm confusion, questionable support for outdated algorithms from the 1970s) and you can design a far more compact format that takes a fraction of the size of JWT[1]. There's a reason JWT got popular with scrappy startups, enterprises hobby projects and Udemy/Medium tutorial authors: they're very easy to spin up and library support is everywhere. I don't mean to say JWT is the right choice for any of these uses — it probably isn't. But it's the easy choice, the worse-is-better solution. The worse solution needs only be better in one respect to win: it should be easier to implement, copy and spread. In the end of the day, JWT is not a good solution for either Google-scale companies or small startups. But it's the small startups that usually lack the resources and awareness to adopt another solution. [1] https://fly.io/blog/api-tokens-a-tedious-survey/#protobuf |
![]() |
| > By just using a “normal” opaque session token and storing it in the database, the same way Google does with the refresh token, and dropping all jwt authentication token nonsense.
Not only is this true, but most actual deployments of JWTs just have you swap a JWT (ID Token) for a opaque session token. That said, I really like having a JWT signed by an IDP which states the user's identity because if designed correctly you only need to trust one party IDP. For instance Google (the IDP) is the ideal party to identify a gmail email address since you already have to trust them for this. I created OpenPubkey to leverage JWTs, while minimizing and in some cases removing trust. OpenPubkey[0, 1] let's you turn JWTs bearer tokens into certificates. This lets you use digital signatures with ephemeral secrets. [0]: https://github.com/openpubkey/openpubkey [1]: https://eprint.iacr.org/2023/296 |
![]() |
| Aren’t JWT bearer tokens certificates already? Only the issuing server has the private keys, and the public keys are used to validate that server signed them? |
![]() |
| How does infrequentcy of revoked tokens reduce requests? Dont you have to check every token to see if its revoked?
Or Do all the server instances store a copy of all revoked tokens in memory/local db? |
![]() |
| Just populate the cache when you need it? You will need a database round trip for the first request per user per application restart, if they haven't reset since. I assumed this was obvious. |
![]() |
| I typically use a service like AWS cognito (using their built-in hosted UI) to handle authentication for my apps. That gives me MFA, Google/Facebook login, email verification, etc for free and has a generous free tier.
I have a template that's backed by terraform and the authentication client is in lambda so the whole thing is serverless, self-contained and practically free. So I just run "terraform apply" and I have scalable auth for my new service. https://github.com/alshdavid/template-cognito (only 1 dependency on AWS, everything else is stdlib) If any service I create is lucky enough to break out of the free-tier and cost is an issue, then I can just move to another OAuth2/OIDC provider. The auth mechanism Cognito uses is just a specification meaning I am not coupled to any one service provider (though the user accounts themselves are). Cognito, Auth0, IdentifyServer, or whatever - I can migrate if cost becomes a problem. The big issue with JWTs are that, if lost, they give permissions to attackers without revocability. For this reason, I keep auth-tokens short lived and refresh them often. Refresh-tokens are revocable and live for a few days. This means that a lost auth-token is only harmful for a few minutes while a lost refresh token is only harmful until revoked or expired. Tokens are stored as path-specific http-only cookies so the only vector for attack is if a user physically opens devtools and gives an attacker the token - or if the attacker has access to the computer (physically or via a malicious terminal script). High risk operations (e.g. delete account, delete content, anything high risk) requires "step-up" authentication - so a user is asked to re-authenticate in those cases. Overall, when you consider that rolling your own authentication comes with the liability associated with holding user data (companies must announce a breach to users, etc) - if a service provider like Cognito is compromised, you won't be liable or the only one affected. JWTs have security concerns, but on balance, when used with third party provider, a sensible configuration and considering the risk of rolling your own - they are fine. |
![]() |
| The notion that you have Google/Facebook scale problems at 10k requests per second (vs 10s of millions of requests per second) is a pretty funny claim in its own right. |
![]() |
| A former student of mine (Vera Yaseneva) redesigned our old auth architecture using jwts and I’m pretty happy with how it turned out. Maybe it is overkill for our simple autograder server, but it was fun getting it to work and I’m sure it is more secure than the old architecture which had many many flaws… it was a maintenance nightmare for years. After the redesign it has been a breeze. Here is the project https://github.com/quickfeed/quickfeed
The security arch is mainly in web/auth and web/interceptor packages if anyone is interested in learning from the code. It uses connectrpc, which has a nice interceptor arch. Happy to share Vera’s thesis report if anyone is interested… |
![]() |
| We use a rate-limiting rule in the existing firewall on the /auth endpoint. Our default is on five failed attempts in a five minute window gets you a one hour ban. |
![]() |
| Two ways, default key is [IP address + User ID] we also have a fallback with a higher limit on [IP address] only when we expect lots of attempts from e.g. a VPN. |
![]() |
| Oh man, he goes straight to stateful services as an alternative to JWTs. What an absolute nightmare, if JWTs are too hard stateful services are certainly more difficult. |
![]() |
| A simple session cookie does not protect against CSRF. In 2005, session IDs were generated with low quality RNGs and too few bits making them easy to guess. OWASP happened for a reason. |
![]() |
| Wrote a brief recap of "permission" and "login" for authentication from my work in JavaScript malwares.
It was a rush outline article with citations. Large binning, salting of hash, and revocatable are my criteria. Some toolkits that went out the window firstly are: * auth0, * Fusion auth, and * Gluu. So, some of the basic criteria are: * User and password login instead of plain HTTP session cookies. * HTTP-only over TLS v1.2+ (secured HTTP, HTTPS) * ECDSA 1K or better * SameSite [1] * __Host prefix [1] * preload HTTP Strict-Transport-Security header line [2] * Bearer token supplied by API clients * Don’t listen on port 80… like ever. Or revoke token if over non-port 443. * DO NOT use JWT [3] * DO NOT use CORS [4] Hope the citations help more. JWT, not recommended, IMHO. https://egbert.net/blog/articles/authentication-for-api.html |
More so, A revocation list on something like Redis can expire with a token and a lot less expensive than an rdbms lookup with joins.
Not too mention, why add extra load with extra DB calls in the first place?
You don't have to be Google scale to want to separate logins from the main service. And that's just for starters.