[{"data":1,"prerenderedAt":692},["ShallowReactive",2],{"docs-nav:en":3,"docs:\u002Fdocs\u002Finfra\u002Fidentity":186},[4],{"title":5,"path":6,"stem":7,"children":8},"Docs","\u002Fdocs","docs",[9,12,16,20,24,28,50,75,120,145],{"title":10,"path":6,"stem":11},"What is Fougere","docs\u002Findex",{"title":13,"path":14,"stem":15},"Getting started","\u002Fdocs\u002Fgetting-started","docs\u002F01.getting-started",{"title":17,"path":18,"stem":19},"An app you already have","\u002Fdocs\u002Fexisting-app","docs\u002F02.existing-app",{"title":21,"path":22,"stem":23},"Bring your schema","\u002Fdocs\u002Fbring-your-schema","docs\u002F03.bring-your-schema",{"title":25,"path":26,"stem":27},"The CLI","\u002Fdocs\u002Fcli","docs\u002F04.cli",{"title":29,"path":30,"stem":31,"children":32,"page":49},"Concepts","\u002Fdocs\u002Fconcepts","docs\u002F2.concepts",[33,37,41,45],{"title":34,"path":35,"stem":36},"Philosophy","\u002Fdocs\u002Fconcepts\u002Fphilosophy","docs\u002F2.concepts\u002F1.philosophy",{"title":38,"path":39,"stem":40},"The Frond","\u002Fdocs\u002Fconcepts\u002Ffrond","docs\u002F2.concepts\u002F2.frond",{"title":42,"path":43,"stem":44},"The shortest path","\u002Fdocs\u002Fconcepts\u002Fshortest-path","docs\u002F2.concepts\u002F3.shortest-path",{"title":46,"path":47,"stem":48},"The base","\u002Fdocs\u002Fconcepts\u002Fthe-base","docs\u002F2.concepts\u002F4.the-base",false,{"title":51,"path":52,"stem":53,"children":54,"page":49},"Schema","\u002Fdocs\u002Fschema","docs\u002F3.schema",[55,59,63,67,71],{"title":56,"path":57,"stem":58},"Entities","\u002Fdocs\u002Fschema\u002Fentities","docs\u002F3.schema\u002F1.entities",{"title":60,"path":61,"stem":62},"Views","\u002Fdocs\u002Fschema\u002Fviews","docs\u002F3.schema\u002F2.views",{"title":64,"path":65,"stem":66},"The identity card","\u002Fdocs\u002Fschema\u002Fcard","docs\u002F3.schema\u002F3.card",{"title":68,"path":69,"stem":70},"Standard Schema","\u002Fdocs\u002Fschema\u002Fstandard-schema","docs\u002F3.schema\u002F4.standard-schema",{"title":72,"path":73,"stem":74},"How a schema moves","\u002Fdocs\u002Fschema\u002Fevolution","docs\u002F3.schema\u002F5.evolution",{"title":76,"path":77,"stem":78,"children":79,"page":49},"Business","\u002Fdocs\u002Fbusiness","docs\u002F4.business",[80,84,88,92,96,100,104,108,112,116],{"title":81,"path":82,"stem":83},"Handlers","\u002Fdocs\u002Fbusiness\u002Fhandlers","docs\u002F4.business\u002F1.handlers",{"title":85,"path":86,"stem":87},"Ports","\u002Fdocs\u002Fbusiness\u002Fports","docs\u002F4.business\u002F10.ports",{"title":89,"path":90,"stem":91},"Presenters","\u002Fdocs\u002Fbusiness\u002Fpresenters","docs\u002F4.business\u002F2.presenters",{"title":93,"path":94,"stem":95},"Collectors","\u002Fdocs\u002Fbusiness\u002Fcollectors","docs\u002F4.business\u002F3.collectors",{"title":97,"path":98,"stem":99},"Errors","\u002Fdocs\u002Fbusiness\u002Ferrors","docs\u002F4.business\u002F4.errors",{"title":101,"path":102,"stem":103},"Seeds","\u002Fdocs\u002Fbusiness\u002Fseeds","docs\u002F4.business\u002F5.seeds",{"title":105,"path":106,"stem":107},"Facts","\u002Fdocs\u002Fbusiness\u002Ffacts","docs\u002F4.business\u002F6.facts",{"title":109,"path":110,"stem":111},"Storage","\u002Fdocs\u002Fbusiness\u002Fstorage","docs\u002F4.business\u002F7.storage",{"title":113,"path":114,"stem":115},"Writes that stand or fall as one","\u002Fdocs\u002Fbusiness\u002Ftogether","docs\u002F4.business\u002F8.together",{"title":117,"path":118,"stem":119},"Repositories","\u002Fdocs\u002Fbusiness\u002Frepositories","docs\u002F4.business\u002F9.repositories",{"title":121,"path":122,"stem":123,"children":124,"page":49},"Client","\u002Fdocs\u002Fclient","docs\u002F5.client",[125,129,133,137,141],{"title":126,"path":127,"stem":128},"Queries & commands","\u002Fdocs\u002Fclient\u002Fqueries-commands","docs\u002F5.client\u002F1.queries-commands",{"title":130,"path":131,"stem":132},"Forms","\u002Fdocs\u002Fclient\u002Fforms","docs\u002F5.client\u002F2.forms",{"title":134,"path":135,"stem":136},"Session","\u002Fdocs\u002Fclient\u002Fsession","docs\u002F5.client\u002F3.session",{"title":138,"path":139,"stem":140},"invoke","\u002Fdocs\u002Fclient\u002Finvoke","docs\u002F5.client\u002F4.invoke",{"title":142,"path":143,"stem":144},"Back-office","\u002Fdocs\u002Fclient\u002Fadmin","docs\u002F5.client\u002F5.admin",{"title":146,"path":147,"stem":148,"children":149,"page":49},"Infrastructure","\u002Fdocs\u002Finfra","docs\u002F6.infra",[150,154,158,162,166,170,174,178,182],{"title":151,"path":152,"stem":153},"The gradient","\u002Fdocs\u002Finfra\u002Fgradient","docs\u002F6.infra\u002F1.gradient",{"title":155,"path":156,"stem":157},"Surfaces — REST & GraphQL","\u002Fdocs\u002Finfra\u002Fsurfaces","docs\u002F6.infra\u002F2.surfaces",{"title":159,"path":160,"stem":161},"Deployment","\u002Fdocs\u002Finfra\u002Fdeployment","docs\u002F6.infra\u002F3.deployment",{"title":163,"path":164,"stem":165},"Hosts","\u002Fdocs\u002Finfra\u002Fhosts","docs\u002F6.infra\u002F4.hosts",{"title":167,"path":168,"stem":169},"Sources","\u002Fdocs\u002Finfra\u002Fsources","docs\u002F6.infra\u002F5.sources",{"title":171,"path":172,"stem":173},"Lifecycle","\u002Fdocs\u002Finfra\u002Flifecycle","docs\u002F6.infra\u002F6.lifecycle",{"title":175,"path":176,"stem":177},"Identity across Fronds","\u002Fdocs\u002Finfra\u002Fidentity","docs\u002F6.infra\u002F7.identity",{"title":179,"path":180,"stem":181},"Observability","\u002Fdocs\u002Finfra\u002Fobservability","docs\u002F6.infra\u002F8.observability",{"title":183,"path":184,"stem":185},"Testing","\u002Fdocs\u002Finfra\u002Ftesting","docs\u002F6.infra\u002F9.testing",{"id":187,"title":175,"body":188,"description":686,"extension":687,"meta":688,"navigation":689,"path":176,"seo":690,"stem":177,"__hash__":691},"docs_en\u002Fdocs\u002F6.infra\u002F7.identity.md",{"type":189,"value":190,"toc":676},"minimark",[191,195,204,209,212,223,230,234,237,358,361,397,401,407,414,417,421,454,472,482,486,489,555,562,566,578,600,629,633,643,653,659,663,672],[192,193,175],"h1",{"id":194},"identity-across-fronds",[196,197,198,199,203],"p",{},"In one process a call is a function call: nothing crosses, nothing needs proving. Split a\nFrond out and the same call becomes a payload, and a payload can say anything. The question\nthis page answers is what a receiver ",[200,201,202],"em",{},"establishes"," rather than accepts.",[205,206,208],"h2",{"id":207},"loopback-or-signed","Loopback or signed",[196,210,211],{},"A receiver refuses to start when it binds beyond loopback with no way to establish its\ncaller. Not per call — at boot:",[213,214,219],"pre",{"className":215,"code":217,"language":218},[216],"language-text","127.0.0.1                        starts, nothing to configure\n0.0.0.0 + FOUGERE_ROOT_KEY       starts, verifies every call\n0.0.0.0 with no root             does not start, and says what to do\n0.0.0.0 + allowUnsigned: true    starts — something in front already authenticated\n","text",[220,221,217],"code",{"__ignoreMap":222},"",[196,224,225,226,229],{},"The address already carries the decision. Binding beyond loopback is a deliberate act —\n",[220,227,228],{},"hosts"," is where you write it down — and a receiver reachable from outside that establishes\nnothing believes whatever state it is handed. Refusing at boot rather than per call is the\npoint: a receiver that starts and then rejects everything is discovered in production, one\nthat will not start is discovered at deployment.",[205,231,233],{"id":232},"what-travels","What travels",[196,235,236],{},"Every call across a link carries an envelope, signed by the caller:",[213,238,242],{"className":239,"code":240,"language":241,"meta":222,"style":222},"language-json shiki shiki-themes material-theme-lighter github-light github-dark","{ \"method\": \"post.list\",\n  \"params\": { \"params\": {}, \"query\": {}, \"body\": null,\n              \"identity\": \"eyJhbGciOiJFZERTQSIs…\" } }\n","json",[220,243,244,279,332],{"__ignoreMap":222},[245,246,249,253,257,261,264,267,270,274,276],"span",{"class":247,"line":248},"line",1,[245,250,252],{"class":251},"sP7_E","{",[245,254,256],{"class":255},"s39Yj"," \"",[245,258,260],{"class":259},"sseR_","method",[245,262,263],{"class":255},"\"",[245,265,266],{"class":251},":",[245,268,256],{"class":269},"sjJ54",[245,271,273],{"class":272},"s_sjI","post.list",[245,275,263],{"class":269},[245,277,278],{"class":251},",\n",[245,280,282,285,288,290,292,295,297,300,302,304,307,309,312,314,316,318,320,323,325,327,330],{"class":247,"line":281},2,[245,283,284],{"class":255},"  \"",[245,286,287],{"class":259},"params",[245,289,263],{"class":255},[245,291,266],{"class":251},[245,293,294],{"class":251}," {",[245,296,256],{"class":255},[245,298,287],{"class":299},"sZMiF",[245,301,263],{"class":255},[245,303,266],{"class":251},[245,305,306],{"class":251}," {},",[245,308,256],{"class":255},[245,310,311],{"class":299},"query",[245,313,263],{"class":255},[245,315,266],{"class":251},[245,317,306],{"class":251},[245,319,256],{"class":255},[245,321,322],{"class":299},"body",[245,324,263],{"class":255},[245,326,266],{"class":251},[245,328,329],{"class":255}," null",[245,331,278],{"class":251},[245,333,335,338,341,343,345,347,350,352,355],{"class":247,"line":334},3,[245,336,337],{"class":255},"              \"",[245,339,340],{"class":299},"identity",[245,342,263],{"class":255},[245,344,266],{"class":251},[245,346,256],{"class":269},[245,348,349],{"class":272},"eyJhbGciOiJFZERTQSIs…",[245,351,263],{"class":269},[245,353,354],{"class":251}," }",[245,356,357],{"class":251}," }\n",[196,359,360],{},"The envelope carries the state — who is signed in, if anyone — and the receiver takes it\nfrom there rather than from the payload. Sending both would leave every reader downstream\nchoosing between a proof and a claim about the same thing.",[196,362,363,364,368,369,372,373,372,376,378,379,381,382,385,386,389,390,392,393,396],{},"It binds the ",[365,366,367],"strong",{},"whole call",": ",[220,370,371],{},"entity",", ",[220,374,375],{},"op",[220,377,287],{}," and ",[220,380,311],{}," by value, the body by\ndigest. Signing the state alone would prove ",[200,383,384],{},"who"," without proving ",[200,387,388],{},"what",", and a captured\nenvelope could be replayed against another operation for as long as it stayed valid —\n",[220,391,273],{}," re-sent as ",[220,394,395],{},"post.delete",", same signature, still good.",[205,398,400],{"id":399},"two-signatures-one-public-key","Two signatures, one public key",[213,402,405],{"className":403,"code":404,"language":218},[216],"GRANT      the root says \"this key is blog\"   — issued once, at deployment\nENVELOPE   blog says \"here is my whole call\"  — signed per call\n",[220,406,404],{"__ignoreMap":222},[196,408,409,410,413],{},"A receiver holds the root's public key and ",[365,411,412],{},"nothing else",". It admits a Frond it has\nnever seen, and a Frond granted tomorrow is admitted by a receiver deployed today without\nits configuration being touched. That is the difference between an authority and a list of\nknown callers: the list has to be updated everywhere, and it goes stale.",[196,415,416],{},"Verification is offline — no service is joined, on any call or at any boot.",[205,418,420],{"id":419},"issuing","Issuing",[213,422,426],{"className":423,"code":424,"language":425,"meta":222,"style":222},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","fougere keys              # once per system — writes .fougere\u002Froot.key, prints FOUGERE_ROOT_KEY\nfougere grant blog        # once per Frond that CALLS — prints FOUGERE_KEY and FOUGERE_GRANT\n","bash",[220,427,428,441],{"__ignoreMap":222},[245,429,430,434,437],{"class":247,"line":248},[245,431,433],{"class":432},"sbgvK","fougere",[245,435,436],{"class":272}," keys",[245,438,440],{"class":439},"sutJx","              # once per system — writes .fougere\u002Froot.key, prints FOUGERE_ROOT_KEY\n",[245,442,443,445,448,451],{"class":247,"line":281},[245,444,433],{"class":432},[245,446,447],{"class":272}," grant",[245,449,450],{"class":272}," blog",[245,452,453],{"class":439},"        # once per Frond that CALLS — prints FOUGERE_KEY and FOUGERE_GRANT\n",[196,455,456,459,460,463,464,467,468,471],{},[220,457,458],{},"fougere keys"," is a command, not a service: it runs at deployment time and exits. Nothing\nstays alive, nothing is joined at boot. The root's private key is written to\n",[220,461,462],{},".fougere\u002Froot.key"," (mode ",[220,465,466],{},"600",", added to ",[220,469,470],{},".gitignore",") and never printed — it signs grants,\nand a grant is the only thing that has to travel.",[196,473,474,477,478,481],{},[220,475,476],{},"fougere grant"," prints and stores nothing. Re-running it issues a ",[365,479,480],{},"new"," key rather than\nshowing the old one, which is what rotation is: run it again, redeploy that Frond, and\nnobody else's configuration moves.",[205,483,485],{"id":484},"what-a-deployment-injects","What a deployment injects",[196,487,488],{},"Three variables, no configuration key — a private key does not belong in a committed file.\nPEM or base64, both are read.",[490,491,492,507],"table",{},[493,494,495],"thead",{},[496,497,498,501,504],"tr",{},[499,500],"th",{},[499,502,503],{},"Where",[499,505,506],{},"What it is",[508,509,510,527,542],"tbody",{},[496,511,512,518,524],{},[513,514,515],"td",{},[220,516,517],{},"FOUGERE_ROOT_KEY",[513,519,520,521],{},"every Frond that ",[365,522,523],{},"answers",[513,525,526],{},"public — safe in an image or a manifest",[496,528,529,534,539],{},[513,530,531],{},[220,532,533],{},"FOUGERE_KEY",[513,535,520,536],{},[365,537,538],{},"calls",[513,540,541],{},"secret, shown once",[496,543,544,549,552],{},[513,545,546],{},[220,547,548],{},"FOUGERE_GRANT",[513,550,551],{},"beside it",[513,553,554],{},"the root's word that this key is that Frond",[196,556,557,558,561],{},"A Frond may hold either half or both: one that only answers has no key, one that only calls\ntrusts no root, one in the middle does both. Trusting a root ",[365,559,560],{},"is"," asking to refuse — there\nis no second flag, because a deployment that names an authority and then accepts unsigned\ncalls has said two things at once.",[205,563,565],{"id":564},"what-a-handler-sees","What a handler sees",[196,567,568,569,573,574,577],{},"Nothing new to write. The state arrives where it always did, so a\n",[570,571,572],"a",{"href":94},"collector"," reading ",[220,575,576],{},"ctx.state.user"," is unchanged — in process,\nand split. What is added is one field:",[213,579,583],{"className":580,"code":581,"language":582,"meta":222,"style":222},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","invocation.caller   \u002F\u002F 'blog' — the Frond that signed, as the root named it\n","ts",[220,584,585],{"__ignoreMap":222},[245,586,587,591,594,597],{"class":247,"line":248},[245,588,590],{"class":589},"su5hD","invocation",[245,592,593],{"class":251},".",[245,595,596],{"class":589},"caller   ",[245,598,599],{"class":439},"\u002F\u002F 'blog' — the Frond that signed, as the root named it\n",[196,601,602,603,606,607,609,610,613,614,617,618,621,622,625,626,593],{},"It is top-level and not a key of ",[220,604,605],{},"state",", and the reason decides how to read it: ",[220,608,605],{},"\ntravels unverified when no verifier is wired, so a ",[220,611,612],{},"caller"," living inside it would be\nforgeable exactly where nothing checks. ",[365,615,616],{},"Absent means not established",", and there is no\nspelling for a claim. A sender clears it on every hop, so it names the last one:\n",[220,619,620],{},"shop → catalog → billing"," has ",[220,623,624],{},"billing"," read ",[220,627,628],{},"catalog",[205,630,632],{"id":631},"what-this-does-not-do","What this does not do",[196,634,635,638,639,642],{},[365,636,637],{},"Authorization."," A signature says who is calling, never what they may call.\n",[220,640,641],{},"if (user.role !== 'admin') throw"," is still yours to write.",[196,644,645,648,649,652],{},[365,646,647],{},"A mesh's job."," Under a service mesh the sidecar terminated mTLS before your process saw\nthe request, and asking for a second signature would redo what was just done a centimetre\naway. That is what ",[220,650,651],{},"allowUnsigned: true"," is for — spelled separately from everything else,\nso it can only ever mean \"I thought about this\".",[196,654,655,658],{},[365,656,657],{},"Cross-language bodies."," The body is bound by digest over its JSON text, which is the one\nplace key order matters. A sender written in another language must emit the body the way it\nhashed it. Everything else is bound by value, which is what keeps a canonical-JSON\ndependency out of the wire.",[205,660,662],{"id":661},"the-browser-is-not-in-this","The browser is not in this",[196,664,665,666,668,669,593],{},"A browser sits outside the topology and holds no key. It never signs, and nothing it sends\nis taken as identity: the host resolves the session server-side from the request's own\ncookie and the payload's ",[220,667,605],{}," is dropped. That was already true and has not changed —\nthis page is about the link between two ",[200,670,671],{},"processes",[673,674,675],"style",{},"html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sseR_, html code.shiki .sseR_{--shiki-light:#9C3EDA;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":222,"searchDepth":281,"depth":281,"links":677},[678,679,680,681,682,683,684,685],{"id":207,"depth":281,"text":208},{"id":232,"depth":281,"text":233},{"id":399,"depth":281,"text":400},{"id":419,"depth":281,"text":420},{"id":484,"depth":281,"text":485},{"id":564,"depth":281,"text":565},{"id":631,"depth":281,"text":632},{"id":661,"depth":281,"text":662},"How a receiver establishes who is calling — signed envelopes, one root, and the rule that there is no third way to serve.","md",{},true,{"title":175,"description":686},"16zRymI0xGemSFd9RE2E6NlHPbquURuEJqH5tsOruUU",1788560231345]