[{"data":1,"prerenderedAt":1143},["ShallowReactive",2],{"navigation_docs":3,"-guide-concepts-identity":587,"-guide-concepts-identity-surround":1138},[4,50,155,441,504],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":49},"Get Started","i-lucide-rocket","\u002Fget-started","1.get-started",[10,15,20,25,30,35,40,45],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fget-started\u002Fintroduction","1.get-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fget-started\u002Finstallation","1.get-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Project structure","\u002Fget-started\u002Fproject-structure","1.get-started\u002F3.project-structure","i-lucide-folder-tree",{"title":26,"path":27,"stem":28,"icon":29},"Configuration","\u002Fget-started\u002Fconfiguration","1.get-started\u002F4.configuration","i-lucide-settings",{"title":31,"path":32,"stem":33,"icon":34},"Authentication","\u002Fget-started\u002Fauthentication","1.get-started\u002F5.authentication","i-lucide-lock",{"title":36,"path":37,"stem":38,"icon":39},"Adding modules","\u002Fget-started\u002Fadding-modules","1.get-started\u002F6.adding-modules","i-lucide-puzzle",{"title":41,"path":42,"stem":43,"icon":44},"Database","\u002Fget-started\u002Fdatabase","1.get-started\u002F7.database","i-lucide-database",{"title":46,"path":47,"stem":48,"icon":6},"Deployment","\u002Fget-started\u002Fdeployment","1.get-started\u002F8.deployment",false,{"title":51,"icon":52,"path":53,"stem":54,"children":55,"page":49},"Guide","i-lucide-book-open","\u002Fguide","2.guide",[56,90,115,125],{"title":57,"icon":49,"path":58,"stem":59,"children":60,"page":49},"Concepts","\u002Fguide\u002Fconcepts","2.guide\u002F1.concepts",[61,66,71,76,80,85],{"title":62,"path":63,"stem":64,"icon":65},"Architecture","\u002Fguide\u002Fconcepts\u002Farchitecture","2.guide\u002F1.concepts\u002F1.architecture","i-lucide-blocks",{"title":67,"path":68,"stem":69,"icon":70},"Access control","\u002Fguide\u002Fconcepts\u002Fauth-and-scopes","2.guide\u002F1.concepts\u002F2.auth-and-scopes","i-lucide-shield-check",{"title":72,"path":73,"stem":74,"icon":75},"Extension points","\u002Fguide\u002Fconcepts\u002Fextension-points","2.guide\u002F1.concepts\u002F3.extension-points","i-lucide-plug",{"title":77,"path":78,"stem":79,"icon":44},"Database extensions","\u002Fguide\u002Fconcepts\u002Fdatabase-extensions","2.guide\u002F1.concepts\u002F4.database-extensions",{"title":81,"path":82,"stem":83,"icon":84},"Identity","\u002Fguide\u002Fconcepts\u002Fidentity","2.guide\u002F1.concepts\u002F5.identity","i-lucide-fingerprint",{"title":86,"path":87,"stem":88,"icon":89},"Glossary","\u002Fguide\u002Fconcepts\u002Fglossary","2.guide\u002F1.concepts\u002F6.glossary","i-lucide-book-a",{"title":91,"icon":49,"path":92,"stem":93,"children":94,"page":49},"UX patterns","\u002Fguide\u002Fux-patterns","2.guide\u002F2.ux-patterns",[95,100,105,110],{"title":96,"path":97,"stem":98,"icon":99},"Layouts and navigation","\u002Fguide\u002Fux-patterns\u002Flayouts-and-navigation","2.guide\u002F2.ux-patterns\u002F1.layouts-and-navigation","i-lucide-layout-template",{"title":101,"path":102,"stem":103,"icon":104},"Pages","\u002Fguide\u002Fux-patterns\u002Fpages","2.guide\u002F2.ux-patterns\u002F2.pages","i-lucide-table",{"title":106,"path":107,"stem":108,"icon":109},"Forms and actions","\u002Fguide\u002Fux-patterns\u002Fforms-and-actions","2.guide\u002F2.ux-patterns\u002F3.forms-and-actions","i-lucide-square-pen",{"title":111,"path":112,"stem":113,"icon":114},"Text and formatting","\u002Fguide\u002Fux-patterns\u002Ftext-and-formatting","2.guide\u002F2.ux-patterns\u002F4.text-and-formatting","i-lucide-type",{"title":116,"icon":49,"path":117,"stem":118,"children":119,"page":49},"Best practices","\u002Fguide\u002Fbest-practices","2.guide\u002F3.best-practices",[120],{"title":121,"path":122,"stem":123,"icon":124},"HTTP routes","\u002Fguide\u002Fbest-practices\u002Fhttp-routes","2.guide\u002F3.best-practices\u002F1.http-routes","i-lucide-route",{"title":126,"icon":49,"path":127,"stem":128,"children":129,"page":49},"Going further","\u002Fguide\u002Fgoing-further","2.guide\u002F4.going-further",[130,135,140,145,150],{"title":131,"path":132,"stem":133,"icon":134},"Use your own OAuth server","\u002Fguide\u002Fgoing-further\u002Fown-oauth-server","2.guide\u002F4.going-further\u002F1.own-oauth-server","i-lucide-key-round",{"title":136,"path":137,"stem":138,"icon":139},"Create an OAuth application","\u002Fguide\u002Fgoing-further\u002Foauth-application","2.guide\u002F4.going-further\u002F2.oauth-application","i-lucide-app-window",{"title":141,"path":142,"stem":143,"icon":144},"Signing keys","\u002Fguide\u002Fgoing-further\u002Fsigning-keys","2.guide\u002F4.going-further\u002F3.signing-keys","i-lucide-key-square",{"title":146,"path":147,"stem":148,"icon":149},"Stripe setup","\u002Fguide\u002Fgoing-further\u002Fstripe-setup","2.guide\u002F4.going-further\u002F4.stripe-setup","i-lucide-credit-card",{"title":151,"path":152,"stem":153,"icon":154},"Migrate to the permission model","\u002Fguide\u002Fgoing-further\u002Fmigrate-access-control","2.guide\u002F4.going-further\u002F5.migrate-access-control","i-lucide-arrow-right-left",{"title":156,"icon":157,"path":158,"stem":159,"children":160},"Modules","i-lucide-boxes","\u002Fmodules","3.modules",[161,163,244,273,320,375],{"title":156,"path":158,"stem":162},"3.modules\u002Findex",{"title":81,"icon":49,"path":164,"stem":165,"children":166,"page":49},"\u002Fmodules\u002Fidentity","3.modules\u002F1.identity",[167,190,194,198,220,224,240],{"title":168,"path":169,"stem":170,"children":171},"Auth","\u002Fmodules\u002Fidentity\u002Fauth","3.modules\u002F1.identity\u002F1.auth\u002Findex",[172,174,178,182,186],{"title":173,"path":169,"stem":170},"Overview",{"title":175,"path":176,"stem":177},"Server API","\u002Fmodules\u002Fidentity\u002Fauth\u002Fserver","3.modules\u002F1.identity\u002F1.auth\u002F2.server",{"title":179,"path":180,"stem":181},"Reference","\u002Fmodules\u002Fidentity\u002Fauth\u002Freference","3.modules\u002F1.identity\u002F1.auth\u002F3.reference",{"title":183,"path":184,"stem":185},"Client mode","\u002Fmodules\u002Fidentity\u002Fauth\u002Fclient-mode","3.modules\u002F1.identity\u002F1.auth\u002F4.client-mode",{"title":187,"path":188,"stem":189},"OAuth providers","\u002Fmodules\u002Fidentity\u002Fauth\u002Foauth-providers","3.modules\u002F1.identity\u002F1.auth\u002F5.oauth-providers",{"title":191,"path":192,"stem":193},"Users","\u002Fmodules\u002Fidentity\u002Fusers","3.modules\u002F1.identity\u002F2.users",{"title":195,"path":196,"stem":197},"Account","\u002Fmodules\u002Fidentity\u002Faccount","3.modules\u002F1.identity\u002F3.account",{"title":199,"path":200,"stem":201,"children":202},"Authorization","\u002Fmodules\u002Fidentity\u002Fauthorization","3.modules\u002F1.identity\u002F4.authorization\u002Findex",[203,204,208,212,216],{"title":173,"path":200,"stem":201},{"title":205,"path":206,"stem":207},"Roles and requirements","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Froles-and-requirements","3.modules\u002F1.identity\u002F4.authorization\u002F2.roles-and-requirements",{"title":209,"path":210,"stem":211},"Policies","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fpolicies","3.modules\u002F1.identity\u002F4.authorization\u002F3.policies",{"title":213,"path":214,"stem":215},"Client","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fclient","3.modules\u002F1.identity\u002F4.authorization\u002F4.client",{"title":217,"path":218,"stem":219},"Admin","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fadmin","3.modules\u002F1.identity\u002F4.authorization\u002F5.admin",{"title":221,"path":222,"stem":223},"Preferences","\u002Fmodules\u002Fidentity\u002Fpreferences","3.modules\u002F1.identity\u002F5.preferences",{"title":225,"path":226,"stem":227,"children":228},"OAuth Server","\u002Fmodules\u002Fidentity\u002Foauth-server","3.modules\u002F1.identity\u002F6.oauth-server\u002Findex",[229,230,233,237],{"title":173,"path":226,"stem":227},{"title":175,"path":231,"stem":232},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver","3.modules\u002F1.identity\u002F6.oauth-server\u002F2.server",{"title":234,"path":235,"stem":236},"Hooks","\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fhooks","3.modules\u002F1.identity\u002F6.oauth-server\u002F3.hooks",{"title":179,"path":238,"stem":239},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Froutes-and-clients","3.modules\u002F1.identity\u002F6.oauth-server\u002F4.routes-and-clients",{"title":241,"path":242,"stem":243},"OAuth Resource","\u002Fmodules\u002Fidentity\u002Foauth-resource","3.modules\u002F1.identity\u002F8.oauth-resource",{"title":245,"icon":49,"path":246,"stem":247,"children":248,"page":49},"Commerce","\u002Fmodules\u002Fcommerce","3.modules\u002F2.commerce",[249,253,257],{"title":250,"path":251,"stem":252},"Stripe","\u002Fmodules\u002Fcommerce\u002Fstripe","3.modules\u002F2.commerce\u002F1.stripe",{"title":254,"path":255,"stem":256},"Payments","\u002Fmodules\u002Fcommerce\u002Fpayments","3.modules\u002F2.commerce\u002F2.payments",{"title":258,"path":259,"stem":260,"children":261},"Subscriptions","\u002Fmodules\u002Fcommerce\u002Fsubscriptions","3.modules\u002F2.commerce\u002F3.subscriptions\u002Findex",[262,263,266,270],{"title":173,"path":259,"stem":260},{"title":175,"path":264,"stem":265},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fserver","3.modules\u002F2.commerce\u002F3.subscriptions\u002F2.server",{"title":267,"path":268,"stem":269},"App","\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fapp","3.modules\u002F2.commerce\u002F3.subscriptions\u002F3.app",{"title":179,"path":271,"stem":272},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Freference","3.modules\u002F2.commerce\u002F3.subscriptions\u002F4.reference",{"title":274,"icon":49,"path":275,"stem":276,"children":277,"page":49},"Features","\u002Fmodules\u002Ffeatures","3.modules\u002F3.features",[278,293,312,316],{"title":279,"path":280,"stem":281,"children":282},"Events","\u002Fmodules\u002Ffeatures\u002Fevents","3.modules\u002F3.features\u002F1.events\u002Findex",[283,284,287,290],{"title":173,"path":280,"stem":281},{"title":175,"path":285,"stem":286},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fserver","3.modules\u002F3.features\u002F1.events\u002F2.server",{"title":267,"path":288,"stem":289},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fapp","3.modules\u002F3.features\u002F1.events\u002F3.app",{"title":179,"path":291,"stem":292},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Freference","3.modules\u002F3.features\u002F1.events\u002F4.reference",{"title":294,"path":295,"stem":296,"children":297},"Forms","\u002Fmodules\u002Ffeatures\u002Fforms","3.modules\u002F3.features\u002F2.forms\u002Findex",[298,299,303,306,309],{"title":173,"path":295,"stem":296},{"title":300,"path":301,"stem":302},"Definition","\u002Fmodules\u002Ffeatures\u002Fforms\u002Fdefinition","3.modules\u002F3.features\u002F2.forms\u002F2.definition",{"title":175,"path":304,"stem":305},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fserver","3.modules\u002F3.features\u002F2.forms\u002F3.server",{"title":267,"path":307,"stem":308},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fapp","3.modules\u002F3.features\u002F2.forms\u002F4.app",{"title":179,"path":310,"stem":311},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Freference","3.modules\u002F3.features\u002F2.forms\u002F5.reference",{"title":313,"path":314,"stem":315},"Cookie Consent","\u002Fmodules\u002Ffeatures\u002Fcookie-consent","3.modules\u002F3.features\u002F3.cookie-consent",{"title":317,"path":318,"stem":319},"MCP","\u002Fmodules\u002Ffeatures\u002Fmcp","3.modules\u002F3.features\u002F4.mcp",{"title":321,"icon":49,"path":322,"stem":323,"children":324,"page":49},"Interface","\u002Fmodules\u002Finterface","3.modules\u002F4.interface",[325,350,353,357,361],{"title":326,"path":327,"stem":328,"children":329},"UI","\u002Fmodules\u002Finterface\u002Fui","3.modules\u002F4.interface\u002F1.ui\u002Findex",[330,331,335,339,343,347],{"title":173,"path":327,"stem":328},{"title":332,"path":333,"stem":334},"Page components","\u002Fmodules\u002Finterface\u002Fui\u002Fpage-components","3.modules\u002F4.interface\u002F1.ui\u002F2.page-components",{"title":336,"path":337,"stem":338},"App shell","\u002Fmodules\u002Finterface\u002Fui\u002Fapp-shell","3.modules\u002F4.interface\u002F1.ui\u002F3.app-shell",{"title":340,"path":341,"stem":342},"Form inputs","\u002Fmodules\u002Finterface\u002Fui\u002Fform-inputs","3.modules\u002F4.interface\u002F1.ui\u002F4.form-inputs",{"title":344,"path":345,"stem":346},"Composables and helpers","\u002Fmodules\u002Finterface\u002Fui\u002Fcomposables","3.modules\u002F4.interface\u002F1.ui\u002F5.composables",{"title":179,"path":348,"stem":349},"\u002Fmodules\u002Finterface\u002Fui\u002Freference","3.modules\u002F4.interface\u002F1.ui\u002F6.reference",{"title":217,"path":351,"stem":352},"\u002Fmodules\u002Finterface\u002Fadmin","3.modules\u002F4.interface\u002F2.admin",{"title":354,"path":355,"stem":356},"Menu","\u002Fmodules\u002Finterface\u002Fmenu","3.modules\u002F4.interface\u002F3.menu",{"title":358,"path":359,"stem":360},"Widget","\u002Fmodules\u002Finterface\u002Fwidget","3.modules\u002F4.interface\u002F4.widget",{"title":362,"path":363,"stem":364,"children":365},"Mobile","\u002Fmodules\u002Finterface\u002Fmobile","3.modules\u002F4.interface\u002F5.mobile\u002Findex",[366,367,371],{"title":173,"path":363,"stem":364},{"title":368,"path":369,"stem":370},"CLI and devices","\u002Fmodules\u002Finterface\u002Fmobile\u002Fcli-and-devices","3.modules\u002F4.interface\u002F5.mobile\u002F2.cli-and-devices",{"title":372,"path":373,"stem":374},"Splash screen","\u002Fmodules\u002Finterface\u002Fmobile\u002Fsplash-screen","3.modules\u002F4.interface\u002F5.mobile\u002F3.splash-screen",{"title":376,"icon":49,"path":377,"stem":378,"children":379,"page":49},"Platform","\u002Fmodules\u002Fplatform","3.modules\u002F5.platform",[380,402,406,423],{"title":381,"path":382,"stem":383,"children":384},"Module Kit","\u002Fmodules\u002Fplatform\u002Fmodule-kit","3.modules\u002F5.platform\u002F1.module-kit\u002Findex",[385,386,390,394,398],{"title":173,"path":382,"stem":383},{"title":387,"path":388,"stem":389},"Server toolkit","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fserver-toolkit","3.modules\u002F5.platform\u002F1.module-kit\u002F2.server-toolkit",{"title":391,"path":392,"stem":393},"Registries and menus","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fregistries-and-menus","3.modules\u002F5.platform\u002F1.module-kit\u002F3.registries-and-menus",{"title":395,"path":396,"stem":397},"Translations","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fi18n","3.modules\u002F5.platform\u002F1.module-kit\u002F4.i18n",{"title":399,"path":400,"stem":401},"Database helpers","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fdatabase","3.modules\u002F5.platform\u002F1.module-kit\u002F5.database",{"title":403,"path":404,"stem":405},"Notify","\u002Fmodules\u002Fplatform\u002Fnotify","3.modules\u002F5.platform\u002F2.notify",{"title":407,"path":408,"stem":409,"children":410},"Email Templates","\u002Fmodules\u002Fplatform\u002Femail-templates","3.modules\u002F5.platform\u002F3.email-templates\u002Findex",[411,412,416,419],{"title":173,"path":408,"stem":409},{"title":413,"path":414,"stem":415},"Email components","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fcomponents","3.modules\u002F5.platform\u002F3.email-templates\u002F2.components",{"title":175,"path":417,"stem":418},"\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fserver","3.modules\u002F5.platform\u002F3.email-templates\u002F3.server",{"title":420,"path":421,"stem":422},"Module emails","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fmodule-emails","3.modules\u002F5.platform\u002F3.email-templates\u002F4.module-emails",{"title":424,"path":425,"stem":426,"children":427},"Base Layer","\u002Fmodules\u002Fplatform\u002Fbase-layer","3.modules\u002F5.platform\u002F4.base-layer\u002Findex",[428,429,433,437],{"title":173,"path":425,"stem":426},{"title":430,"path":431,"stem":432},"Content collections","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcontent-collections","3.modules\u002F5.platform\u002F4.base-layer\u002F2.content-collections",{"title":434,"path":435,"stem":436},"Layouts and routing","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Flayouts-and-routing","3.modules\u002F5.platform\u002F4.base-layer\u002F3.layouts-and-routing",{"title":438,"path":439,"stem":440},"Composables and utils","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcomposables-and-utils","3.modules\u002F5.platform\u002F4.base-layer\u002F4.composables-and-utils",{"title":442,"icon":443,"path":444,"stem":445,"children":446,"page":49},"Examples","i-lucide-code-xml","\u002Fexamples","4.examples",[447,466,476],{"title":448,"icon":49,"path":449,"stem":450,"children":451,"page":49},"Starters","\u002Fexamples\u002Fstarters","4.examples\u002F1.starters",[452,456,461],{"title":453,"path":454,"stem":455,"icon":6},"Workspace starter","\u002Fexamples\u002Fstarters\u002Fworkspace","4.examples\u002F1.starters\u002F1.workspace",{"title":457,"path":458,"stem":459,"icon":460},"Mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile","4.examples\u002F1.starters\u002F2.mobile","i-lucide-smartphone",{"title":462,"path":463,"stem":464,"icon":465},"Standalone mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile-standalone","4.examples\u002F1.starters\u002F3.mobile-standalone","i-lucide-tablet-smartphone",{"title":467,"icon":49,"path":468,"stem":469,"children":470,"page":49},"Apps","\u002Fexamples\u002Fapps","4.examples\u002F2.apps",[471],{"title":472,"path":473,"stem":474,"icon":475},"Frigear website app","\u002Fexamples\u002Fapps\u002Ffrigear-nu-website","4.examples\u002F2.apps\u002F1.frigear-nu-website","i-lucide-layout-dashboard",{"title":477,"icon":49,"path":478,"stem":479,"children":480,"page":49},"Tasks","\u002Fexamples\u002Ftasks","4.examples\u002F3.tasks",[481,485,489,494,499],{"title":482,"path":483,"stem":484,"icon":70},"Protect a route","\u002Fexamples\u002Ftasks\u002Fprotect-a-route","4.examples\u002F3.tasks\u002F1.protect-a-route",{"title":486,"path":487,"stem":488,"icon":475},"Add an admin page","\u002Fexamples\u002Ftasks\u002Fadd-an-admin-page","4.examples\u002F3.tasks\u002F2.add-an-admin-page",{"title":490,"path":491,"stem":492,"icon":493},"Sell event tickets","\u002Fexamples\u002Ftasks\u002Fsell-event-tickets","4.examples\u002F3.tasks\u002F3.sell-event-tickets","i-lucide-ticket",{"title":495,"path":496,"stem":497,"icon":498},"Build a form","\u002Fexamples\u002Ftasks\u002Fbuild-a-form","4.examples\u002F3.tasks\u002F4.build-a-form","i-lucide-clipboard-list",{"title":500,"path":501,"stem":502,"icon":503},"Send an email","\u002Fexamples\u002Ftasks\u002Fsend-an-email","4.examples\u002F3.tasks\u002F5.send-an-email","i-lucide-mail",{"title":505,"icon":506,"path":507,"stem":508,"children":509,"page":49},"Contribute","i-lucide-git-pull-request","\u002Fcontribute","5.contribute",[510,513,518,544,549,552,556,571],{"title":173,"path":511,"stem":512,"icon":506},"\u002Fcontribute\u002Foverview","5.contribute\u002F1.overview",{"title":514,"path":515,"stem":516,"icon":517},"Local development","\u002Fcontribute\u002Flocal-development","5.contribute\u002F2.local-development","i-lucide-terminal",{"title":519,"icon":49,"path":520,"stem":521,"children":522,"page":49},"Writing a module","\u002Fcontribute\u002Fwriting-a-module","5.contribute\u002F3.writing-a-module",[523,527,532,536,541],{"title":524,"path":525,"stem":526,"icon":39},"Scaffold a module","\u002Fcontribute\u002Fwriting-a-module\u002Fscaffold","5.contribute\u002F3.writing-a-module\u002F1.scaffold",{"title":528,"path":529,"stem":530,"icon":531},"Setup checklist","\u002Fcontribute\u002Fwriting-a-module\u002Fsetup-checklist","5.contribute\u002F3.writing-a-module\u002F2.setup-checklist","i-lucide-list-checks",{"title":533,"path":534,"stem":535,"icon":44},"Database tables","\u002Fcontribute\u002Fwriting-a-module\u002Fdatabase-tables","5.contribute\u002F3.writing-a-module\u002F3.database-tables",{"title":537,"path":538,"stem":539,"icon":540},"Testing","\u002Fcontribute\u002Fwriting-a-module\u002Ftesting","5.contribute\u002F3.writing-a-module\u002F4.testing","i-lucide-flask-conical",{"title":67,"path":542,"stem":543,"icon":70},"\u002Fcontribute\u002Fwriting-a-module\u002Faccess-control","5.contribute\u002F3.writing-a-module\u002F5.access-control",{"title":545,"path":546,"stem":547,"icon":548},"Publishing","\u002Fcontribute\u002Fpublishing","5.contribute\u002F4.publishing","i-lucide-package-check",{"title":448,"path":550,"stem":551,"icon":6},"\u002Fcontribute\u002Fstarters","5.contribute\u002F5.starters",{"title":553,"path":554,"stem":555,"icon":52},"Documentation","\u002Fcontribute\u002Fdocumentation","5.contribute\u002F6.documentation",{"title":557,"icon":49,"path":558,"stem":559,"children":560,"page":49},"Tooling","\u002Fcontribute\u002Ftooling","5.contribute\u002F7.tooling",[561,566],{"title":562,"path":563,"stem":564,"icon":565},"CLI","\u002Fcontribute\u002Ftooling\u002Fcli","5.contribute\u002F7.tooling\u002F1.cli","i-lucide-square-terminal",{"title":567,"path":568,"stem":569,"icon":570},"ESLint config","\u002Fcontribute\u002Ftooling\u002Feslint-config","5.contribute\u002F7.tooling\u002F2.eslint-config","i-lucide-spell-check",{"title":572,"icon":49,"path":573,"stem":574,"children":575},"Decisions","\u002Fcontribute\u002Fdecisions","5.contribute\u002F8.decisions\u002Findex",[576,578,583],{"title":572,"path":573,"stem":574,"icon":577},"i-lucide-scale",{"title":579,"path":580,"stem":581,"icon":582},"Session lifetimes per OAuth client","\u002Fcontribute\u002Fdecisions\u002Foauth-token-lifetimes","5.contribute\u002F8.decisions\u002F1.oauth-token-lifetimes","i-lucide-timer",{"title":584,"path":585,"stem":586,"icon":70},"One permission model","\u002Fcontribute\u002Fdecisions\u002Fpermission-model","5.contribute\u002F8.decisions\u002F2.permission-model",{"id":588,"title":81,"body":589,"description":1131,"extension":1132,"links":1133,"meta":1134,"navigation":1135,"path":82,"seo":1136,"stem":83,"__hash__":1137},"docs\u002F2.guide\u002F1.concepts\u002F5.identity.md",{"type":590,"value":591,"toc":1116},"minimark",[592,596,605,610,694,705,724,728,765,769,772,777,885,895,899,902,916,923,926,930,940,958,969,973,976,1018,1028,1032,1035,1064,1073,1077,1084,1090,1094],[593,594,595],"p",{},"A web app, a mobile app and the services they call all need to know who the user is. Several modules each handle one\npart of that, and picking the wrong one can leave tokens where any script can read them. After this page you can decide\nwhich module each of your apps needs, and whether its OAuth application is public or confidential.",[597,598,600,604],"callout",{"icon":599},"i-lucide-users",[601,602,603],"strong",{},"For:"," app developers who sign users in across more than one app.",[606,607,609],"h2",{"id":608},"which-one-do-i-use","Which one do I use?",[611,612,613,626],"table",{},[614,615,616],"thead",{},[617,618,619,623],"tr",{},[620,621,622],"th",{},"You are building",[620,624,625],{},"Use",[627,628,629,641,652,664,673,682],"tbody",{},[617,630,631,635],{},[632,633,634],"td",{},"A web app with its own server, whose users sign in at an issuer",[632,636,637,640],{},[638,639,168],"a",{"href":169}," in server mode, with Sign in with frigear.nu, as a confidential OAuth application",[617,642,643,646],{},[632,644,645],{},"A single-page app (SPA) in the browser",[632,647,648,651],{},[638,649,650],{"href":184},"Auth in client mode",", as a public OAuth application",[617,653,654,657],{},[632,655,656],{},"A mobile app",[632,658,659,661,662,651],{},[638,660,362],{"href":363}," with ",[638,663,650],{"href":184},[617,665,666,669],{},[632,667,668],{},"The service the mobile app calls",[632,670,671],{},[638,672,241],{"href":242},[617,674,675,678],{},[632,676,677],{},"Your own issuer",[632,679,680],{},[638,681,225],{"href":226},[617,683,684,687],{},[632,685,686],{},"An app that signs in at another issuer (Keycloak, Auth0, Zitadel)",[632,688,689,691,692],{},[638,690,650],{"href":184}," pointed at it. See ",[638,693,131],{"href":132},[695,696,697,698,701,702,704],"caution",{},"Keep a web app with its own server in Auth's ",[638,699,700],{"href":87},"server mode",", with its single sign-on.\n",[638,703,183],{"href":87}," keeps the tokens in the browser, where any cross-site scripting (XSS) attack can\nread them. The server also cannot know who is signed in. Moving a web app from server mode to client mode is a\ndowngrade.",[593,706,707,708,712,713,716,717,720,721,723],{},"Both modes have the same app API: ",[709,710,711],"code",{},"useUserSession",", ",[709,714,715],{},"useAuth"," and ",[709,718,719],{},"$api",". Both take their guards from\n",[638,722,199],{"href":200},". In client mode those guards only shape the interface: the API decides.",[606,725,727],{"id":726},"terms","Terms",[729,730,731,741,749,757],"ul",{},[732,733,734,740],"li",{},[601,735,736,739],{},[638,737,738],{"href":87},"Issuer","."," The server where users sign in and which issues tokens to other apps.\nFrigear's issuer is the OAuth Server module.",[732,742,743,748],{},[601,744,745,739],{},[638,746,747],{"href":87},"Public \u002F confidential"," The two OAuth application types, fixed at creation. A\nconfidential one is a server that keeps a client secret, and a public one has no secret.",[732,750,751,756],{},[601,752,753,739],{},[638,754,755],{"href":87},"PKCE"," Proof Key for Code Exchange: a one-time secret that proves the app finishing a\nsign-in is the app that started it.",[732,758,759,764],{},[601,760,761,739],{},[638,762,763],{"href":87},"SPA"," Single-page application: a browser app with no server of its own.",[606,766,768],{"id":767},"how-it-works","How it works",[593,770,771],{},"The modules are different roles in one OAuth 2.0 \u002F OpenID Connect (OIDC) picture. They are not alternatives for the\nsame job.",[773,774,776],"h3",{"id":775},"the-roles","The roles",[611,778,779,795],{},[614,780,781],{},[617,782,783,786,789,792],{},[620,784,785],{},"Role",[620,787,788],{},"Module",[620,790,791],{},"Runs in",[620,793,794],{},"Does",[627,796,797,813,831,848,865],{},[617,798,799,803,807,810],{},[632,800,801],{},[601,802,738],{},[632,804,805],{},[638,806,225],{"href":226},[632,808,809],{},"The server of frigear.nu, or your own",[632,811,812],{},"Users sign in, OAuth applications are registered, tokens are issued.",[617,814,815,820,825,828],{},[632,816,817],{},[601,818,819],{},"Server mode app",[632,821,822,824],{},[638,823,168],{"href":169}," (Sign in with frigear.nu)",[632,826,827],{},"A Nuxt app with a server",[632,829,830],{},"Confidential OIDC sign-in at an issuer. The server knows who is signed in.",[617,832,833,838,842,845],{},[632,834,835],{},[601,836,837],{},"Client mode app",[632,839,840],{},[638,841,650],{"href":184},[632,843,844],{},"The browser or a phone",[632,846,847],{},"Signs in with PKCE, keeps and refreshes the tokens, and sends them to the API.",[617,849,850,855,859,862],{},[632,851,852],{},[601,853,854],{},"Resource server",[632,856,857],{},[638,858,241],{"href":242},[632,860,861],{},"Each service the app calls",[632,863,864],{},"Verifies the access token a service receives.",[617,866,867,871,875,878],{},[632,868,869],{},[601,870,336],{},[632,872,873],{},[638,874,362],{"href":363},[632,876,877],{},"The phone",[632,879,880,881,884],{},"The Capacitor shell. Installs Auth when ",[709,882,883],{},"authentication"," is configured; the app sets client mode.",[886,887,893],"pre",{"className":888,"code":890,"language":891,"meta":892},[889],"language-text","phone: Mobile + Auth (client) ──sign in──▶ issuer: OAuth Server (frigear.nu or your own)\n        │                                       │ access token\n        └──────── Bearer token ────────▶ service: OAuth Resource ──▶ requireAuth\n","text","",[709,894,890],{"__ignoreMap":892},[773,896,898],{"id":897},"public-and-confidential-applications","Public and confidential applications",[593,900,901],{},"An OAuth application registered at the issuer is one of two types. The type is fixed when the application is created.",[729,903,904,910],{},[732,905,906,909],{},[601,907,908],{},"Confidential."," A server keeps a secret and sends it with every token request, next to PKCE. Use it for a web app\nwith its own server.",[732,911,912,915],{},[601,913,914],{},"Public."," Anything shipped to a browser or a device can be extracted, so there is no secret. PKCE with S256\nprotects the code exchange. Refresh token rotation protects the refresh token. Use it for an SPA, a Capacitor app, a\ndesktop app or a command-line tool.",[917,918,919,922],"note",{},[709,920,921],{},"nuxt.config"," cannot hold a confidential OAuth application. Register one in the admin area of the issuer.",[593,924,925],{},"PKCE with S256 is mandatory for both types. There is no machine-to-machine flow: every token belongs to a signed-in\nuser.",[773,927,929],{"id":928},"sign-in-with-frigearnu","Sign in with frigear.nu",[593,931,932,935,936,939],{},[709,933,934],{},"oauth.frigear"," in Auth registers ",[709,937,938],{},"GET \u002Fauth\u002Ffrigear",", an OIDC sign-in at frigear.nu for pull request previews and local\ndevelopment. It is decided per request. It is active when either holds:",[729,941,942,952],{},[732,943,944,947,948,951],{},[709,945,946],{},"runtimeConfig.previewDeployment"," is ",[709,949,950],{},"true",",",[732,953,954,957],{},[709,955,956],{},"runtimeConfig.frigear.sso"," has a client id and secret.",[593,959,960,961,964,965,968],{},"Register ",[709,962,963],{},"\u003Corigin>\u002Fauth\u002Ffrigear"," as the callback at the issuer. Users are matched by email. The ",[709,966,967],{},"role"," claim can be\napplied at every sign-in.",[773,970,972],{"id":971},"one-app-for-every-service","One app for every service",[593,974,975],{},"frigear.nu can be the only sign-in of one mobile app that talks to every Frigear service, such as vouchers, shop and\ninventory. The flow:",[977,978,979,982,985,992,1003],"ol",{},[732,980,981],{},"The mobile app is a public OAuth application at frigear.nu, with a native redirect URI and no secret.",[732,983,984],{},"It signs in with PKCE. It asks for the scopes of the services it needs, and keeps the rotating refresh token.",[732,986,987,988,991],{},"Every access token has one shared audience (",[709,989,990],{},"accessTokenAudience","). It is short-lived, because services cannot revoke\nit.",[732,993,994,995,998,999,1002],{},"Each service installs OAuth Resource. It verifies the token and hands routes the usual Auth principal, with the\n",[709,996,997],{},"permissions"," claim the issuer froze into the token. So ",[709,1000,1001],{},"authorize(event, 'vouchers:read')"," works as it does for\nsessions and API tokens.",[732,1004,1005,1006,1009,1010,1013,1014,1017],{},"frigear.nu lists every service's scopes in ",[709,1007,1008],{},"oauthServer.scopes",", each with a ",[709,1011,1012],{},"service"," name. The consent screen\ngroups them by it. A scope is granted only to users whose roles allow every permission it stands for, and within the issuer's\n",[709,1015,1016],{},"roleScopes"," cap.",[593,1019,1020,1021,1024,1025,739],{},"A token without the scope gets ",[709,1022,1023],{},"403 insufficient_scope",". An invalid, expired or foreign token gets ",[709,1026,1027],{},"401",[773,1029,1031],{"id":1030},"any-issuer-works","Any issuer works",[593,1033,1034],{},"Auth in client mode signs in as a public OAuth application with PKCE at any OIDC issuer that meets a checklist:",[729,1036,1037,1040,1043,1046,1049,1052,1055,1061],{},[732,1038,1039],{},"discovery,",[732,1041,1042],{},"PKCE S256,",[732,1044,1045],{},"public OAuth applications,",[732,1047,1048],{},"exact redirect URIs,",[732,1050,1051],{},"cross-origin resource sharing (CORS) for the webview,",[732,1053,1054],{},"rotating refresh tokens,",[732,1056,1057,1060],{},[709,1058,1059],{},"iss"," in the response,",[732,1062,1063],{},"an id_token and a userinfo endpoint.",[593,1065,1066,1067,1070,1071,739],{},"Point ",[709,1068,1069],{},"authentication.client.issuer"," at it, and the rest of the app is unchanged. The checklist and notes per issuer are\nin ",[638,1072,131],{"href":132},[606,1074,1076],{"id":1075},"common-mistakes","Common mistakes",[1078,1079,1080,1083],"warning",{},[601,1081,1082],{},"Putting a web app in client mode."," Its tokens move into the browser, and its server no longer knows who is signed\nin. Keep it in server mode.",[1078,1085,1086,1089],{},[601,1087,1088],{},"Revoking access tokens and expecting services to notice."," Services cannot revoke an access token. Keep it short-lived\nand rely on its expiry.",[606,1091,1093],{"id":1092},"next-steps","Next steps",[729,1095,1096,1101,1106,1111],{},[732,1097,1098,1100],{},[638,1099,136],{"href":137}," – register your app at the issuer.",[732,1102,1103,1105],{},[638,1104,141],{"href":142}," – generate and rotate the key the issuer signs tokens with.",[732,1107,1108,1110],{},[638,1109,457],{"href":458}," – run a mobile app on this stack.",[732,1112,1113,1115],{},[638,1114,67],{"href":68}," – see how an access token becomes a principal and how a route decides.",{"title":892,"searchDepth":1117,"depth":1117,"links":1118},2,[1119,1120,1121,1129,1130],{"id":608,"depth":1117,"text":609},{"id":726,"depth":1117,"text":727},{"id":767,"depth":1117,"text":768,"children":1122},[1123,1125,1126,1127,1128],{"id":775,"depth":1124,"text":776},3,{"id":897,"depth":1124,"text":898},{"id":928,"depth":1124,"text":929},{"id":971,"depth":1124,"text":972},{"id":1030,"depth":1124,"text":1031},{"id":1075,"depth":1117,"text":1076},{"id":1092,"depth":1117,"text":1093},"Which module each of your apps needs to sign people in across a web app, a mobile app and the services they call.","md",null,{},{"icon":84},{"title":81,"description":1131},"qVAcGj1T78PfMiIeBE3SCpCn3h8Dpt_4aT_NDJ2DKw0",[1139,1141],{"title":77,"path":78,"stem":79,"description":1140,"icon":44,"children":-1},"How your app or a module adds columns to a table another module owns, why every schema ships once per dialect, and which migrations to commit.",{"title":86,"path":87,"stem":88,"description":1142,"icon":89,"children":-1},"The one word these docs use for each concept, what it means and which page explains it.",1794503147588]