[{"data":1,"prerenderedAt":2988},["ShallowReactive",2],{"navigation_docs":3,"-examples-starters-mobile":587,"-examples-starters-mobile-surround":2983},[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":457,"body":589,"description":2976,"extension":2977,"links":2978,"meta":2979,"navigation":2980,"path":458,"seo":2981,"stem":459,"__hash__":2982},"docs\u002F4.examples\u002F1.starters\u002F2.mobile.md",{"type":590,"value":591,"toc":2955},"minimark",[592,599,633,651,669,677,682,690,726,730,766,878,891,895,899,904,989,1000,1014,1104,1107,1129,1135,1138,1187,1191,1203,1216,1255,1274,1289,1293,1305,1308,1353,1364,1380,1386,1390,1393,1405,1423,1426,1451,1468,1472,1484,1494,1502,1516,1520,1529,1606,1636,1647,1651,1656,1906,1911,1946,1959,1974,1978,2181,2192,2198,2248,2254,2257,2267,2327,2354,2363,2768,2776,2780,2789,2905,2912,2916,2951],[593,594,595],"p",{},[596,597,598],"strong",{},"What you'll have afterwards:",[600,601,602,616,627],"ul",{},[603,604,605,606,610,611,615],"li",{},"An iOS and Android app, also runnable in a browser on ",[607,608,609],"code",{},"http:\u002F\u002Flocalhost:3001",", that signs you in at an\n",[612,613,614],"a",{"href":87},"issuer"," you already have.",[603,617,618,619,622,623,626],{},"A ",[596,620,621],{},"Home"," tab that shows who signed in and calls ",[607,624,625],{},"\u002Fapi\u002Fme"," on your API with the access token.",[603,628,629,630,632],{},"An ",[596,631,195],{}," tab that shows your profile and signs you out.",[593,634,635,636,639,640,642,643,646,647,650],{},"Use this ",[612,637,638],{"href":87},"starter"," when your users and API already live on a server: frigear.nu, your own\n",[612,641,225],{"href":226},", or any issuer on the\n",[612,644,645],{"href":132},"checklist",". It is a pnpm workspace with one app in ",[607,648,649],{},"apps\u002Fmobile",". The app is a\nstatic Nuxt 4 single-page app (SPA) that Capacitor wraps for iOS and Android.",[600,652,653,658,664],{},[603,654,655,657],{},[612,656,362],{"href":363}," is the app shell.",[603,659,660,663],{},[612,661,662],{"href":184},"Auth in client mode"," signs the user in and keeps the tokens.",[603,665,666,668],{},[612,667,199],{"href":200}," provides the guards.",[670,671,672,673,676],"tip",{},"No server yet? The ",[612,674,675],{"href":463},"standalone mobile starter"," is this app plus a web app that is\nthe issuer and the API, wired together.",[678,679,681],"h2",{"id":680},"prerequisites","Prerequisites",[593,683,684,685,689],{},"The tools in ",[612,686,688],{"href":687},"\u002Fget-started\u002Finstallation#requirements","Requirements",", plus:",[691,692,693,706],"table",{},[694,695,696],"thead",{},[697,698,699,703],"tr",{},[700,701,702],"th",{},"Tool",[700,704,705],{},"For",[707,708,709,718],"tbody",{},[697,710,711,715],{},[712,713,714],"td",{},"Xcode",[712,716,717],{},"iOS: a recent Xcode with an iOS simulator. The native project uses Swift Package Manager, no CocoaPods.",[697,719,720,723],{},[712,721,722],{},"Android Studio",[712,724,725],{},"Android: a Java Development Kit (JDK) and the Android SDK.",[678,727,729],{"id":728},"_1-create-the-project","1. Create the project",[593,731,732,733,741,742,745,746,748,749,754,755,757,758,761,762,765],{},"Copy ",[612,734,738],{"href":735,"rel":736},"https:\u002F\u002Fgithub.com\u002FFrigear-nu\u002Ffrigear-nu\u002Ftree\u002Fmain\u002F.starters\u002Fmobile",[737],"nofollow",[607,739,740],{},".starters\u002Fmobile"," with\n",[607,743,744],{},"pnpm create frigear-app"," (",[612,747,562],{"href":563},") or with ",[612,750,753],{"href":751,"rel":752},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fgiget",[737],"giget",".\n",[607,756,744],{}," also runs ",[607,759,760],{},"pnpm install"," and ",[607,763,764],{},"pnpm prepare:env",":",[767,768,769,823],"code-group",{},[770,771,777],"pre",{"className":772,"code":773,"filename":774,"language":775,"meta":776,"style":776},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm create frigear-app my-app --platform mobile\ncd my-app\ngit init\n","pnpm create","bash","",[607,778,779,804,814],{"__ignoreMap":776},[780,781,784,788,792,795,798,801],"span",{"class":782,"line":783},"line",1,[780,785,787],{"class":786},"sBMFI","pnpm",[780,789,791],{"class":790},"sfazB"," create",[780,793,794],{"class":790}," frigear-app",[780,796,797],{"class":790}," my-app",[780,799,800],{"class":790}," --platform",[780,802,803],{"class":790}," mobile\n",[780,805,807,811],{"class":782,"line":806},2,[780,808,810],{"class":809},"s2Zo4","cd",[780,812,813],{"class":790}," my-app\n",[780,815,817,820],{"class":782,"line":816},3,[780,818,819],{"class":786},"git",[780,821,822],{"class":790}," init\n",[770,824,826],{"className":772,"code":825,"filename":753,"language":775,"meta":776,"style":776},"pnpx giget@latest gh:Frigear-nu\u002Ffrigear-nu\u002F.starters\u002Fmobile my-app\ncd my-app\ngit init\ncorepack enable\npnpm install\npnpm prepare:env\n",[607,827,828,841,847,853,862,870],{"__ignoreMap":776},[780,829,830,833,836,839],{"class":782,"line":783},[780,831,832],{"class":786},"pnpx",[780,834,835],{"class":790}," giget@latest",[780,837,838],{"class":790}," gh:Frigear-nu\u002Ffrigear-nu\u002F.starters\u002Fmobile",[780,840,813],{"class":790},[780,842,843,845],{"class":782,"line":806},[780,844,810],{"class":809},[780,846,813],{"class":790},[780,848,849,851],{"class":782,"line":816},[780,850,819],{"class":786},[780,852,822],{"class":790},[780,854,856,859],{"class":782,"line":855},4,[780,857,858],{"class":786},"corepack",[780,860,861],{"class":790}," enable\n",[780,863,865,867],{"class":782,"line":864},5,[780,866,787],{"class":786},[780,868,869],{"class":790}," install\n",[780,871,873,875],{"class":782,"line":872},6,[780,874,787],{"class":786},[780,876,877],{"class":790}," prepare:env\n",[593,879,880,882,883,886,887,890],{},[607,881,764],{}," copies ",[607,884,885],{},"apps\u002Fmobile\u002F.env.example"," to ",[607,888,889],{},"apps\u002Fmobile\u002F.env",". It never overwrites a value.",[678,892,894],{"id":893},"_2-choose-the-issuer","2. Choose the issuer",[896,897,898],"warning",{},"The app does not run until the issuer knows it. Register it there before the first run.",[593,900,901,902,765],{},"Set the issuer and your API in ",[607,903,889],{},[691,905,906,916],{},[694,907,908],{},[697,909,910,913],{},[700,911,912],{},"Variable",[700,914,915],{},"What it is",[707,917,918,936,953,971],{},[697,919,920,925],{},[712,921,922],{},[607,923,924],{},"NUXT_PUBLIC_AUTHENTICATION_CLIENT_ISSUER",[712,926,927,928,931,932,935],{},"The issuer URL, for example ",[607,929,930],{},"https:\u002F\u002Ffrigear.nu",". The example file sets ",[607,933,934],{},"http:\u002F\u002Flocalhost:3000",".",[697,937,938,943],{},[712,939,940],{},[607,941,942],{},"NUXT_PUBLIC_AUTHENTICATION_CLIENT_API_BASE",[712,944,945,946,761,949,952],{},"The API that ",[607,947,948],{},"useApi()",[607,950,951],{},"$api"," call with the access token. Without it: the issuer.",[697,954,955,960],{},[712,956,957],{},[607,958,959],{},"NUXT_PUBLIC_AUTHENTICATION_CLIENT_CLIENT_ID",[712,961,962,963,966,967,970],{},"Optional. Overrides ",[607,964,965],{},"clientId"," of ",[607,968,969],{},"identity.ts"," per environment.",[697,972,973,978],{},[712,974,975],{},[607,976,977],{},"NUXT_PUBLIC_AUTHENTICATION_CLIENT_SCOPE",[712,979,962,980,966,983,985,986,935],{},[607,981,982],{},"scopes",[607,984,969],{},", space separated. Must include ",[607,987,988],{},"openid",[990,991,992,995,996,999],"note",{},[607,993,994],{},"NUXT_PUBLIC_*"," values are baked into the build. Run ",[607,997,998],{},"pnpm mobile:sync"," after you change them.",[593,1001,1002,1003,1006,1007,1010,1011,765],{},"Then register a ",[596,1004,1005],{},"public"," ",[612,1008,1009],{"href":87},"OAuth application"," at the issuer: one that uses PKCE (Proof Key\nfor Code Exchange) and has no secret. Use the values from ",[607,1012,1013],{},"apps\u002Fmobile\u002Fidentity.ts",[691,1015,1016,1026],{},[694,1017,1018],{},[697,1019,1020,1023],{},[700,1021,1022],{},"What",[700,1024,1025],{},"Value in the starter",[707,1027,1028,1041,1058,1072,1090],{},[697,1029,1030,1033],{},[712,1031,1032],{},"Client id",[712,1034,1035,745,1038,1040],{},[607,1036,1037],{},"mobile",[607,1039,965],{},")",[697,1042,1043,1046],{},[712,1044,1045],{},"Redirect URI of the app",[712,1047,1048,745,1051,1054,1055,1040],{},[607,1049,1050],{},"com.example.mobile:\u002Fauth\u002Fcallback",[607,1052,1053],{},"appId"," in lowercase, then ",[607,1056,1057],{},"callbackPath",[697,1059,1060,1066],{},[712,1061,1062,1063],{},"Redirect URI for ",[607,1064,1065],{},"pnpm dev",[712,1067,1068,1071],{},[607,1069,1070],{},"http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback"," (development only)",[697,1073,1074,1077],{},[712,1075,1076],{},"Cross-origin resource sharing (CORS) origins",[712,1078,1079,1082,1083,1086,1087,1089],{},[607,1080,1081],{},"capacitor:\u002F\u002Flocalhost"," (iOS), ",[607,1084,1085],{},"https:\u002F\u002Flocalhost"," (Android), ",[607,1088,609],{}," (development)",[697,1091,1092,1095],{},[712,1093,1094],{},"Scopes",[712,1096,1097,1100,1101,1040],{},[607,1098,1099],{},"openid profile email"," (see below for ",[607,1102,1103],{},"offline_access",[593,1105,1106],{},"The app needs a refresh token to stay signed in:",[600,1108,1109,1115],{},[603,1110,1111,1112,1114],{},"OAuth Server always issues one. It does not support the ",[607,1113,1103],{}," scope and drops it.",[603,1116,1117,1118,1120,1121,1123,1124,1126,1127,935],{},"Most other issuers only issue one for ",[607,1119,1103],{},". For Keycloak, Auth0, Zitadel and the like, add it to\n",[607,1122,982],{}," in ",[607,1125,969],{},", or to ",[607,1128,977],{},[896,1130,1131,1132,1134],{},"Keep ",[607,1133,988],{}," in the scopes. The signed-in user comes from the ID token.",[593,1136,1137],{},"How that looks for each kind of issuer:",[1139,1140,1141,1163,1181],"tabs",{},[1142,1143,1145,1146,1149,1150,1153,1154,1158,1159,1162],"tabs-item",{"label":1144},"Your own OAuth Server","Add one entry to ",[607,1147,1148],{},"oauthServer.clients"," with these redirect URIs, and ",[607,1151,1152],{},"cors.paths: ['\u002Fapi\u002F']"," for your API.\n",[612,1155,1157],{"href":1156},"#add-appsmobile-to-an-existing-workspace","Add apps\u002Fmobile to an existing workspace"," shows the config. The\n",[612,1160,1161],{"href":463},"standalone starter"," has it wired up.",[1142,1164,1166,1172],{"label":1165},"frigear.nu",[593,1167,1168,1169,1171],{},"Staff of frigear.nu register the OAuth application, so ask one of them. Its CORS allows the webview origins, so the app\nworks on a device. A browser on ",[607,1170,609],{}," is not an allowed origin there.",[593,1173,1174,1175,1177,1178,1180],{},"frigear.nu has no ",[607,1176,625],{},". Point ",[607,1179,942],{}," at your own API, or the Home tab shows\nthe failed call.",[1142,1182,1184,1186],{"label":1183},"Keycloak, Auth0, Zitadel",[612,1185,131],{"href":132}," has the checklist (discovery, PKCE S256, exact\nredirect URIs, CORS, refresh tokens) and a recipe per issuer.",[678,1188,1190],{"id":1189},"_3-run-it-in-a-browser","3. Run it in a browser",[770,1192,1194],{"className":772,"code":1193,"language":775,"meta":776,"style":776},"pnpm dev\n",[607,1195,1196],{"__ignoreMap":776},[780,1197,1198,1200],{"class":782,"line":783},[780,1199,787],{"class":786},[780,1201,1202],{"class":790}," dev\n",[593,1204,1205,1206,1208,1209,1212,1213,765],{},"Open ",[607,1207,609],{},". Every page needs a signed-in user (the ",[607,1210,1211],{},"'\u002F**'"," route rule), so you land on ",[607,1214,1215],{},"\u002Flogin",[1217,1218,1219,1232,1240],"ol",{},[603,1220,1221,1224,1225,761,1228,1231],{},[596,1222,1223],{},"Sign in"," sends you to the issuer, where you sign in and approve the app. Passkeys work when the issuer offers\nthem, as the ",[607,1226,1227],{},"workspace",[607,1229,1230],{},"mobile-standalone"," starters do.",[603,1233,1234,1235,1237,1238,935],{},"The issuer redirects to ",[607,1236,1070],{},". The app exchanges the code for tokens and opens\n",[596,1239,621],{},[603,1241,1242,1244,1245,1248,1249,1252,1253,935],{},[596,1243,195],{}," shows your name and email. ",[596,1246,1247],{},"Sign out"," revokes the refresh token at the issuer and returns to ",[607,1250,1251],{},"\u002F",". The\nroute rule sends you on to ",[607,1254,1215],{},[1256,1257,1260,1006,1263,1265,1266,1269,1270,1273],"callout",{"color":1258,"icon":1259},"success","i-lucide-check",[596,1261,1262],{},"What you should see:",[596,1264,621],{}," shows your sign-in (",[607,1267,1268],{},"useUserSession().user",") and the result of ",[607,1271,1272],{},"GET \u002Fapi\u002Fme"," on your\nAPI. A reload keeps you signed in.",[593,1275,1276,1277,1280,1281,1288],{},"Tokens live in ",[607,1278,1279],{},"localStorage"," in a browser. On a device they live in the Keychain or Keystore, through\n",[612,1282,1285],{"href":1283,"rel":1284},"https:\u002F\u002Fgithub.com\u002Faparajita\u002Fcapacitor-secure-storage",[737],[607,1286,1287],{},"@aparajita\u002Fcapacitor-secure-storage",". The access token is\nrefreshed when it expires.",[678,1290,1292],{"id":1291},"_4-run-it-on-the-ios-simulator","4. Run it on the iOS simulator",[770,1294,1296],{"className":772,"code":1295,"language":775,"meta":776,"style":776},"pnpm mobile:ios\n",[607,1297,1298],{"__ignoreMap":776},[780,1299,1300,1302],{"class":782,"line":783},[780,1301,787],{"class":786},[780,1303,1304],{"class":790}," mobile:ios\n",[593,1306,1307],{},"The script does four things:",[1217,1309,1310,1331,1338,1347],{},[603,1311,1312,1313,1316,1317,1320,1321,1324,1325,761,1328,935],{},"The first time, it creates the native projects (",[607,1314,1315],{},"frigear-mobile native --if-missing","). That runs ",[607,1318,1319],{},"cap add ios"," and\n",[607,1322,1323],{},"cap add android",", and adds the URL scheme of the redirect URI to ",[607,1326,1327],{},"Info.plist",[607,1329,1330],{},"AndroidManifest.xml",[603,1332,1333,1334,1337],{},"It builds the SPA (",[607,1335,1336],{},"nuxt generate",").",[603,1339,1340,1341,745,1344,1337],{},"It copies the SPA into ",[607,1342,1343],{},"ios\u002F",[607,1345,1346],{},"cap sync ios",[603,1348,1349,1350,1337],{},"It asks which simulator to run (",[607,1351,1352],{},"cap run ios",[593,1354,1355,1357,1358,1360,1361,1363],{},[596,1356,1223],{}," opens the in-app browser at the issuer. After you approve, the issuer redirects to\n",[607,1359,1050],{},". iOS hands that URL to the app, and you are signed in. The simulator shares your\nMac's network, so an issuer on ",[607,1362,934],{}," works as is.",[896,1365,1366,1367,1369,1370,1373,1374,1376,1377,935],{},"Run ",[607,1368,998],{}," after every change to the web code or ",[607,1371,1372],{},".env",", and after every ",[607,1375,760],{},". The Swift Package\nManager paths point into ",[607,1378,1379],{},"node_modules",[593,1381,1382,1385],{},[607,1383,1384],{},"pnpm mobile:doctor"," lists missing plugins and URL schemes.",[678,1387,1389],{"id":1388},"_5-run-it-on-an-iphone","5. Run it on an iPhone",[593,1391,1392],{},"With an issuer on the internet, open the project in Xcode and run it on the phone:",[770,1394,1396],{"className":772,"code":1395,"language":775,"meta":776,"style":776},"pnpm mobile:ios:device\n",[607,1397,1398],{"__ignoreMap":776},[780,1399,1400,1402],{"class":782,"line":783},[780,1401,787],{"class":786},[780,1403,1404],{"class":790}," mobile:ios:device\n",[593,1406,1407,1408,1411,1412,1415,1416,1419,1420,935],{},"The script builds with ",[607,1409,1410],{},"MOBILE_LAN=1",". That rewrites ",[607,1413,1414],{},"localhost"," in the issuer and API base to this Mac's local network\n(LAN) address. ",[607,1417,1418],{},"MOBILE_LAN=192.168.1.20"," picks the address. So a phone on the same Wi-Fi reaches an issuer running on\nyour Mac. Start that server on every interface; for a Nuxt server, run ",[607,1421,1422],{},"nuxt dev --host 0.0.0.0",[593,1424,1425],{},"Then, in Xcode:",[1217,1427,1428,1438,1448],{},[603,1429,1430,1431,1433,1434,1437],{},"Select the ",[596,1432,267],{}," target, then ",[596,1435,1436],{},"Signing & Capabilities",", and pick your team. A free Apple ID works, but the\ninstall then expires after 7 days. Do not commit the team id.",[603,1439,1440,1441,1444,1445,935],{},"Connect the phone and turn on ",[596,1442,1443],{},"Developer Mode"," on it (Settings, Privacy & Security). Choose it as the run\ndestination and press ",[596,1446,1447],{},"Run",[603,1449,1450],{},"The first time, trust the developer profile on the phone (Settings, General, VPN & Device Management). Allow local\nnetwork access when asked.",[990,1452,1453,1456,1457,1460,1461,1464,1465,935],{},[607,1454,1455],{},"MOBILE_LAN"," never applies to ",[607,1458,1459],{},"nuxt dev",". The build fails when ",[607,1462,1463],{},"MOBILE_RELEASE=1"," is also set, so a LAN address never\nships. For live reload on a device, see ",[612,1466,1467],{"href":363},"Device development",[678,1469,1471],{"id":1470},"_6-run-it-on-android","6. Run it on Android",[770,1473,1475],{"className":772,"code":1474,"language":775,"meta":776,"style":776},"pnpm mobile:android\n",[607,1476,1477],{"__ignoreMap":776},[780,1478,1479,1481],{"class":782,"line":783},[780,1480,787],{"class":786},[780,1482,1483],{"class":790}," mobile:android\n",[593,1485,1486,1487,1490,1491,935],{},"The steps are the same as for iOS: native project, build, ",[607,1488,1489],{},"cap sync android",", ",[607,1492,1493],{},"cap run android",[600,1495,1496,1499],{},[603,1497,1498],{},"Android shows the web tab bar and header. The native chrome is iOS only by default.",[603,1500,1501],{},"The back button closes an overlay, then goes back, then returns to the first tab.",[896,1503,1504,1505,1507,1508,1511,1512,1515],{},"The Android WebView runs at ",[607,1506,1085],{},", so a plain ",[607,1509,1510],{},"http"," development server is mixed content. Use an ",[607,1513,1514],{},"https","\nissuer, or live reload, when you test on Android.",[678,1517,1519],{"id":1518},"how-the-guards-work","How the guards work",[593,1521,1522,1523,1525,1526,765],{},"Mobile protects nothing by default. The app picks its guards with ",[612,1524,199],{"href":200},", in\n",[607,1527,1528],{},"apps\u002Fmobile\u002Fnuxt.config.ts",[770,1530,1534],{"className":1531,"code":1532,"filename":1528,"language":1533,"meta":776,"style":776},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","routeRules: {\n  '\u002F**': { authenticated: true },\n},\nauthorization: { redirectTo: '\u002Flogin' },\n","ts",[607,1535,1536,1547,1577,1582],{"__ignoreMap":776},[780,1537,1538,1541,1544],{"class":782,"line":783},[780,1539,1540],{"class":786},"routeRules",[780,1542,765],{"class":1543},"sMK4o",[780,1545,1546],{"class":1543}," {\n",[780,1548,1549,1552,1555,1558,1562,1565,1568,1570,1574],{"class":782,"line":806},[780,1550,1551],{"class":1543},"  '",[780,1553,1554],{"class":790},"\u002F**",[780,1556,1557],{"class":1543},"'",[780,1559,1561],{"class":1560},"swJcz",": ",[780,1563,1564],{"class":1543},"{",[780,1566,1567],{"class":1560}," authenticated",[780,1569,765],{"class":1543},[780,1571,1573],{"class":1572},"sfNiH"," true",[780,1575,1576],{"class":1543}," },\n",[780,1578,1579],{"class":782,"line":816},[780,1580,1581],{"class":1543},"},\n",[780,1583,1584,1587,1589,1592,1595,1597,1600,1602,1604],{"class":782,"line":855},[780,1585,1586],{"class":786},"authorization",[780,1588,765],{"class":1543},[780,1590,1591],{"class":1543}," {",[780,1593,1594],{"class":786}," redirectTo",[780,1596,765],{"class":1543},[780,1598,1599],{"class":1543}," '",[780,1601,1215],{"class":790},[780,1603,1557],{"class":1543},[780,1605,1576],{"class":1543},[600,1607,1608,1613,1620],{},[603,1609,1610,1612],{},[607,1611,1215],{}," and the callback stay public without a rule.",[603,1614,1615,1616,1619],{},"Both tabs have ",[607,1617,1618],{},"requiresAuth: true",", so they are hidden while signed out. The sign-in page shows no tab bar.",[603,1621,1622,1623,1625,1626,1629,1630,1632,1633,935],{},"For a public home screen, remove the ",[607,1624,1211],{}," rule and remove ",[607,1627,1628],{},"requiresAuth"," from the ",[596,1631,621],{}," tab. Guard single pages\ninstead, for example ",[607,1634,1635],{},"'\u002Faccount': { authenticated: true }",[1637,1638,1639,1640,1643,1644,935],"caution",{},"These rules only decide what the app shows. The tokens and the user live on the device, where their owner can change\nthem. Keep privileged data out of the bundle, and let your API decide with ",[607,1641,1642],{},"requireAuth"," or ",[607,1645,1646],{},"requireUser",[678,1648,1650],{"id":1649},"rename-the-app","Rename the app",[593,1652,1653,1654,765],{},"Everything that names the app is in ",[607,1655,1013],{},[770,1657,1659],{"className":1531,"code":1658,"filename":1013,"language":1533,"meta":776,"style":776},"export const appId = 'com.example.mobile' \u002F\u002F bundle id, application id, and (lowercase) the redirect URI scheme\nexport const appName = 'Example' \u002F\u002F home screen name and fallback page title\nexport const clientId = 'mobile' \u002F\u002F the OAuth client_id\nexport const callbackPath = '\u002Fauth\u002Fcallback'\nexport const devPort = 3001 \u002F\u002F keep the `dev` script of apps\u002Fmobile\u002Fpackage.json in step\nexport const scopes = ['openid', 'profile', 'email'] \u002F\u002F add 'offline_access' for issuers that need it\nexport const nativeRedirectUri = `${appId.toLowerCase().replaceAll('_', '-')}:${callbackPath}`\nexport const devRedirectUri = `http:\u002F\u002Flocalhost:${devPort}${callbackPath}`\n",[607,1660,1661,1689,1710,1730,1749,1767,1812,1876],{"__ignoreMap":776},[780,1662,1663,1667,1671,1675,1678,1680,1683,1685],{"class":782,"line":783},[780,1664,1666],{"class":1665},"s7zQu","export",[780,1668,1670],{"class":1669},"spNyl"," const",[780,1672,1674],{"class":1673},"sTEyZ"," appId ",[780,1676,1677],{"class":1543},"=",[780,1679,1599],{"class":1543},[780,1681,1682],{"class":790},"com.example.mobile",[780,1684,1557],{"class":1543},[780,1686,1688],{"class":1687},"sHwdD"," \u002F\u002F bundle id, application id, and (lowercase) the redirect URI scheme\n",[780,1690,1691,1693,1695,1698,1700,1702,1705,1707],{"class":782,"line":806},[780,1692,1666],{"class":1665},[780,1694,1670],{"class":1669},[780,1696,1697],{"class":1673}," appName ",[780,1699,1677],{"class":1543},[780,1701,1599],{"class":1543},[780,1703,1704],{"class":790},"Example",[780,1706,1557],{"class":1543},[780,1708,1709],{"class":1687}," \u002F\u002F home screen name and fallback page title\n",[780,1711,1712,1714,1716,1719,1721,1723,1725,1727],{"class":782,"line":816},[780,1713,1666],{"class":1665},[780,1715,1670],{"class":1669},[780,1717,1718],{"class":1673}," clientId ",[780,1720,1677],{"class":1543},[780,1722,1599],{"class":1543},[780,1724,1037],{"class":790},[780,1726,1557],{"class":1543},[780,1728,1729],{"class":1687}," \u002F\u002F the OAuth client_id\n",[780,1731,1732,1734,1736,1739,1741,1743,1746],{"class":782,"line":855},[780,1733,1666],{"class":1665},[780,1735,1670],{"class":1669},[780,1737,1738],{"class":1673}," callbackPath ",[780,1740,1677],{"class":1543},[780,1742,1599],{"class":1543},[780,1744,1745],{"class":790},"\u002Fauth\u002Fcallback",[780,1747,1748],{"class":1543},"'\n",[780,1750,1751,1753,1755,1758,1760,1764],{"class":782,"line":864},[780,1752,1666],{"class":1665},[780,1754,1670],{"class":1669},[780,1756,1757],{"class":1673}," devPort ",[780,1759,1677],{"class":1543},[780,1761,1763],{"class":1762},"sbssI"," 3001",[780,1765,1766],{"class":1687}," \u002F\u002F keep the `dev` script of apps\u002Fmobile\u002Fpackage.json in step\n",[780,1768,1769,1771,1773,1776,1778,1781,1783,1785,1787,1790,1792,1795,1797,1799,1801,1804,1806,1809],{"class":782,"line":872},[780,1770,1666],{"class":1665},[780,1772,1670],{"class":1669},[780,1774,1775],{"class":1673}," scopes ",[780,1777,1677],{"class":1543},[780,1779,1780],{"class":1673}," [",[780,1782,1557],{"class":1543},[780,1784,988],{"class":790},[780,1786,1557],{"class":1543},[780,1788,1789],{"class":1543},",",[780,1791,1599],{"class":1543},[780,1793,1794],{"class":790},"profile",[780,1796,1557],{"class":1543},[780,1798,1789],{"class":1543},[780,1800,1599],{"class":1543},[780,1802,1803],{"class":790},"email",[780,1805,1557],{"class":1543},[780,1807,1808],{"class":1673},"] ",[780,1810,1811],{"class":1687},"\u002F\u002F add 'offline_access' for issuers that need it\n",[780,1813,1815,1817,1819,1822,1824,1827,1829,1831,1834,1837,1839,1842,1845,1847,1850,1852,1854,1856,1859,1861,1863,1866,1868,1871,1873],{"class":782,"line":1814},7,[780,1816,1666],{"class":1665},[780,1818,1670],{"class":1669},[780,1820,1821],{"class":1673}," nativeRedirectUri ",[780,1823,1677],{"class":1543},[780,1825,1826],{"class":1543}," `${",[780,1828,1053],{"class":1673},[780,1830,935],{"class":1543},[780,1832,1833],{"class":809},"toLowerCase",[780,1835,1836],{"class":1673},"()",[780,1838,935],{"class":1543},[780,1840,1841],{"class":809},"replaceAll",[780,1843,1844],{"class":1673},"(",[780,1846,1557],{"class":1543},[780,1848,1849],{"class":790},"_",[780,1851,1557],{"class":1543},[780,1853,1789],{"class":1543},[780,1855,1599],{"class":1543},[780,1857,1858],{"class":790},"-",[780,1860,1557],{"class":1543},[780,1862,1040],{"class":1673},[780,1864,1865],{"class":1543},"}",[780,1867,765],{"class":790},[780,1869,1870],{"class":1543},"${",[780,1872,1057],{"class":1673},[780,1874,1875],{"class":1543},"}`\n",[780,1877,1879,1881,1883,1886,1888,1891,1894,1896,1899,1902,1904],{"class":782,"line":1878},8,[780,1880,1666],{"class":1665},[780,1882,1670],{"class":1669},[780,1884,1885],{"class":1673}," devRedirectUri ",[780,1887,1677],{"class":1543},[780,1889,1890],{"class":1543}," `",[780,1892,1893],{"class":790},"http:\u002F\u002Flocalhost:",[780,1895,1870],{"class":1543},[780,1897,1898],{"class":1673},"devPort",[780,1900,1901],{"class":1543},"}${",[780,1903,1057],{"class":1673},[780,1905,1875],{"class":1543},[593,1907,1908,1909,765],{},"After changing ",[607,1910,1053],{},[1217,1912,1913,1924],{},[603,1914,1915,1916,1919,1920,1923],{},"Register the new redirect URI (",[607,1917,1918],{},"\u003Cnew app id>:\u002Fauth\u002Fcallback",") at the issuer. An OAuth Server in the same workspace\npicks it up from ",[607,1921,1922],{},"@workspace\u002Fmobile\u002Fidentity"," instead.",[603,1925,1926,1927,761,1930,1933,1934,1937,1938,1941,1942,1945],{},"Delete ",[607,1928,1929],{},"apps\u002Fmobile\u002Fios",[607,1931,1932],{},"apps\u002Fmobile\u002Fandroid",". Then run ",[607,1935,1936],{},"pnpm mobile:native",", or any ",[607,1939,1940],{},"mobile:ios"," \u002F\n",[607,1943,1944],{},"mobile:android"," script, to create them with the new id and URL scheme.",[896,1947,1948,1949,761,1951,1953,1954,1956,1957,935],{},"Use lowercase letters, digits, ",[607,1950,935],{},[607,1952,1858],{}," in the app id. The redirect URI scheme is the app id in lowercase, with ",[607,1955,1849],{},"\nturned into ",[607,1958,1858],{},[593,1960,1961,1962,1123,1965,1967,1968,745,1971,1337],{},"The tabs are ",[607,1963,1964],{},"mobile.tabs",[607,1966,1528],{},". The sign-in page's title and icon are in\n",[607,1969,1970],{},"apps\u002Fmobile\u002Fapp\u002Fapp.config.ts",[607,1972,1973],{},"authentication.login",[678,1975,1977],{"id":1976},"what-is-in-the-starter","What is in the starter",[691,1979,1980,1990],{},[694,1981,1982],{},[697,1983,1984,1987],{},[700,1985,1986],{},"Path",[700,1988,1989],{},"What it does",[707,1991,1992,2029,2045,2055,2068,2078,2089,2116,2129,2141,2152,2171],{},[697,1993,1994,1999],{},[712,1995,1996],{},[607,1997,1998],{},"package.json",[712,2000,2001,2002,1490,2005,1490,2008,1490,2011,1490,2014,1490,2017,1490,2020,1490,2023,761,2026,935],{},"Root scripts: ",[607,2003,2004],{},"dev",[607,2006,2007],{},"build",[607,2009,2010],{},"lint",[607,2012,2013],{},"lint:fix",[607,2015,2016],{},"typecheck",[607,2018,2019],{},"test",[607,2021,2022],{},"prepare:env",[607,2024,2025],{},"clean",[607,2027,2028],{},"mobile:*",[697,2030,2031,2036],{},[712,2032,2033],{},[607,2034,2035],{},"pnpm-workspace.yaml",[712,2037,2038,2039,761,2042,935],{},"Lists ",[607,2040,2041],{},"apps\u002F*",[607,2043,2044],{},"packages\u002F*",[697,2046,2047,2052],{},[712,2048,2049],{},[607,2050,2051],{},"AGENTS.md",[712,2053,2054],{},"Conventions for you and your coding agents.",[697,2056,2057,2062],{},[712,2058,2059],{},[607,2060,2061],{},"scripts\u002Fprepare-env.mjs",[712,2063,2064,2065,2067],{},"Creates the apps' ",[607,2066,1372],{}," files.",[697,2069,2070,2075],{},[712,2071,2072],{},[607,2073,2074],{},"packages\u002Feslint-config\u002F",[712,2076,2077],{},"Shared lint config for packages you add.",[697,2079,2080,2084],{},[712,2081,2082],{},[607,2083,1013],{},[712,2085,2086,2087,935],{},"App id, name, client id, callback path, dev port and scopes. Exported as ",[607,2088,1922],{},[697,2090,2091,2095],{},[712,2092,2093],{},[607,2094,1528],{},[712,2096,2097,1490,2100,761,2103,2106,2107,2109,2110,2113,2114,935],{},[607,2098,2099],{},"@nuxt\u002Fui",[607,2101,2102],{},"@frigear-nu\u002Fmobile",[607,2104,2105],{},"@frigear-nu\u002Fauthorization",", the tabs, the ",[607,2108,1211],{}," route rule, and ",[607,2111,2112],{},"authentication"," in client mode from ",[607,2115,969],{},[697,2117,2118,2122],{},[712,2119,2120],{},[607,2121,885],{},[712,2123,2124,761,2126,935],{},[607,2125,924],{},[607,2127,2128],{},"_API_BASE",[697,2130,2131,2136],{},[712,2132,2133],{},[607,2134,2135],{},"apps\u002Fmobile\u002Fapp\u002Fapp.vue",[712,2137,2138,935],{},[607,2139,2140],{},"\u003CUApp :toaster=\"useMobileToaster()\">",[697,2142,2143,2147],{},[712,2144,2145],{},[607,2146,1970],{},[712,2148,2149,2150,1337],{},"Sign-in page branding (",[607,2151,1973],{},[697,2153,2154,2159],{},[712,2155,2156],{},[607,2157,2158],{},"apps\u002Fmobile\u002Fapp\u002Fpages\u002Findex.vue",[712,2160,2161,2162,761,2164,2167,2168,935],{},"Home: ",[607,2163,1268],{},[607,2165,2166],{},"useApi('\u002Fapi\u002Fme')",", refreshed with ",[607,2169,2170],{},"onTabEnter",[697,2172,2173,2178],{},[712,2174,2175],{},[607,2176,2177],{},"apps\u002Fmobile\u002Fapp\u002Fpages\u002Faccount.vue",[712,2179,2180],{},"Account: profile and sign-out.",[593,2182,2183,761,2185,2188,2189,2191],{},[607,2184,1343],{},[607,2186,2187],{},"android\u002F"," are not in the starter: ",[607,2190,1936],{}," creates them. Commit them once you change native\nsettings, such as the signing team, icons or entitlements.",[593,2193,2194,2195,2197],{},"The app's ",[607,2196,1998],{}," lists:",[600,2199,2200,2234,2239],{},[603,2201,2202,2203,1490,2206,1490,2209,1490,2212,1490,2215,1490,2218,2221,2222,1490,2225,1490,2228,761,2231,1789],{},"the Capacitor plugins the shell uses: ",[607,2204,2205],{},"@capacitor\u002Fapp",[607,2207,2208],{},"browser",[607,2210,2211],{},"haptics",[607,2213,2214],{},"action-sheet",[607,2216,2217],{},"dialog",[607,2219,2220],{},"share",",\n",[607,2223,2224],{},"status-bar",[607,2226,2227],{},"keyboard",[607,2229,2230],{},"preferences",[607,2232,2233],{},"@capgo\u002Fcapacitor-native-navigation",[603,2235,2236,2238],{},[607,2237,1287],{}," for the tokens,",[603,2240,2241,1490,2243,761,2246,935],{},[607,2242,2102],{},[607,2244,2245],{},"@frigear-nu\u002Fauth",[607,2247,2105],{},[896,2249,2250,2251,2253],{},"Capacitor only links the native code of the app's own dependencies. Add plugins to ",[607,2252,649],{},", not to a package it\nuses.",[678,2255,1157],{"id":2256},"add-appsmobile-to-an-existing-workspace",[593,2258,2259,2260,2263,2264,2266],{},"To add the app to a project made from the ",[612,2261,2262],{"href":454},"workspace starter",", or any pnpm workspace with\n",[607,2265,2041],{},", copy just the app:",[767,2268,2269,2298],{},[770,2270,2272],{"className":772,"code":2271,"filename":562,"language":775,"meta":776,"style":776},"pnpx @frigear-nu\u002Fcli app:create mobile --template mobile\npnpm prepare:env\n",[607,2273,2274,2292],{"__ignoreMap":776},[780,2275,2276,2278,2281,2284,2287,2290],{"class":782,"line":783},[780,2277,832],{"class":786},[780,2279,2280],{"class":790}," @frigear-nu\u002Fcli",[780,2282,2283],{"class":790}," app:create",[780,2285,2286],{"class":790}," mobile",[780,2288,2289],{"class":790}," --template",[780,2291,803],{"class":790},[780,2293,2294,2296],{"class":782,"line":806},[780,2295,787],{"class":786},[780,2297,877],{"class":790},[770,2299,2301],{"className":772,"code":2300,"filename":753,"language":775,"meta":776,"style":776},"pnpx giget@latest gh:Frigear-nu\u002Ffrigear-nu\u002F.starters\u002Fmobile\u002Fapps\u002Fmobile apps\u002Fmobile\npnpm install\npnpm prepare:env\n",[607,2302,2303,2315,2321],{"__ignoreMap":776},[780,2304,2305,2307,2309,2312],{"class":782,"line":783},[780,2306,832],{"class":786},[780,2308,835],{"class":790},[780,2310,2311],{"class":790}," gh:Frigear-nu\u002Ffrigear-nu\u002F.starters\u002Fmobile\u002Fapps\u002Fmobile",[780,2313,2314],{"class":790}," apps\u002Fmobile\n",[780,2316,2317,2319],{"class":782,"line":806},[780,2318,787],{"class":786},[780,2320,869],{"class":790},[780,2322,2323,2325],{"class":782,"line":816},[780,2324,787],{"class":786},[780,2326,877],{"class":790},[593,2328,2329,2331,2332,2334,2335,2337,2338,2341,2342,2345,2346,1490,2349,1320,2352,935],{},[607,2330,764],{}," creates ",[607,2333,889],{}," too: ",[607,2336,2061],{}," handles every app with an ",[607,2339,2340],{},".env.example",".\nIf your project predates it, copy the script from a current starter. Add the root scripts you want from the\n",[612,2343,1161],{"href":2344},"\u002Fexamples\u002Fstarters\u002Fmobile-standalone#commands",", such as ",[607,2347,2348],{},"dev:mobile",[607,2350,2351],{},"mobile:native",[607,2353,1940],{},[593,2355,2356,2357,2360,2361,765],{},"To make ",[607,2358,2359],{},"apps\u002Fweb"," the issuer and API of the app, apply the difference between the two starters. It is all in\n",[607,2362,2359],{},[2364,2365,2366,2371,2398,2402,2411,2697,2701,2716,2734,2738,2757,2761],"steps",{},[2367,2368,2370],"h3",{"id":2369},"install-oauth-server-and-the-apps-identity","Install OAuth Server and the app's identity",[770,2372,2374],{"className":772,"code":2373,"language":775,"meta":776,"style":776},"pnpm --filter .\u002Fapps\u002Fweb add @frigear-nu\u002Foauth-server @workspace\u002Fmobile@workspace:*\n",[607,2375,2376],{"__ignoreMap":776},[780,2377,2378,2380,2383,2386,2389,2392,2395],{"class":782,"line":783},[780,2379,787],{"class":786},[780,2381,2382],{"class":790}," --filter",[780,2384,2385],{"class":790}," .\u002Fapps\u002Fweb",[780,2387,2388],{"class":790}," add",[780,2390,2391],{"class":790}," @frigear-nu\u002Foauth-server",[780,2393,2394],{"class":790}," @workspace\u002Fmobile@workspace:",[780,2396,2397],{"class":1673},"*\n",[2367,2399,2401],{"id":2400},"register-the-app-as-a-configured-client","Register the app as a configured client",[593,2403,618,2404,2407,2408,765],{},[612,2405,2406],{"href":87},"configured client"," is a public OAuth application defined in config. Add it in\n",[607,2409,2410],{},"apps\u002Fweb\u002Fnuxt.config.ts",[770,2412,2414],{"className":1531,"code":2413,"filename":2410,"language":1533,"meta":776,"style":776},"import { appName, clientId, devRedirectUri, nativeRedirectUri } from '@workspace\u002Fmobile\u002Fidentity'\n\nexport default defineNuxtConfig({\n  modules: [\n    \u002F\u002F ...\n    '@frigear-nu\u002Foauth-server',\n  ],\n  oauthServer: {\n    cors: {\n      origins: ['capacitor:\u002F\u002Flocalhost', 'https:\u002F\u002Flocalhost'],\n      paths: ['\u002Fapi\u002F'],\n    },\n    clients: [{\n      id: clientId,\n      name: appName,\n      redirectUris: [nativeRedirectUri],\n      devRedirectUris: [devRedirectUri],\n    }],\n    settings: { pages: { admin: true } },\n  },\n})\n",[607,2415,2416,2453,2459,2474,2484,2489,2501,2508,2517,2527,2556,2577,2583,2595,2607,2619,2632,2645,2655,2683,2689],{"__ignoreMap":776},[780,2417,2418,2421,2423,2426,2428,2431,2433,2436,2438,2441,2444,2447,2449,2451],{"class":782,"line":783},[780,2419,2420],{"class":1665},"import",[780,2422,1591],{"class":1543},[780,2424,2425],{"class":1673}," appName",[780,2427,1789],{"class":1543},[780,2429,2430],{"class":1673}," clientId",[780,2432,1789],{"class":1543},[780,2434,2435],{"class":1673}," devRedirectUri",[780,2437,1789],{"class":1543},[780,2439,2440],{"class":1673}," nativeRedirectUri",[780,2442,2443],{"class":1543}," }",[780,2445,2446],{"class":1665}," from",[780,2448,1599],{"class":1543},[780,2450,1922],{"class":790},[780,2452,1748],{"class":1543},[780,2454,2455],{"class":782,"line":806},[780,2456,2458],{"emptyLinePlaceholder":2457},true,"\n",[780,2460,2461,2463,2466,2469,2471],{"class":782,"line":816},[780,2462,1666],{"class":1665},[780,2464,2465],{"class":1665}," default",[780,2467,2468],{"class":809}," defineNuxtConfig",[780,2470,1844],{"class":1673},[780,2472,2473],{"class":1543},"{\n",[780,2475,2476,2479,2481],{"class":782,"line":855},[780,2477,2478],{"class":1560},"  modules",[780,2480,765],{"class":1543},[780,2482,2483],{"class":1673}," [\n",[780,2485,2486],{"class":782,"line":864},[780,2487,2488],{"class":1687},"    \u002F\u002F ...\n",[780,2490,2491,2494,2497,2499],{"class":782,"line":872},[780,2492,2493],{"class":1543},"    '",[780,2495,2496],{"class":790},"@frigear-nu\u002Foauth-server",[780,2498,1557],{"class":1543},[780,2500,2221],{"class":1543},[780,2502,2503,2506],{"class":782,"line":1814},[780,2504,2505],{"class":1673},"  ]",[780,2507,2221],{"class":1543},[780,2509,2510,2513,2515],{"class":782,"line":1878},[780,2511,2512],{"class":1560},"  oauthServer",[780,2514,765],{"class":1543},[780,2516,1546],{"class":1543},[780,2518,2520,2523,2525],{"class":782,"line":2519},9,[780,2521,2522],{"class":1560},"    cors",[780,2524,765],{"class":1543},[780,2526,1546],{"class":1543},[780,2528,2530,2533,2535,2537,2539,2541,2543,2545,2547,2549,2551,2554],{"class":782,"line":2529},10,[780,2531,2532],{"class":1560},"      origins",[780,2534,765],{"class":1543},[780,2536,1780],{"class":1673},[780,2538,1557],{"class":1543},[780,2540,1081],{"class":790},[780,2542,1557],{"class":1543},[780,2544,1789],{"class":1543},[780,2546,1599],{"class":1543},[780,2548,1085],{"class":790},[780,2550,1557],{"class":1543},[780,2552,2553],{"class":1673},"]",[780,2555,2221],{"class":1543},[780,2557,2559,2562,2564,2566,2568,2571,2573,2575],{"class":782,"line":2558},11,[780,2560,2561],{"class":1560},"      paths",[780,2563,765],{"class":1543},[780,2565,1780],{"class":1673},[780,2567,1557],{"class":1543},[780,2569,2570],{"class":790},"\u002Fapi\u002F",[780,2572,1557],{"class":1543},[780,2574,2553],{"class":1673},[780,2576,2221],{"class":1543},[780,2578,2580],{"class":782,"line":2579},12,[780,2581,2582],{"class":1543},"    },\n",[780,2584,2586,2589,2591,2593],{"class":782,"line":2585},13,[780,2587,2588],{"class":1560},"    clients",[780,2590,765],{"class":1543},[780,2592,1780],{"class":1673},[780,2594,2473],{"class":1543},[780,2596,2598,2601,2603,2605],{"class":782,"line":2597},14,[780,2599,2600],{"class":1560},"      id",[780,2602,765],{"class":1543},[780,2604,2430],{"class":1673},[780,2606,2221],{"class":1543},[780,2608,2610,2613,2615,2617],{"class":782,"line":2609},15,[780,2611,2612],{"class":1560},"      name",[780,2614,765],{"class":1543},[780,2616,2425],{"class":1673},[780,2618,2221],{"class":1543},[780,2620,2622,2625,2627,2630],{"class":782,"line":2621},16,[780,2623,2624],{"class":1560},"      redirectUris",[780,2626,765],{"class":1543},[780,2628,2629],{"class":1673}," [nativeRedirectUri]",[780,2631,2221],{"class":1543},[780,2633,2635,2638,2640,2643],{"class":782,"line":2634},17,[780,2636,2637],{"class":1560},"      devRedirectUris",[780,2639,765],{"class":1543},[780,2641,2642],{"class":1673}," [devRedirectUri]",[780,2644,2221],{"class":1543},[780,2646,2648,2651,2653],{"class":782,"line":2647},18,[780,2649,2650],{"class":1543},"    }",[780,2652,2553],{"class":1673},[780,2654,2221],{"class":1543},[780,2656,2658,2661,2663,2665,2668,2670,2672,2675,2677,2679,2681],{"class":782,"line":2657},19,[780,2659,2660],{"class":1560},"    settings",[780,2662,765],{"class":1543},[780,2664,1591],{"class":1543},[780,2666,2667],{"class":1560}," pages",[780,2669,765],{"class":1543},[780,2671,1591],{"class":1543},[780,2673,2674],{"class":1560}," admin",[780,2676,765],{"class":1543},[780,2678,1573],{"class":1572},[780,2680,2443],{"class":1543},[780,2682,1576],{"class":1543},[780,2684,2686],{"class":782,"line":2685},20,[780,2687,2688],{"class":1543},"  },\n",[780,2690,2692,2694],{"class":782,"line":2691},21,[780,2693,1865],{"class":1543},[780,2695,2696],{"class":1673},")\n",[2367,2698,2700],{"id":2699},"add-an-api-route","Add an API route",[593,2702,2703,2704,2707,2708,2711,2712,2715],{},"The workspace starter's roles decide who manages OAuth applications: ",[607,2705,2706],{},"admin"," holds ",[607,2709,2710],{},"oauth:manage",", and an\napplication's ",[607,2713,2714],{},"allowedRoles"," resolves through the same roles. No plugin is needed for either.",[593,2717,732,2718,2721,2722,2725,2726,2731,2732,935],{},[607,2719,2720],{},"apps\u002Fweb\u002Fserver\u002Fapi\u002Fme.get.ts"," from ",[607,2723,2724],{},".starters\u002Fmobile-standalone","\n(",[612,2727,2730],{"href":2728,"rel":2729},"https:\u002F\u002Fgithub.com\u002FFrigear-nu\u002Ffrigear-nu\u002Ftree\u002Fmain\u002F.starters\u002Fmobile-standalone\u002Fapps\u002Fweb\u002Fserver\u002Fapi",[737],"on GitHub","): the\nAPI route the app calls, with ",[607,2733,1646],{},[2367,2735,2737],{"id":2736},"add-a-signing-key","Add a signing key",[593,2739,2740,2741,886,2744,2747,2748,2750,2751,2753,2754,935],{},"Add ",[607,2742,2743],{},"NUXT_OAUTH_SERVER_PRIVATE_KEY=",[607,2745,2746],{},"apps\u002Fweb\u002F.env.example",", and generate a key\n(",[612,2749,141],{"href":142},"). ",[607,2752,1459],{}," works without one: it keeps a dev key in\n",[607,2755,2756],{},".data\u002Foauth-server\u002Fdev-key.pem",[2367,2758,2760],{"id":2759},"generate-the-migration","Generate the migration",[593,2762,2763,2764,2767],{},"OAuth Server adds tables. Run ",[607,2765,2766],{},"pnpm db:generate"," and commit the migration.",[593,2769,2770,2771,2775],{},"The ",[612,2772,2774],{"href":2773},"\u002Fexamples\u002Fstarters\u002Fmobile-standalone#how-the-two-apps-fit-together","standalone starter guide"," explains each piece.",[678,2777,2779],{"id":2778},"production-checklist","Production checklist",[1637,2781,2782,2783,2785,2786,2788],{},"Never ship a ",[607,2784,1414],{}," or LAN address, and never register the ",[607,2787,1414],{}," redirect URI in production.",[600,2790,2793,2816,2826,2840,2851,2867,2876,2882,2899],{"className":2791},[2792],"contains-task-list",[603,2794,2797,2801,2802,1490,2804,761,2807,1123,2809,2811,2812,761,2814,935],{"className":2795},[2796],"task-list-item",[2798,2799],"input",{"disabled":2457,"type":2800},"checkbox"," Set your own ",[607,2803,1053],{},[607,2805,2806],{},"appName",[607,2808,965],{},[607,2810,969],{},", and recreate ",[607,2813,1343],{},[607,2815,2187],{},[603,2817,2819,2821,2822,2825],{"className":2818},[2796],[2798,2820],{"disabled":2457,"type":2800}," Register the production redirect URI ",[607,2823,2824],{},"\u003Capp id>:\u002Fauth\u002Fcallback"," at the issuer.",[603,2827,2829,2831,2832,761,2834,2836,2837,2839],{"className":2828},[2796],[2798,2830],{"disabled":2457,"type":2800}," Point ",[607,2833,924],{},[607,2835,942],{}," at ",[607,2838,1514],{},"\nproduction URLs, in the environment of the release build.",[603,2841,2843,2845,2846,761,2848,2850],{"className":2842},[2796],[2798,2844],{"disabled":2457,"type":2800}," Allow ",[607,2847,1081],{},[607,2849,1085],{}," for CORS at the issuer and on your API.",[603,2852,2854,2856,2857,2859,2860,2863,2864,2866],{"className":2853},[2796],[2798,2855],{"disabled":2457,"type":2800}," Build releases with ",[607,2858,1463],{},", for example ",[607,2861,2862],{},"MOBILE_RELEASE=1 pnpm mobile:sync",", so ",[607,2865,1455],{}," cannot\nslip in.",[603,2868,2870,2872,2873,2875],{"className":2869},[2796],[2798,2871],{"disabled":2457,"type":2800}," Keep ",[607,2874,1287],{}," installed. Without it a production build fails, because tokens would\nbe stored unencrypted.",[603,2877,2879,2881],{"className":2878},[2796],[2798,2880],{"disabled":2457,"type":2800}," Check the route rules. Nothing is protected without them, and your API checks every protected request.",[603,2883,2885,2887,2888,2890,2891,2894,2895,2898],{"className":2884},[2796],[2798,2886],{"disabled":2457,"type":2800}," Run ",[607,2889,1384],{}," and fix what it reports. For CI, ",[607,2892,2893],{},"frigear-mobile doctor --strict"," exits with ",[607,2896,2897],{},"1"," on\nerrors.",[603,2900,2902,2904],{"className":2901},[2796],[2798,2903],{"disabled":2457,"type":2800}," Replace the app icon and splash screen in the native projects, set the version and build number, and set up\nsigning for the App Store and Google Play.",[593,2906,2770,2907,2911],{},[612,2908,2910],{"href":2909},"\u002Fmodules\u002Finterface\u002Fmobile\u002Fcli-and-devices#release-checklist","Mobile release checklist"," has the details.",[678,2913,2915],{"id":2914},"next-steps","Next steps",[600,2917,2918,2927,2941,2946],{},[603,2919,2920,2922,2923,2926],{},[612,2921,362],{"href":363}," – tabs, page meta, native chrome, ",[607,2924,2925],{},"useNative()",", deep links and the CLI.",[603,2928,2929,2931,2932,1490,2935,1490,2938,2940],{},[612,2930,662],{"href":184}," – ",[607,2933,2934],{},"useUserSession()",[607,2936,2937],{},"useAuth()",[607,2939,951],{},", token storage\nand the options.",[603,2942,2943,2945],{},[612,2944,199],{"href":200}," – route rules and permissions for the app's guards.",[603,2947,2948,2950],{},[612,2949,131],{"href":132}," – point the app at another issuer.",[2952,2953,2954],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":776,"searchDepth":806,"depth":806,"links":2956},[2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2974,2975],{"id":680,"depth":806,"text":681},{"id":728,"depth":806,"text":729},{"id":893,"depth":806,"text":894},{"id":1189,"depth":806,"text":1190},{"id":1291,"depth":806,"text":1292},{"id":1388,"depth":806,"text":1389},{"id":1470,"depth":806,"text":1471},{"id":1518,"depth":806,"text":1519},{"id":1649,"depth":806,"text":1650},{"id":1976,"depth":806,"text":1977},{"id":2256,"depth":806,"text":1157,"children":2968},[2969,2970,2971,2972,2973],{"id":2369,"depth":816,"text":2370},{"id":2400,"depth":816,"text":2401},{"id":2699,"depth":816,"text":2700},{"id":2736,"depth":816,"text":2737},{"id":2759,"depth":816,"text":2760},{"id":2778,"depth":806,"text":2779},{"id":2914,"depth":806,"text":2915},"Start an iOS and Android app on the Frigear mobile shell that signs in at frigear.nu or any OpenID Connect issuer, from the mobile starter.","md",null,{},{"icon":460},{"title":457,"description":2976},"GCEkHE_nLnncuYNQTdr5QlAjI4ESNrbAloEx7DcZqJI",[2984,2986],{"title":453,"path":454,"stem":455,"description":2985,"icon":6,"children":-1},"A pnpm workspace with one Nuxt 4 app that has registration, sign-in, account pages and a user admin, built on the published Frigear modules.",{"title":462,"path":463,"stem":464,"description":2987,"icon":465,"children":-1},"Start a mobile app with its own backend - a Nuxt 4 web app that is the OAuth server and API, and a Capacitor app that signs in at it - from the standalone mobile starter.",1794503140359]