[{"data":1,"prerenderedAt":1950},["ShallowReactive",2],{"navigation_docs":3,"-guide-going-further-own-oauth-server":587,"-guide-going-further-own-oauth-server-surround":1945},[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":131,"body":589,"description":1938,"extension":1939,"links":1940,"meta":1941,"navigation":1942,"path":132,"seo":1943,"stem":133,"__hash__":1944},"docs\u002F2.guide\u002F4.going-further\u002F1.own-oauth-server.md",{"type":590,"value":591,"toc":1926},"minimark",[592,601,614,627,642,658,663,666,823,831,844,856,869,908,912,969,980,983,994,1238,1289,1292,1487,1502,1506,1512,1544,1548,1551,1610,1616,1620,1664,1670,1674,1713,1716,1724,1728,1901,1905,1922],[593,594,595,596,600],"p",{},"Your users may already have accounts at an identity server you run, such as Keycloak, Auth0 or Zitadel. A Frigear\nmobile or browser app can sign them in there, instead of at frigear.nu. This page lists what that\n",[597,598,599],"a",{"href":87},"issuer"," must support, and how to set it up for the common ones.",[602,603,605,609,610,613],"callout",{"icon":604},"i-lucide-users",[606,607,608],"strong",{},"For:"," app developers whose app uses Auth in ",[597,611,612],{"href":87},"client mode",", and whoever runs the issuer.",[593,615,616,619,620,623,624,626],{},[597,617,618],{"href":184},"Auth in client mode"," signs users in as a public\n",[597,621,622],{"href":87},"OAuth application",", with PKCE (Proof Key for Code Exchange). It needs neither\n",[597,625,225],{"href":226}," nor frigear.nu. Any issuer that meets the checklist below works:",[628,629,630,639],"ol",{},[631,632,633,634,638],"li",{},"Point ",[635,636,637],"code",{},"authentication.client.issuer"," at it.",[631,640,641],{},"Register the app there as a public application.",[593,643,644,645,648,649,652,653,657],{},"The rest of the app is unchanged: the ",[597,646,647],{"href":363},"mobile shell",", ",[635,650,651],{},"$api"," and the sign-in page. Why an\napp in a browser or on a device has no secret is on\n",[597,654,656],{"href":655},"\u002Fguide\u002Fconcepts\u002Fidentity#public-and-confidential-applications","Public and confidential applications",".",[659,660,662],"h2",{"id":661},"the-issuer-checklist","The issuer checklist",[593,664,665],{},"Check each item at the issuer before you debug the app.",[667,668,671,698,719,739,752,776,793,811],"ul",{"className":669},[670],"contains-task-list",[631,672,675,680,681,680,684,687,688,690,691,693,694,697],{"className":673},[674],"task-list-item",[676,677],"input",{"disabled":678,"type":679},true,"checkbox"," ",[606,682,683],{},"Discovery.",[635,685,686],{},"GET \u003Cissuer>\u002F.well-known\u002Fopenid-configuration"," answers JSON whose ",[635,689,599],{}," equals the configured\n",[635,692,599],{}," exactly, including any trailing path. ",[635,695,696],{},"oauth-authorization-server"," (RFC 8414) works too.",[631,699,701,680,703,706,707,710,711,714,715,718],{"className":700},[674],[676,702],{"disabled":678,"type":679},[606,704,705],{},"PKCE with S256."," The issuer accepts ",[635,708,709],{},"code_challenge_method=S256",". When the discovery document has\n",[635,712,713],{},"code_challenge_methods_supported",", it must list ",[635,716,717],{},"S256",". The client refuses the issuer otherwise.",[631,720,722,680,724,727,728,731,732,735,736,657],{"className":721},[674],[676,723],{"disabled":678,"type":679},[606,725,726],{},"A public application with no secret."," The application has ",[635,729,730],{},"token_endpoint_auth_method: none",". The token and\nrevoke endpoints accept a ",[635,733,734],{},"client_id"," without a ",[635,737,738],{},"client_secret",[631,740,742,680,744,747,748,657],{"className":741},[674],[676,743],{"disabled":678,"type":679},[606,745,746],{},"Exact redirect URIs."," Register each URI exactly, with no wildcards. See ",[597,749,751],{"href":750},"#redirect-uris","Redirect URIs",[631,753,755,680,757,760,761,764,765,768,769,771,772,775],{"className":754},[674],[676,756],{"disabled":678,"type":679},[606,758,759],{},"CORS from the webview."," These endpoints answer cross-origin requests (CORS) from ",[635,762,763],{},"capacitor:\u002F\u002Flocalhost","\n(iOS), ",[635,766,767],{},"https:\u002F\u002Flocalhost"," (Android) and your dev origin, with the ",[635,770,199],{}," header allowed: the token,\nrevocation, userinfo and discovery endpoints, and your API. Use exact origins, never ",[635,773,774],{},"*",". The authorization endpoint\nis a navigation and needs no CORS.",[631,777,779,680,781,784,785,788,789,792],{"className":778},[674],[676,780],{"disabled":678,"type":679},[606,782,783],{},"Refresh tokens."," The application is allowed the ",[635,786,787],{},"refresh_token"," grant and the ",[635,790,791],{},"offline_access"," scope. The\nissuer rotates refresh tokens: a used one is replaced, and a replayed one revokes the family.",[631,794,796,680,798,804,805,807,808,657],{"className":795},[674],[676,797],{"disabled":678,"type":679},[606,799,800,803],{},[635,801,802],{},"iss"," in the authorization response."," The issuer returns ",[635,806,802],{}," in the redirect (RFC 9207) and advertises\n",[635,809,810],{},"authorization_response_iss_parameter_supported: true",[631,812,814,680,816,819,820,657],{"className":813},[674],[676,815],{"disabled":678,"type":679},[606,817,818],{},"An ID token."," The scopes include ",[635,821,822],{},"openid",[824,825,826,827,830],"warning",{},"Never put a client secret in the app. It would ship in the bundle. A production build that embeds\n",[635,828,829],{},"NUXT_PUBLIC_AUTHENTICATION_CLIENT_CLIENT_SECRET"," fails.",[593,832,833,835,836,839,840,843],{},[606,834,683],{}," Without a discovery document, set ",[635,837,838],{},"authentication.client.discovery: false",". The client then uses the\npaths of OAuth Server, or the ones you list in ",[635,841,842],{},"authentication.client.endpoints",". Listing endpoints alone does not\nturn discovery off.",[593,845,846,848,849,851,852,855],{},[606,847,783],{}," Add ",[635,850,791],{}," to ",[635,853,854],{},"authentication.client.scopes",". Otherwise the session ends when the access\ntoken expires.",[593,857,858,862,863,865,866,868],{},[606,859,860,657],{},[635,861,802],{}," The client rejects a response whose ",[635,864,802],{}," differs from the issuer. It also rejects a response without ",[635,867,802],{},"\nwhen the issuer advertises support for it.",[593,870,871,874,875,878,879,648,882,885,886,648,889,892,893,896,897,900,901,904,905,657],{},[606,872,873],{},"ID token."," The signed-in user (",[635,876,877],{},"useUserSession().user",") is mapped from the ID token's claims: ",[635,880,881],{},"sub",[635,883,884],{},"name",",\n",[635,887,888],{},"email",[635,890,891],{},"picture"," and ",[635,894,895],{},"role",". A sign-in whose response has no valid ID token fails. A userinfo endpoint is needed\nonly with ",[635,898,899],{},"client.userinfo: true",", or when your ",[635,902,903],{},"client.mapUser"," calls ",[635,906,907],{},"userinfo()",[909,910,751],"h3",{"id":911},"redirect-uris",[913,914,915,930],"table",{},[916,917,918],"thead",{},[919,920,921,924,927],"tr",{},[922,923,267],"th",{},[922,925,926],{},"Redirect URI",[922,928,929],{},"Example",[931,932,933,954],"tbody",{},[919,934,935,939,944],{},[936,937,938],"td",{},"Web",[936,940,941],{},[635,942,943],{},"\u003Corigin>\u002Fauth\u002Fcallback",[936,945,946,949,950,953],{},[635,947,948],{},"https:\u002F\u002Fapp.example.com\u002Fauth\u002Fcallback",", and ",[635,951,952],{},"http:\u002F\u002Flocalhost:3000\u002Fauth\u002Fcallback"," for development",[919,955,956,959,964],{},[936,957,958],{},"Native",[936,960,961],{},[635,962,963],{},"\u003CappId in lowercase>:\u002Fauth\u002Fcallback",[936,965,966],{},[635,967,968],{},"com.example.app:\u002Fauth\u002Fcallback",[593,970,971,972,975,976,979],{},"A native redirect URI is a custom scheme with one slash and no host. Some providers call it a private-use URI scheme,\nor a \"native\" application type. When you change ",[635,973,974],{},"callbackPath"," or ",[635,977,978],{},"native.redirectUri",", register the changed URIs.",[659,981,225],{"id":982},"oauth-server",[593,984,985,986,988,989,991,992,657],{},"With ",[597,987,225],{"href":226}," on the issuer, the app is a public application: PKCE only, and no\nsecret. Define it in config, so its ",[635,990,734],{}," is the same in every environment. The full walkthrough, including\nconfidential applications, is ",[597,993,136],{"href":137},[995,996,1002],"pre",{"className":997,"code":998,"filename":999,"language":1000,"meta":1001,"style":1001},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default defineNuxtConfig({\n  oauthServer: {\n    \u002F\u002F Your own API that the app calls with the access token.\n    cors: { paths: ['\u002Fapi\u002F'] },\n    clients: [{\n      id: 'bills',\n      name: 'Bills',\n      redirectUris: ['nu.frigear.bills:\u002Fauth\u002Fcallback'],\n      \u002F\u002F Accepted under `nuxt dev` only.\n      devRedirectUris: ['http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback'],\n      \u002F\u002F The webviews' origins; native redirect URIs have none to derive.\n      corsOrigins: ['capacitor:\u002F\u002Flocalhost', 'https:\u002F\u002Flocalhost'],\n    }],\n  },\n})\n","nuxt.config.ts (the issuer)","ts","",[635,1003,1004,1028,1041,1048,1082,1094,1112,1129,1151,1157,1178,1184,1213,1223,1229],{"__ignoreMap":1001},[1005,1006,1009,1013,1016,1020,1024],"span",{"class":1007,"line":1008},"line",1,[1005,1010,1012],{"class":1011},"s7zQu","export",[1005,1014,1015],{"class":1011}," default",[1005,1017,1019],{"class":1018},"s2Zo4"," defineNuxtConfig",[1005,1021,1023],{"class":1022},"sTEyZ","(",[1005,1025,1027],{"class":1026},"sMK4o","{\n",[1005,1029,1031,1035,1038],{"class":1007,"line":1030},2,[1005,1032,1034],{"class":1033},"swJcz","  oauthServer",[1005,1036,1037],{"class":1026},":",[1005,1039,1040],{"class":1026}," {\n",[1005,1042,1044],{"class":1007,"line":1043},3,[1005,1045,1047],{"class":1046},"sHwdD","    \u002F\u002F Your own API that the app calls with the access token.\n",[1005,1049,1051,1054,1056,1059,1062,1064,1067,1070,1074,1076,1079],{"class":1007,"line":1050},4,[1005,1052,1053],{"class":1033},"    cors",[1005,1055,1037],{"class":1026},[1005,1057,1058],{"class":1026}," {",[1005,1060,1061],{"class":1033}," paths",[1005,1063,1037],{"class":1026},[1005,1065,1066],{"class":1022}," [",[1005,1068,1069],{"class":1026},"'",[1005,1071,1073],{"class":1072},"sfazB","\u002Fapi\u002F",[1005,1075,1069],{"class":1026},[1005,1077,1078],{"class":1022},"] ",[1005,1080,1081],{"class":1026},"},\n",[1005,1083,1085,1088,1090,1092],{"class":1007,"line":1084},5,[1005,1086,1087],{"class":1033},"    clients",[1005,1089,1037],{"class":1026},[1005,1091,1066],{"class":1022},[1005,1093,1027],{"class":1026},[1005,1095,1097,1100,1102,1105,1108,1110],{"class":1007,"line":1096},6,[1005,1098,1099],{"class":1033},"      id",[1005,1101,1037],{"class":1026},[1005,1103,1104],{"class":1026}," '",[1005,1106,1107],{"class":1072},"bills",[1005,1109,1069],{"class":1026},[1005,1111,885],{"class":1026},[1005,1113,1115,1118,1120,1122,1125,1127],{"class":1007,"line":1114},7,[1005,1116,1117],{"class":1033},"      name",[1005,1119,1037],{"class":1026},[1005,1121,1104],{"class":1026},[1005,1123,1124],{"class":1072},"Bills",[1005,1126,1069],{"class":1026},[1005,1128,885],{"class":1026},[1005,1130,1132,1135,1137,1139,1141,1144,1146,1149],{"class":1007,"line":1131},8,[1005,1133,1134],{"class":1033},"      redirectUris",[1005,1136,1037],{"class":1026},[1005,1138,1066],{"class":1022},[1005,1140,1069],{"class":1026},[1005,1142,1143],{"class":1072},"nu.frigear.bills:\u002Fauth\u002Fcallback",[1005,1145,1069],{"class":1026},[1005,1147,1148],{"class":1022},"]",[1005,1150,885],{"class":1026},[1005,1152,1154],{"class":1007,"line":1153},9,[1005,1155,1156],{"class":1046},"      \u002F\u002F Accepted under `nuxt dev` only.\n",[1005,1158,1160,1163,1165,1167,1169,1172,1174,1176],{"class":1007,"line":1159},10,[1005,1161,1162],{"class":1033},"      devRedirectUris",[1005,1164,1037],{"class":1026},[1005,1166,1066],{"class":1022},[1005,1168,1069],{"class":1026},[1005,1170,1171],{"class":1072},"http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback",[1005,1173,1069],{"class":1026},[1005,1175,1148],{"class":1022},[1005,1177,885],{"class":1026},[1005,1179,1181],{"class":1007,"line":1180},11,[1005,1182,1183],{"class":1046},"      \u002F\u002F The webviews' origins; native redirect URIs have none to derive.\n",[1005,1185,1187,1190,1192,1194,1196,1198,1200,1203,1205,1207,1209,1211],{"class":1007,"line":1186},12,[1005,1188,1189],{"class":1033},"      corsOrigins",[1005,1191,1037],{"class":1026},[1005,1193,1066],{"class":1022},[1005,1195,1069],{"class":1026},[1005,1197,763],{"class":1072},[1005,1199,1069],{"class":1026},[1005,1201,1202],{"class":1026},",",[1005,1204,1104],{"class":1026},[1005,1206,767],{"class":1072},[1005,1208,1069],{"class":1026},[1005,1210,1148],{"class":1022},[1005,1212,885],{"class":1026},[1005,1214,1216,1219,1221],{"class":1007,"line":1215},13,[1005,1217,1218],{"class":1026},"    }",[1005,1220,1148],{"class":1022},[1005,1222,885],{"class":1026},[1005,1224,1226],{"class":1007,"line":1225},14,[1005,1227,1228],{"class":1026},"  },\n",[1005,1230,1232,1235],{"class":1007,"line":1231},15,[1005,1233,1234],{"class":1026},"}",[1005,1236,1237],{"class":1022},")\n",[913,1239,1240,1250],{},[916,1241,1242],{},[919,1243,1244,1247],{},[922,1245,1246],{},"Option",[922,1248,1249],{},"Purpose",[931,1251,1252,1269,1279],{},[919,1253,1254,1259],{},[936,1255,1256],{},[635,1257,1258],{},"oauthServer.clients",[936,1260,1261,1262,1264,1265,1268],{},"Public applications with a fixed ",[635,1263,734],{},". ",[635,1266,1267],{},"NUXT_OAUTH_SERVER_CLIENTS"," replaces the list with JSON.",[919,1270,1271,1276],{},[936,1272,1273],{},[635,1274,1275],{},"oauthServer.cors.origins",[936,1277,1278],{},"Exact origins that may call the token, revoke, userinfo and discovery endpoints. The origins of configured clients are added.",[919,1280,1281,1286],{},[936,1282,1283],{},[635,1284,1285],{},"oauthServer.cors.paths",[936,1287,1288],{},"Path prefixes of your own API that those origins may call with a bearer token.",[593,1290,1291],{},"The app side:",[995,1293,1296],{"className":997,"code":1294,"filename":1295,"language":1000,"meta":1001,"style":1001},"export default defineNuxtConfig({\n  modules: ['@nuxt\u002Fui', '@frigear-nu\u002Fauth'],\n  authentication: {\n    mode: 'client',\n    client: {\n      issuer: 'https:\u002F\u002Fid.example.com',\n      clientId: 'bills',\n      scopes: ['openid', 'profile', 'email', 'offline_access'],\n      native: { scheme: 'nu.frigear.bills' },\n    },\n  },\n})\n","nuxt.config.ts (the app)",[635,1297,1298,1310,1339,1348,1364,1373,1389,1404,1448,1472,1477,1481],{"__ignoreMap":1001},[1005,1299,1300,1302,1304,1306,1308],{"class":1007,"line":1008},[1005,1301,1012],{"class":1011},[1005,1303,1015],{"class":1011},[1005,1305,1019],{"class":1018},[1005,1307,1023],{"class":1022},[1005,1309,1027],{"class":1026},[1005,1311,1312,1315,1317,1319,1321,1324,1326,1328,1330,1333,1335,1337],{"class":1007,"line":1030},[1005,1313,1314],{"class":1033},"  modules",[1005,1316,1037],{"class":1026},[1005,1318,1066],{"class":1022},[1005,1320,1069],{"class":1026},[1005,1322,1323],{"class":1072},"@nuxt\u002Fui",[1005,1325,1069],{"class":1026},[1005,1327,1202],{"class":1026},[1005,1329,1104],{"class":1026},[1005,1331,1332],{"class":1072},"@frigear-nu\u002Fauth",[1005,1334,1069],{"class":1026},[1005,1336,1148],{"class":1022},[1005,1338,885],{"class":1026},[1005,1340,1341,1344,1346],{"class":1007,"line":1043},[1005,1342,1343],{"class":1033},"  authentication",[1005,1345,1037],{"class":1026},[1005,1347,1040],{"class":1026},[1005,1349,1350,1353,1355,1357,1360,1362],{"class":1007,"line":1050},[1005,1351,1352],{"class":1033},"    mode",[1005,1354,1037],{"class":1026},[1005,1356,1104],{"class":1026},[1005,1358,1359],{"class":1072},"client",[1005,1361,1069],{"class":1026},[1005,1363,885],{"class":1026},[1005,1365,1366,1369,1371],{"class":1007,"line":1084},[1005,1367,1368],{"class":1033},"    client",[1005,1370,1037],{"class":1026},[1005,1372,1040],{"class":1026},[1005,1374,1375,1378,1380,1382,1385,1387],{"class":1007,"line":1096},[1005,1376,1377],{"class":1033},"      issuer",[1005,1379,1037],{"class":1026},[1005,1381,1104],{"class":1026},[1005,1383,1384],{"class":1072},"https:\u002F\u002Fid.example.com",[1005,1386,1069],{"class":1026},[1005,1388,885],{"class":1026},[1005,1390,1391,1394,1396,1398,1400,1402],{"class":1007,"line":1114},[1005,1392,1393],{"class":1033},"      clientId",[1005,1395,1037],{"class":1026},[1005,1397,1104],{"class":1026},[1005,1399,1107],{"class":1072},[1005,1401,1069],{"class":1026},[1005,1403,885],{"class":1026},[1005,1405,1406,1409,1411,1413,1415,1417,1419,1421,1423,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446],{"class":1007,"line":1131},[1005,1407,1408],{"class":1033},"      scopes",[1005,1410,1037],{"class":1026},[1005,1412,1066],{"class":1022},[1005,1414,1069],{"class":1026},[1005,1416,822],{"class":1072},[1005,1418,1069],{"class":1026},[1005,1420,1202],{"class":1026},[1005,1422,1104],{"class":1026},[1005,1424,1425],{"class":1072},"profile",[1005,1427,1069],{"class":1026},[1005,1429,1202],{"class":1026},[1005,1431,1104],{"class":1026},[1005,1433,888],{"class":1072},[1005,1435,1069],{"class":1026},[1005,1437,1202],{"class":1026},[1005,1439,1104],{"class":1026},[1005,1441,791],{"class":1072},[1005,1443,1069],{"class":1026},[1005,1445,1148],{"class":1022},[1005,1447,885],{"class":1026},[1005,1449,1450,1453,1455,1457,1460,1462,1464,1467,1469],{"class":1007,"line":1153},[1005,1451,1452],{"class":1033},"      native",[1005,1454,1037],{"class":1026},[1005,1456,1058],{"class":1026},[1005,1458,1459],{"class":1033}," scheme",[1005,1461,1037],{"class":1026},[1005,1463,1104],{"class":1026},[1005,1465,1466],{"class":1072},"nu.frigear.bills",[1005,1468,1069],{"class":1026},[1005,1470,1471],{"class":1026}," },\n",[1005,1473,1474],{"class":1007,"line":1159},[1005,1475,1476],{"class":1026},"    },\n",[1005,1478,1479],{"class":1007,"line":1180},[1005,1480,1228],{"class":1026},[1005,1482,1483,1485],{"class":1007,"line":1186},[1005,1484,1234],{"class":1026},[1005,1486,1237],{"class":1022},[1488,1489,1490,892,1493,1496,1497,1499,1500,657],"note",{},[597,1491,1492],{"href":87},"Configured clients",[635,1494,1495],{},"cors.paths"," need OAuth Server 0.4.0 or later. On an older\nversion, register the application in the admin area as a public application. Add the native redirect URI\nto it, and list the webview origins in ",[635,1498,1275],{},". See ",[597,1501,225],{"href":226},[659,1503,1505],{"id":1504},"frigearnu","frigear.nu",[593,1507,1508,1509,657],{},"frigear.nu runs OAuth Server. Use ",[635,1510,1511],{},"issuer: 'https:\u002F\u002Ffrigear.nu'",[667,1513,1514,1524,1535],{},[631,1515,1516,1519,1520,1523],{},[606,1517,1518],{},"Registration."," Staff of frigear.nu register the application by hand in the admin area, with the client type\n",[606,1521,1522],{},"Public"," and the redirect URIs above. Ask one of them: there is no self-service registration.",[631,1525,1526,1529,1530,892,1532,1534],{},[606,1527,1528],{},"CORS."," It already allows ",[635,1531,763],{},[635,1533,767],{},", so the webview needs nothing more.",[631,1536,1537,1540,1541,1543],{},[606,1538,1539],{},"Your services."," Services behind the app accept the same access token through\n",[597,1542,241],{"href":242},". So one sign-in serves every service.",[659,1545,1547],{"id":1546},"keycloak","Keycloak",[593,1549,1550],{},"Setting names vary between versions. The intent is what matters.",[628,1552,1553,1568,1575,1585,1597,1604],{},[631,1554,1555,1556,1559,1560,1563,1564,1567],{},"Create a client in the realm: ",[606,1557,1558],{},"Client authentication"," off (public), ",[606,1561,1562],{},"Standard flow"," on and ",[606,1565,1566],{},"Direct access\ngrants"," off.",[631,1569,1570,1571,1574],{},"Set ",[606,1572,1573],{},"Valid redirect URIs"," to the web and native callback URIs.",[631,1576,1570,1577,851,1580,648,1582,1584],{},[606,1578,1579],{},"Web origins",[635,1581,763],{},[635,1583,767],{}," and your dev origin. This sets CORS.",[631,1586,1587,1588,1591,1592,851,1595,657],{},"Under ",[606,1589,1590],{},"Advanced",", set ",[606,1593,1594],{},"Proof Key for Code Exchange Code Challenge Method",[635,1596,717],{},[631,1598,1599,1600,1603],{},"Turn on ",[606,1601,1602],{},"Revoke Refresh Token"," in the realm's token settings. That turns on refresh token rotation.",[631,1605,1606,1607,1609],{},"Make ",[635,1608,791],{}," a client scope of the client.",[593,1611,1612,1613,657],{},"The issuer is ",[635,1614,1615],{},"https:\u002F\u002F\u003Chost>\u002Frealms\u002F\u003Crealm>",[659,1617,1619],{"id":1618},"auth0","Auth0",[628,1621,1622,1632,1638,1647,1654],{},[631,1623,1624,1625,1627,1628,1631],{},"Create an application of type ",[606,1626,958],{}," for apps in a Capacitor webview, or ",[606,1629,1630],{},"Single Page Application"," for a\nbrowser only.",[631,1633,1634,1635,657],{},"Add the callback URIs to ",[606,1636,1637],{},"Allowed Callback URLs",[631,1639,1640,1641,892,1644,657],{},"Add the origins to ",[606,1642,1643],{},"Allowed Origins (CORS)",[606,1645,1646],{},"Allowed Web Origins",[631,1648,1649,1650,1653],{},"Enable ",[606,1651,1652],{},"Refresh Token Rotation"," in the application's settings.",[631,1655,1656,1657,851,1660,1663],{},"When you call an API registered at Auth0, add ",[635,1658,1659],{},"audience",[635,1661,1662],{},"authentication.client.authorizationParams",", and allow\noffline access on that API.",[593,1665,1612,1666,1669],{},[635,1667,1668],{},"https:\u002F\u002F\u003Ctenant>.auth0.com\u002F",", or your custom domain.",[659,1671,1673],{"id":1672},"zitadel","Zitadel",[628,1675,1676,1688,1691,1697,1703],{},[631,1677,1624,1678,1680,1681,1684,1685,657],{},[606,1679,958],{},", with authentication method ",[606,1682,1683],{},"None"," and PKCE, or of type ",[606,1686,1687],{},"User Agent",[631,1689,1690],{},"Register the redirect URIs. Native apps accept custom-scheme redirects.",[631,1692,1570,1693,1696],{},[606,1694,1695],{},"Refresh Token"," under grant types.",[631,1698,1699,1700,657],{},"Add the origins under ",[606,1701,1702],{},"Additional origins",[631,1704,1705,1706,1709,1710,657],{},"To register ",[635,1707,1708],{},"http:\u002F\u002Flocalhost"," redirects, set the application to ",[606,1711,1712],{},"Development mode",[593,1714,1715],{},"The issuer is your instance URL.",[602,1717,1720,1723],{"icon":1718,"color":1719},"i-lucide-check","success",[606,1721,1722],{},"What you should see:"," the app's sign-in page sends you to the issuer, and you come back signed in. The session\nsurvives the access token's expiry, because the refresh token renews it.",[659,1725,1727],{"id":1726},"troubleshooting","Troubleshooting",[913,1729,1730,1740],{},[916,1731,1732],{},[919,1733,1734,1737],{},[922,1735,1736],{},"Symptom",[922,1738,1739],{},"Likely cause",[931,1741,1742,1760,1785,1798,1810,1824,1832,1847,1866,1877,1891],{},[919,1743,1744,1753],{},[936,1745,1746,1747,975,1750],{},"The browser shows ",[635,1748,1749],{},"invalid_redirect_uri",[635,1751,1752],{},"redirect_uri_mismatch",[936,1754,1755,1756,1759],{},"The URI is not registered exactly. Compare scheme, case, slashes and the path (",[635,1757,1758],{},"appId:\u002Fauth\u002Fcallback",", one slash).",[919,1761,1762,1768],{},[936,1763,1764,1767],{},[635,1765,1766],{},"config"," error: issuer mismatch",[936,1769,1770,1771,1773,1774,1776,1777,1780,1781,1784],{},"The discovery document's ",[635,1772,599],{}," differs from ",[635,1775,637],{},", for example ",[635,1778,1779],{},"http"," against ",[635,1782,1783],{},"https"," or another path.",[919,1786,1787,1795],{},[936,1788,1789,1791,1792],{},[635,1790,1766],{}," error: ",[635,1793,1794],{},"invalid_client",[936,1796,1797],{},"The application is confidential, or its id is wrong. Register a public application.",[919,1799,1800,1803],{},[936,1801,1802],{},"Sign-in works in the browser, then the token request fails in the app",[936,1804,1805,1806,975,1808,657],{},"CORS: the token endpoint does not allow ",[635,1807,763],{},[635,1809,767],{},[919,1811,1812,1815],{},[936,1813,1814],{},"Signed in, but the session ends when the access token expires",[936,1816,1817,1818,1820,1821,1823],{},"No ",[635,1819,791],{}," scope, no ",[635,1822,787],{}," grant, or the provider issues no refresh token to public applications.",[919,1825,1826,1829],{},[936,1827,1828],{},"Signed out after the first refresh",[936,1830,1831],{},"The provider rotated the refresh token and the response did not carry the new one, or two apps share one refresh token.",[919,1833,1834,1844],{},[936,1835,1836,1837,1839,1840,1843],{},"The ",[635,1838,651],{}," calls get ",[635,1841,1842],{},"401"," and the user is signed out",[936,1845,1846],{},"The API does not accept the token (audience, issuer) even after a refresh.",[919,1848,1849,1856],{},[936,1850,1836,1851,1839,1853,1855],{},[635,1852,651],{},[635,1854,1842],{},", and the user stays signed in",[936,1857,1858,1859,1862,1863,657],{},"No token was sent: the request's origin is not ",[635,1860,1861],{},"client.apiBase",", the issuer or one of ",[635,1864,1865],{},"client.apiOrigins",[919,1867,1868,1871],{},[936,1869,1870],{},"The sign-in fails right after the redirect back",[936,1872,1873,1874,1876],{},"No ID token in the token response. Add ",[635,1875,822],{}," to the scopes, and check that the issuer returns one to this application.",[919,1878,1879,1882],{},[936,1880,1881],{},"The app does not come back after signing in on a device",[936,1883,1884,1885,892,1888,657],{},"The native URL scheme is not registered in the iOS or Android project. Run ",[635,1886,1887],{},"frigear-mobile native",[635,1889,1890],{},"frigear-mobile doctor",[919,1892,1893,1896],{},[936,1894,1895],{},"A production build fails with a client secret error",[936,1897,1898,1900],{},[635,1899,829],{}," is set. Remove it, and use a public application.",[659,1902,1904],{"id":1903},"next-steps","Next steps",[667,1906,1907,1912,1917],{},[631,1908,1909,1911],{},[597,1910,136],{"href":137}," – register the app at an issuer that runs\nOAuth Server.",[631,1913,1914,1916],{},[597,1915,457],{"href":458}," – run an app that signs in this way.",[631,1918,1919,1921],{},[597,1920,241],{"href":242}," – accept the same access token in your other services.",[1923,1924,1925],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}",{"title":1001,"searchDepth":1030,"depth":1030,"links":1927},[1928,1931,1932,1933,1934,1935,1936,1937],{"id":661,"depth":1030,"text":662,"children":1929},[1930],{"id":911,"depth":1043,"text":751},{"id":982,"depth":1030,"text":225},{"id":1504,"depth":1030,"text":1505},{"id":1546,"depth":1030,"text":1547},{"id":1618,"depth":1030,"text":1619},{"id":1672,"depth":1030,"text":1673},{"id":1726,"depth":1030,"text":1727},{"id":1903,"depth":1030,"text":1904},"Sign a Frigear mobile or browser app in at any OpenID Connect issuer, such as OAuth Server, frigear.nu, Keycloak, Auth0 or Zitadel.","md",null,{},{"icon":134},{"title":131,"description":1938},"RTMjRvv-KCtwgNjTdfXcqKC50rMw4kmCt-pJ75wXPlQ",[1946,1948],{"title":121,"path":122,"stem":123,"description":1947,"icon":124,"children":-1},"The URL, handler, error, response and test conventions every module API follows, and the server toolkit that implements them.",{"title":136,"path":137,"stem":138,"description":1949,"icon":139,"children":-1},"Register an app at an issuer that runs OAuth Server, as a confidential application in the admin area or a public one in the admin area or nuxt.config.",1794503146578]