[{"data":1,"prerenderedAt":403},["ShallowReactive",2],{"navigation":3,"/blog/why-fapi-recommends-private-key-jwt":141,"/blog/why-fapi-recommends-private-key-jwt-surround":392},[4,36,55,80,101,116,128],{"title":5,"path":6,"stem":7,"children":8,"icon":35},"ASP.NET Core","/aspnet-core","1.aspnet-core/1.index",[9,11,15,19,23,27,31],{"title":10,"path":6,"stem":7},"Cursor Pagination",{"title":12,"path":13,"stem":14},"Dapper - Micro ORM","/aspnet-core/dapper","1.aspnet-core/2.dapper",{"title":16,"path":17,"stem":18},"DbUp - Database Migrations","/aspnet-core/dbup","1.aspnet-core/3.dbup",{"title":20,"path":21,"stem":22},"Serilog - Structured Logging for .NET","/aspnet-core/serilog","1.aspnet-core/4.serilog",{"title":24,"path":25,"stem":26},"Class Inheritance in C#","/aspnet-core/inheritance","1.aspnet-core/5.inheritance",{"title":28,"path":29,"stem":30},"C# Collections","/aspnet-core/collections","1.aspnet-core/6.collections",{"title":32,"path":33,"stem":34},".NET gitignore Command","/aspnet-core/gitignore","1.aspnet-core/7.gitignore",false,{"title":37,"path":38,"stem":39,"children":40,"icon":35},"Oauth2.0 and OpenIdConnect","/oauth2andopenidconnect","10.oauth2andopenidconnect/1.index",[41,43,47,51],{"title":42,"path":38,"stem":39},"RFC 6749 - OAuth 2.0 Authorization Framework",{"title":44,"path":45,"stem":46},"Authentication (AuthN) vs Authorization (AuthZ)","/oauth2andopenidconnect/authnandauthz","10.oauth2andopenidconnect/2.authnandauthz",{"title":48,"path":49,"stem":50},"TOTP Authentication","/oauth2andopenidconnect/totp","10.oauth2andopenidconnect/3.totp",{"title":52,"path":53,"stem":54},"OAuth 2.0 Resource Indicators - RFC 8707","/oauth2andopenidconnect/oauth2resourceindicators","10.oauth2andopenidconnect/4.OAuth2ResourceIndicators",{"title":56,"path":57,"stem":58,"children":59,"page":35},"Azure","/azure","2.azure",[60,64,68,72,76],{"title":61,"path":62,"stem":63},"Azure Cost Management","/azure/azuremanagementandgovernance","2.azure/1.Azuremanagementandgovernance",{"title":65,"path":66,"stem":67},"Azure Policy","/azure/azurepolicy","2.azure/2.azurepolicy",{"title":69,"path":70,"stem":71},"Code Blocks","/azure/code-blocks","2.azure/3.code-blocks",{"title":73,"path":74,"stem":75},"Prose Components","/azure/prose-components","2.azure/4.prose-components",{"title":77,"path":78,"stem":79},"Images and Embeds","/azure/images-embeds","2.azure/5.images-embeds",{"title":81,"path":82,"stem":83,"children":84,"page":35},"Git","/git","3.git",[85,89,93,97],{"title":86,"path":87,"stem":88},"Git Rebase","/git/git-rebase","3.git/1.git-rebase",{"title":90,"path":91,"stem":92},"Git Stash","/git/git-stash","3.git/2.git-stash",{"title":94,"path":95,"stem":96},"SemVer","/git/semver","3.git/3.semver",{"title":98,"path":99,"stem":100},"Conventional Commits","/git/conventional-commits","3.git/4.conventional-commits",{"title":102,"path":103,"stem":104,"children":105,"icon":35},"Design Patterns","/design-patterns","7.design-patterns/1.index",[106,108,112],{"title":107,"path":103,"stem":104},"Introduction",{"title":109,"path":110,"stem":111},"Installation","/design-patterns/installation","7.design-patterns/2.installation",{"title":113,"path":114,"stem":115},"Usage","/design-patterns/usage","7.design-patterns/3.usage",{"title":117,"path":118,"stem":119,"children":120,"icon":35},"Software Principles","/principles","8.principles/1.index",[121,122,125],{"title":107,"path":118,"stem":119},{"title":109,"path":123,"stem":124},"/principles/installation","8.principles/2.installation",{"title":113,"path":126,"stem":127},"/principles/usage","8.principles/3.usage",{"title":129,"path":130,"stem":131,"children":132,"icon":35},"Software Architecture","/architecture","9.architecture/1.index",[133,135,138],{"title":134,"path":130,"stem":131},"CQRS Pattern",{"title":109,"path":136,"stem":137},"/architecture/installation","9.architecture/2.installation",{"title":113,"path":139,"stem":140},"/architecture/usage","9.architecture/3.usage",{"id":142,"title":143,"body":144,"date":381,"description":382,"extension":383,"featured":35,"meta":384,"navigation":385,"path":386,"readTime":387,"seo":388,"stem":389,"tag":390,"__hash__":391},"blog/blog/16.why-fapi-recommends-private-key-jwt.md","Why FAPI Recommends Private Key JWT: Your Public Key Is Meant to Be Public",{"type":145,"value":146,"toc":364},"minimark",[147,156,164,169,181,185,192,196,203,207,226,235,239,278,282,289,293,296,300,303,307,310,314,317,321,328,331,335],[148,149,150,151,155],"p",{},"A public key is an identity. A private key is the proof. Owning one doesn't give you the other — and understanding exactly ",[152,153,154],"em",{},"why"," that's true, mathematically, is what makes the rest of this make sense rather than just sound reassuring.",[148,157,158,159,163],{},"Financial APIs operate in an environment where \"trust me, I know the password\" isn't a strong enough claim. Banks, payment providers, and other regulated platforms need something closer to cryptographic proof than a shared secret can offer, which is a big part of why the Financial-grade API (FAPI) profiles recommend ",[160,161,162],"strong",{},"Private Key JWT"," as a client authentication method. The goal isn't just stronger authentication in a vague sense. It's a specific guarantee: proof that the client making a request actually possesses a private key, without that key ever having to travel anywhere or be shared with anyone who needs to verify it.",[165,166,168],"h2",{"id":167},"the-problem-with-shared-secrets","The Problem With Shared Secrets",[148,170,171,172,176,177,180],{},"Traditional OAuth client authentication often comes down to a ",[173,174,175],"code",{},"client_id"," and ",[173,178,179],{},"client_secret"," — a password, essentially, known by both the client and the authorization server. That works, but it has a structural weakness baked in: both parties hold the same credential. If the secret leaks — copied into a config file, committed to a repo, exposed in a log — an attacker who finds it can authenticate as the client with no further effort. Rotating it requires coordinating both sides. The entire model depends on a shared piece of knowledge staying secret, and shared secrets have a well-documented habit of not staying secret forever.",[165,182,184],{"id":183},"private-key-jwt-changes-whats-being-proven","Private Key JWT Changes What's Being Proven",[148,186,187,188,191],{},"Instead of proving identity by revealing something both sides know, the client proves identity by digitally signing a JWT with a private key that only it possesses. The authorization server never holds that private key — it holds the corresponding ",[152,189,190],{},"public"," key, and verification is a fundamentally different operation from signing. The private key never leaves the client, ever, in this model. That single property is what makes the rest of the design work.",[165,193,195],{"id":194},"why-the-public-key-is-genuinely-safe-to-share","Why the Public Key Is Genuinely Safe to Share",[148,197,198,199,202],{},"A common misconception is worth addressing directly: if an attacker gets hold of a public key, can't they use it to impersonate the owner? No — and this isn't just a design convention, it's a mathematical property of how these algorithms are constructed. Public-key cryptography is built on ",[160,200,201],{},"trapdoor functions",": operations that are computationally easy to perform in one direction and, without a specific secret, computationally infeasible to reverse. Signing with a private key is the easy direction. Recovering that private key from the corresponding public key is the direction that isn't practically reversible — not because nobody's tried, but because of specific, well-studied mathematical problems that current computers can't solve at scale in any useful timeframe.",[165,204,206],{"id":205},"the-actual-math-rsa","The Actual Math: RSA",[148,208,209,210,213,214,221,222,225],{},"RSA's trapdoor is built on ",[160,211,212],{},"integer factorization",". Key generation starts by picking two large prime numbers, p and q, and multiplying them together to get n = p × q. The public key is derived from n; the private key is derived from p and q individually. Multiplying two large primes together is fast — any computer does it instantly. Going the other direction — given only n, recovering the specific primes p and q that were multiplied to produce it — is the integer factorization problem, and it's dramatically harder. There's no known way to do it efficiently for large n; the best publicly known method, the ",[215,216,220],"a",{"href":217,"rel":218},"https://cr.yp.to/nfscircuit/rsa.html",[219],"nofollow","General Number Field Sieve",", has running time that grows ",[152,223,224],{},"superpolynomially"," with the size of the number, meaning the difficulty explodes far faster than the key size grows.",[148,227,228,229,234],{},"This isn't theoretical. In 2020, researchers factored RSA-250, an 829-bit number, and it required ",[215,230,233],{"href":231,"rel":232},"https://quantum.cloud.ibm.com/learning/en/courses/fundamentals-of-quantum-algorithms/quantum-algorithmic-foundations/factoring-and-gcd",[219],"thousands of CPU-core-years distributed across tens of thousands of machines"," to do it. Real-world RSA keys today run 2048 or 4096 bits — not roughly twice as hard to factor as an 829-bit number, but astronomically harder, because the difficulty curve is exponential-ish, not linear, in key length. An attacker holding an RSA public key holds n. Turning that into the private key means factoring n, and at current key sizes, that's outside the reach of every computer on Earth combined, working for longer than the age of the universe, using any publicly known classical algorithm.",[165,236,238],{"id":237},"the-actual-math-elliptic-curve-cryptography-ecdsa","The Actual Math: Elliptic Curve Cryptography (ECDSA)",[148,240,241,242,245,246,249,250,253,254,256,257,260,261,176,263,265,266,176,268,270,271,273,274,277],{},"Elliptic curve algorithms — increasingly common in FAPI deployments because of smaller key sizes and faster operations — rely on a different hard problem with the same basic shape. A private key is just a number, ",[152,243,244],{},"d",". The public key is a point on an elliptic curve, computed as ",[152,247,248],{},"Q = d × G",", where ",[152,251,252],{},"G"," is a fixed, publicly known starting point on the curve and the \"multiplication\" is a specific operation called elliptic curve point multiplication — repeated point addition, done ",[152,255,244],{}," times. Computing ",[152,258,259],{},"Q"," from ",[152,262,244],{},[152,264,252],{}," is fast. Going backward — given ",[152,267,259],{},[152,269,252],{},", recovering ",[152,272,244],{}," — is the ",[160,275,276],{},"elliptic curve discrete logarithm problem (ECDLP)",", and no efficient classical algorithm is known to solve it. The best known general approaches, like Pollard's rho method, still take time that grows exponentially with key size, which is exactly why elliptic curve keys can be so much smaller than RSA keys while offering comparable security — the underlying hard problem is harder, per bit, than integer factorization is.",[165,279,281],{"id":280},"what-computationally-infeasible-actually-means-here","What \"Computationally Infeasible\" Actually Means Here",[148,283,284,285,288],{},"It's worth being precise about the claim being made, because it's narrower than \"impossible.\" Nobody has mathematically proven that fast factoring or fast ECDLP-solving algorithms ",[152,286,287],{},"can't"," exist — it's an open problem in computer science whether one might someday be found. What's true today is that decades of dedicated cryptanalytic research haven't found one, and the best publicly known methods remain computationally infeasible at standard key sizes using classical computers. There is one well-known exception worth naming honestly: a sufficiently large, fault-tolerant quantum computer running Shor's algorithm could solve both integer factorization and the elliptic curve discrete logarithm problem efficiently, which is exactly why NIST has been standardizing post-quantum cryptographic algorithms as a hedge against that future possibility. That's a genuine, forward-looking caveat — and it's also why \"computationally infeasible with current technology\" is the accurate phrase, rather than a stronger claim the math doesn't actually support.",[165,290,292],{"id":291},"a-simple-analogy-for-the-non-mathematical-version","A Simple Analogy for the Non-Mathematical Version",[148,294,295],{},"Think of it like a wax seal. Anyone can look at the seal pattern, and anyone can compare another mark against it to check whether it matches. But only the person holding the actual stamp can produce that exact impression. The public key is the reference pattern everyone's allowed to see. The private key is the physical stamp nobody else has. Having a perfect description of what the seal looks like doesn't let you manufacture the stamp that makes it — and the RSA and elliptic curve math above is the formal version of exactly that intuition.",[165,297,299],{"id":298},"why-this-matters-specifically-for-fapi","Why This Matters Specifically for FAPI",[148,301,302],{},"Financial-grade systems need client authentication that resists leaked configuration files, shared secrets, replay attempts, and impersonation — and Private Key JWT delivers on each of those specifically because of the math above. The authorization server never stores a secret whose theft would let someone else authenticate as the client; it stores a public key that's only useful for verification, not signing. Keys can be rotated by publishing a new public key without any password synchronization. And because compromising the authorization server only exposes public keys, that compromise alone doesn't hand an attacker the ability to forge new authenticated requests.",[165,304,306],{"id":305},"what-an-attacker-actually-gets-from-a-stolen-public-key","What an Attacker Actually Gets From a Stolen Public Key",[148,308,309],{},"Very little. They can verify signatures, identify which key produced a given signature, and inspect any associated certificate. They cannot generate a valid signature, authenticate as the client, or produce a new JWT assertion the authorization server will accept — because none of those actions are possible without solving integer factorization or ECDLP for that specific key, which remains outside practical reach.",[165,311,313],{"id":312},"the-private-key-is-still-the-whole-game","The Private Key Is Still the Whole Game",[148,315,316],{},"None of this makes the private key's protection optional — quite the opposite. If an attacker steals the private key directly, all the math above becomes irrelevant, because they no longer need to solve a hard problem; they just have the answer. That's why organizations pairing Private Key JWT with real security invest in Hardware Security Modules, Trusted Platform Modules, secure enclaves, cloud key management services, and strict access controls around where and how the private key is ever used. Private Key JWT is only as strong as the protection around the one thing the whole scheme depends on staying secret.",[165,318,320],{"id":319},"the-takeaway","The Takeaway",[148,322,323,324,327],{},"Private Key JWT doesn't replace TLS, secure key storage, replay protection, or proper JWT validation — including audience checks, expiration checks, and unique ",[173,325,326],{},"jti"," values to prevent a captured token from being reused. It strengthens one specific layer, client authentication, by replacing a shared secret with a mathematical guarantee: the public key genuinely can't be reversed into the private key using any publicly known method, at the key sizes financial-grade systems actually use. That's not a policy choice or a convention — it's integer factorization and the elliptic curve discrete logarithm problem doing exactly what they were chosen to do. Identity, in this model, was never protected by hiding the public key. It's protected by the fact that even total, public knowledge of it doesn't get an attacker any closer to the private key that actually proves who's on the other end of the request.",[329,330],"hr",{},[165,332,334],{"id":333},"references-and-further-reading","References and Further Reading",[336,337,338,347,355],"ul",{},[339,340,341,342,346],"li",{},"D. J. Bernstein, ",[215,343,345],{"href":217,"rel":344},[219],"\"Background: RSA and the Number Field Sieve\""," — a technical overview of why RSA key size directly determines resistance to the best known factoring methods.",[339,348,349,350,354],{},"IBM Quantum Learning, ",[215,351,353],{"href":231,"rel":352},[219],"\"Factoring and Computing GCDs\""," — includes the RSA-250 factoring record and what it took to break an 829-bit key classically.",[339,356,357,358,363],{},"LORIA, ",[215,359,362],{"href":360,"rel":361},"https://members.loria.fr/EThome/teaching/2022-cse-291-14/slides/cse-291-14.pdf",[219],"\"The Number Field Sieve\""," — lecture notes distinguishing the integer factorization problem (RSA) from the discrete logarithm problem (Diffie-Hellman/ECDSA) and why both underpin modern public-key cryptography.",{"title":365,"searchDepth":366,"depth":367,"links":368},"",1,2,[369,370,371,372,373,374,375,376,377,378,379,380],{"id":167,"depth":367,"text":168},{"id":183,"depth":367,"text":184},{"id":194,"depth":367,"text":195},{"id":205,"depth":367,"text":206},{"id":237,"depth":367,"text":238},{"id":280,"depth":367,"text":281},{"id":291,"depth":367,"text":292},{"id":298,"depth":367,"text":299},{"id":305,"depth":367,"text":306},{"id":312,"depth":367,"text":313},{"id":319,"depth":367,"text":320},{"id":333,"depth":367,"text":334},"2026-08-09","A public key is an identity. A private key is the proof. The math behind why an attacker who steals your public key still can't forge a signature — RSA factoring, ECDLP, and what FAPI actually gets from Private Key JWT.","md",{},true,"/blog/why-fapi-recommends-private-key-jwt","9 min",{"title":143,"description":382},"blog/16.why-fapi-recommends-private-key-jwt","OAuth2 / OpenID Connect","hMATOyg14y13qBpAP1jCrqBNm28NVURN83Dn85riaO8",[393,398],{"title":394,"path":395,"stem":396,"description":397,"children":-1},"Stop Turning Deployments Into Board Meetings","/blog/stop-turning-deployments-into-board-meetings","blog/15.stop-turning-deployments-into-board-meetings","A deployment call with a dozen stakeholders watching one engineer click deploy isn't engineering maturity. It's engineering anxiety, dressed up as process — and it's usually compensating for a rollback plan nobody trusts.",{"title":399,"path":400,"stem":401,"description":402,"children":-1},"FIPS Compliance: It's More Than Running Your Application on Kubernetes","/blog/fips-compliance-beyond-kubernetes","blog/17.fips-compliance-beyond-kubernetes","FIPS doesn't certify applications, orchestrators, or clouds — it certifies specific, versioned cryptographic modules. \"We deployed to Kubernetes\" was never an answer to the question FIPS is actually asking.",1785744836151]