[{"data":1,"prerenderedAt":1994},["ShallowReactive",2],{"navigation_docs":3,"-modules-identity-oauth-resource":587,"-modules-identity-oauth-resource-surround":1989},[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":241,"body":589,"description":1982,"extension":1983,"links":1984,"meta":1985,"navigation":1986,"path":242,"seo":1987,"stem":243,"__hash__":1988},"docs\u002F3.modules\u002F1.identity\u002F8.oauth-resource.md",{"type":590,"value":591,"toc":1961},"minimark",[592,608,619,624,674,678,702,706,1090,1124,1128,1135,1398,1408,1418,1428,1446,1450,1461,1486,1490,1493,1497,1508,1549,1559,1565,1585,1589,1600,1628,1634,1656,1663,1665,1678,1776,1782,1823,1826,1832,1851,1858,1861,1880,1884,1893,1916,1920,1957],[593,594,595,596,600,601,604,605,607],"p",{},"Your mobile app talks to several services, such as vouchers, a shop and an inventory. People should sign in once, not\nonce per service. OAuth Resource lets a service accept the ",[597,598,599],"a",{"href":87},"access tokens"," that an\n",[597,602,603],{"href":87},"issuer"," gave the app, so the service needs no sign-in of its own. If the service should be\nthe place where people sign in, use ",[597,606,225],{"href":226}," instead.",[593,609,610,611,615,616,618],{},"The module is ",[612,613,614],"code",{},"@frigear-nu\u002Foauth-resource",". ",[597,617,81],{"href":82}," shows where it fits.",[620,621,623],"h2",{"id":622},"what-you-get","What you get",[625,626,627,646,652,658,668],"ul",{},[628,629,630,634,635,638,639,641,642,645],"li",{},[631,632,633],"strong",{},"Server routes."," None of its own. Your routes that call ",[612,636,637],{},"authorize(event, permission)"," accept the issuer's access\ntokens and get the usual ",[597,640,168],{"href":169}," principal. What the token may do is the ",[612,643,644],{},"permissions"," the\nissuer put in it, or its live introspection answer. On by default.",[628,647,648,651],{},[631,649,650],{},"Users."," A user the service has not seen is created from the issuer's profile the first time a token arrives. On\nby default.",[628,653,654,657],{},[631,655,656],{},"Introspection."," Optional: ask the issuer what a token may do now, instead of trusting the claim in it.",[628,659,660,663,664,667],{},[631,661,662],{},"Cross-origin calls."," CORS headers on ",[612,665,666],{},"\u002Fapi\u002F"," for the webviews of a Capacitor app. On by default.",[628,669,670,673],{},[631,671,672],{},"Pages, composables and emails."," None.",[675,676],"module-runtime",{":server":677},"true",[679,680,681,684,685,687,688,690,691,684,694,697,698,701],"note",{},[631,682,683],{},"Installed for you:"," ",[597,686,168],{"href":169},", ",[597,689,191],{"href":192}," and Module Kit.\n",[631,692,693],{},"You add:",[612,695,696],{},"@nuxthub\u002Fcore",".\n",[631,699,700],{},"Optional:"," nothing.",[620,703,705],{"id":704},"quick-setup","Quick setup",[707,708,709,714,742,749,759,883,891,895,917,982,986,989,1087],"steps",{},[710,711,713],"h3",{"id":712},"install-the-module","Install the module",[715,716,721],"pre",{"className":717,"code":718,"language":719,"meta":720,"style":720},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm add @frigear-nu\u002Foauth-resource @nuxthub\u002Fcore\n","bash","",[612,722,723],{"__ignoreMap":720},[724,725,728,732,736,739],"span",{"class":726,"line":727},"line",1,[724,729,731],{"class":730},"sBMFI","pnpm",[724,733,735],{"class":734},"sfazB"," add",[724,737,738],{"class":734}," @frigear-nu\u002Foauth-resource",[724,740,741],{"class":734}," @nuxthub\u002Fcore\n",[710,743,745,746],{"id":744},"configure-it-in-nuxtconfigts","Configure it in ",[612,747,748],{},"nuxt.config.ts",[593,750,751,752,754,755,758],{},"The defaults accept frigear.nu's tokens. Point ",[612,753,603],{}," and ",[612,756,757],{},"audience"," at any issuer whose access tokens follow\nRFC 9068, the JSON Web Token profile for access tokens.",[715,760,764],{"className":761,"code":762,"filename":748,"language":763,"meta":720,"style":720},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default defineNuxtConfig({\n  modules: ['@nuxthub\u002Fcore', '@frigear-nu\u002Foauth-resource'],\n  oauthResource: {\n    issuer: 'https:\u002F\u002Ffrigear.nu',\n    audience: 'frigear-services',\n  },\n})\n","ts",[612,765,766,787,823,834,851,868,874],{"__ignoreMap":720},[724,767,768,772,775,779,783],{"class":726,"line":727},[724,769,771],{"class":770},"s7zQu","export",[724,773,774],{"class":770}," default",[724,776,778],{"class":777},"s2Zo4"," defineNuxtConfig",[724,780,782],{"class":781},"sTEyZ","(",[724,784,786],{"class":785},"sMK4o","{\n",[724,788,790,794,797,800,803,805,807,810,813,815,817,820],{"class":726,"line":789},2,[724,791,793],{"class":792},"swJcz","  modules",[724,795,796],{"class":785},":",[724,798,799],{"class":781}," [",[724,801,802],{"class":785},"'",[724,804,696],{"class":734},[724,806,802],{"class":785},[724,808,809],{"class":785},",",[724,811,812],{"class":785}," '",[724,814,614],{"class":734},[724,816,802],{"class":785},[724,818,819],{"class":781},"]",[724,821,822],{"class":785},",\n",[724,824,826,829,831],{"class":726,"line":825},3,[724,827,828],{"class":792},"  oauthResource",[724,830,796],{"class":785},[724,832,833],{"class":785}," {\n",[724,835,837,840,842,844,847,849],{"class":726,"line":836},4,[724,838,839],{"class":792},"    issuer",[724,841,796],{"class":785},[724,843,812],{"class":785},[724,845,846],{"class":734},"https:\u002F\u002Ffrigear.nu",[724,848,802],{"class":785},[724,850,822],{"class":785},[724,852,854,857,859,861,864,866],{"class":726,"line":853},5,[724,855,856],{"class":792},"    audience",[724,858,796],{"class":785},[724,860,812],{"class":785},[724,862,863],{"class":734},"frigear-services",[724,865,802],{"class":785},[724,867,822],{"class":785},[724,869,871],{"class":726,"line":870},6,[724,872,873],{"class":785},"  },\n",[724,875,877,880],{"class":726,"line":876},7,[724,878,879],{"class":785},"}",[724,881,882],{"class":781},")\n",[884,885,886,887,890],"warning",{},"Without this step the issuer drops your service's scopes as unsupported. Tokens then lack them, and every route that\nrequires one answers ",[612,888,889],{},"403",".",[710,892,894],{"id":893},"register-the-services-permissions-at-the-issuer-and-here","Register the service's permissions at the issuer and here",[593,896,897,898,901,902,905,906,909,910,914,915,890],{},"The issuer has to know the permissions, or it drops them as unknown. Register them there with ",[612,899,900],{},"addPermission",", and\ndescribe them in its ",[612,903,904],{},"oauthServer.scopes",", each with a ",[612,907,908],{},"service"," name, so the consent screen groups them under your\nservice. See ",[597,911,913],{"href":912},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver#scopes","Scopes",". Register the same permissions in this service too:\na route that checks an unknown permission answers ",[612,916,889],{},[715,918,921],{"className":761,"code":919,"filename":920,"language":763,"meta":720,"style":720},"authentication: {\n  permissions: [{ name: 'vouchers:read', label: 'See your vouchers' }],\n},\n","nuxt.config.ts of the service",[612,922,923,932,977],{"__ignoreMap":720},[724,924,925,928,930],{"class":726,"line":727},[724,926,927],{"class":730},"authentication",[724,929,796],{"class":785},[724,931,833],{"class":785},[724,933,934,937,939,941,944,947,949,951,954,956,958,961,963,965,968,970,973,975],{"class":726,"line":789},[724,935,936],{"class":730},"  permissions",[724,938,796],{"class":785},[724,940,799],{"class":792},[724,942,943],{"class":785},"{",[724,945,946],{"class":792}," name",[724,948,796],{"class":785},[724,950,812],{"class":785},[724,952,953],{"class":734},"vouchers:read",[724,955,802],{"class":785},[724,957,809],{"class":785},[724,959,960],{"class":792}," label",[724,962,796],{"class":785},[724,964,812],{"class":785},[724,966,967],{"class":734},"See your vouchers",[724,969,802],{"class":785},[724,971,972],{"class":785}," }",[724,974,819],{"class":792},[724,976,822],{"class":785},[724,978,979],{"class":726,"line":825},[724,980,981],{"class":785},"},\n",[710,983,985],{"id":984},"check-a-permission-in-a-route","Check a permission in a route",[593,987,988],{},"Routes check the permission they need, exactly as for API tokens or sessions:",[715,990,993],{"className":761,"code":991,"filename":992,"language":763,"meta":720,"style":720},"export default defineEventHandler(async (event) => {\n  const { user } = await authorize(event, 'vouchers:read')\n  return listVouchers(user.id)\n})\n","server\u002Fapi\u002Fvouchers\u002Findex.get.ts",[612,994,995,1025,1061,1081],{"__ignoreMap":720},[724,996,997,999,1001,1004,1006,1010,1013,1017,1020,1023],{"class":726,"line":727},[724,998,771],{"class":770},[724,1000,774],{"class":770},[724,1002,1003],{"class":777}," defineEventHandler",[724,1005,782],{"class":781},[724,1007,1009],{"class":1008},"spNyl","async",[724,1011,1012],{"class":785}," (",[724,1014,1016],{"class":1015},"sHdIc","event",[724,1018,1019],{"class":785},")",[724,1021,1022],{"class":1008}," =>",[724,1024,833],{"class":785},[724,1026,1027,1030,1033,1036,1038,1041,1044,1047,1049,1051,1053,1055,1057,1059],{"class":726,"line":789},[724,1028,1029],{"class":1008},"  const",[724,1031,1032],{"class":785}," {",[724,1034,1035],{"class":781}," user",[724,1037,972],{"class":785},[724,1039,1040],{"class":785}," =",[724,1042,1043],{"class":770}," await",[724,1045,1046],{"class":777}," authorize",[724,1048,782],{"class":792},[724,1050,1016],{"class":781},[724,1052,809],{"class":785},[724,1054,812],{"class":785},[724,1056,953],{"class":734},[724,1058,802],{"class":785},[724,1060,882],{"class":792},[724,1062,1063,1066,1069,1071,1074,1076,1079],{"class":726,"line":825},[724,1064,1065],{"class":770},"  return",[724,1067,1068],{"class":777}," listVouchers",[724,1070,782],{"class":792},[724,1072,1073],{"class":781},"user",[724,1075,890],{"class":785},[724,1077,1078],{"class":781},"id",[724,1080,882],{"class":792},[724,1082,1083,1085],{"class":726,"line":836},[724,1084,879],{"class":785},[724,1086,882],{"class":781},[593,1088,1089],{},"A signed-in session still passes when the service's own roles grant the permission, so its web pages keep working.",[1091,1092,1095,1098,1099,1101,1102,1104,1105,1108,1109,1112,1113,1116,1117,1012,1120,1123],"callout",{"color":1093,"icon":1094},"success","i-lucide-check",[631,1096,1097],{},"What you should see:"," a call with a valid access token whose ",[612,1100,644],{}," include ",[612,1103,953],{}," answers ",[612,1106,1107],{},"200",". A\ntoken without it answers ",[612,1110,1111],{},"403 insufficient_scope",", with ",[612,1114,1115],{},"WWW-Authenticate: Bearer error=\"insufficient_scope\"",". An invalid,\nexpired or foreign token answers ",[612,1118,1119],{},"401",[612,1121,1122],{},"error=\"invalid_token\"",").",[620,1125,1127],{"id":1126},"options","Options",[593,1129,1130,1131,1134],{},"Options go under the ",[612,1132,1133],{},"oauthResource"," key.",[1136,1137,1138,1157],"table",{},[1139,1140,1141],"thead",{},[1142,1143,1144,1148,1151,1154],"tr",{},[1145,1146,1147],"th",{},"Option",[1145,1149,1150],{},"Type",[1145,1152,1153],{},"Default",[1145,1155,1156],{},"Purpose",[1158,1159,1160,1183,1204,1223,1242,1265,1283,1307,1327,1344,1360,1379],"tbody",{},[1142,1161,1162,1167,1172,1176],{},[1163,1164,1165],"td",{},[612,1166,603],{},[1163,1168,1169],{},[612,1170,1171],{},"string",[1163,1173,1174],{},[612,1175,846],{},[1163,1177,1178,1179,1182],{},"The ",[612,1180,1181],{},"iss"," that tokens must carry.",[1142,1184,1185,1189,1194,1198],{},[1163,1186,1187],{},[612,1188,757],{},[1163,1190,1191],{},[612,1192,1193],{},"string | string[]",[1163,1195,1196],{},[612,1197,863],{},[1163,1199,1178,1200,1203],{},[612,1201,1202],{},"aud"," that tokens must carry, any one of them.",[1142,1205,1206,1211,1215,1220],{},[1163,1207,1208],{},[612,1209,1210],{},"jwksUrl",[1163,1212,1213],{},[612,1214,1171],{},[1163,1216,1217],{},[612,1218,1219],{},"\u003Cissuer>\u002F.well-known\u002Fjwks.json",[1163,1221,1222],{},"Where the issuer's signing keys are read.",[1142,1224,1225,1230,1234,1239],{},[1163,1226,1227],{},[612,1228,1229],{},"userinfoUrl",[1163,1231,1232],{},[612,1233,1171],{},[1163,1235,1236],{},[612,1237,1238],{},"\u003Cissuer>\u002Foauth\u002Fuserinfo",[1163,1240,1241],{},"Where an unknown user's profile is read.",[1142,1243,1244,1249,1254,1262],{},[1163,1245,1246],{},[612,1247,1248],{},"corsOrigins",[1163,1250,1251],{},[612,1252,1253],{},"string[]",[1163,1255,1256,687,1259],{},[612,1257,1258],{},"capacitor:\u002F\u002Flocalhost",[612,1260,1261],{},"https:\u002F\u002Flocalhost",[1163,1263,1264],{},"Exact origins whose webviews may call the API with a bearer token.",[1142,1266,1267,1272,1276,1280],{},[1163,1268,1269],{},[612,1270,1271],{},"corsPaths",[1163,1273,1274],{},[612,1275,1253],{},[1163,1277,1278],{},[612,1279,666],{},[1163,1281,1282],{},"Path prefixes the CORS headers apply to.",[1142,1284,1285,1290,1295,1300],{},[1163,1286,1287],{},[612,1288,1289],{},"forceId",[1163,1291,1292],{},[612,1293,1294],{},"boolean",[1163,1296,1297],{},[612,1298,1299],{},"runtimeConfig.frigear.forceId",[1163,1301,1302,1303,1306],{},"Use the issuer's user ID (",[612,1304,1305],{},"sub",") as the local user ID.",[1142,1308,1309,1314,1319,1324],{},[1163,1310,1311],{},[612,1312,1313],{},"jwksCacheTtl",[1163,1315,1316],{},[612,1317,1318],{},"number",[1163,1320,1321],{},[612,1322,1323],{},"600",[1163,1325,1326],{},"Seconds the signing keys are cached.",[1142,1328,1329,1334,1338,1341],{},[1163,1330,1331],{},[612,1332,1333],{},"introspection.clientId",[1163,1335,1336],{},[612,1337,1171],{},[1163,1339,1340],{},"unset",[1163,1342,1343],{},"The confidential client of the issuer that asks. Setting it turns introspection on.",[1142,1345,1346,1351,1355,1357],{},[1163,1347,1348],{},[612,1349,1350],{},"introspection.clientSecret",[1163,1352,1353],{},[612,1354,1171],{},[1163,1356,1340],{},[1163,1358,1359],{},"The secret of that client. Set it through env.",[1142,1361,1362,1367,1371,1376],{},[1163,1363,1364],{},[612,1365,1366],{},"introspection.endpoint",[1163,1368,1369],{},[612,1370,1171],{},[1163,1372,1373],{},[612,1374,1375],{},"\u003Cissuer>\u002Foauth\u002Fintrospect",[1163,1377,1378],{},"Where the token is introspected.",[1142,1380,1381,1386,1390,1395],{},[1163,1382,1383],{},[612,1384,1385],{},"introspection.cacheTtl",[1163,1387,1388],{},[612,1389,1318],{},[1163,1391,1392],{},[612,1393,1394],{},"30",[1163,1396,1397],{},"Seconds an answer is cached, never past the token's expiry.",[593,1399,1400,1404,1405,890],{},[631,1401,1402],{},[612,1403,757],{}," matches the issuer's ",[612,1406,1407],{},"accessTokenAudience",[593,1409,1410,1414,1415,890],{},[631,1411,1412],{},[612,1413,1248],{}," takes exact origins only, never ",[612,1416,1417],{},"*",[593,1419,1420,1424,1425,890],{},[631,1421,1422],{},[612,1423,1289],{}," is explained under ",[597,1426,191],{"href":1427},"#users",[593,1429,1430,1424,1435,1439,1440,1443,1444,890],{},[631,1431,1432],{},[612,1433,1434],{},"introspection",[597,1436,1438],{"href":1437},"#introspection","Introspection",". The secret belongs in\n",[612,1441,1442],{},"NUXT_OAUTH_RESOURCE_INTROSPECTION_CLIENT_SECRET",", not in ",[612,1445,748],{},[710,1447,1449],{"id":1448},"runtime-config","Runtime config",[593,1451,1452,1453,1456,1457,1460],{},"Every option is also ",[612,1454,1455],{},"runtimeConfig.oauthResource.\u003Coption>",". Set it per environment with\n",[612,1458,1459],{},"NUXT_OAUTH_RESOURCE_\u003COPTION>",", with lists comma separated.",[679,1462,1463,1464,1467,1468,1470,1471,1474,1475,1478,1479,1482,1483,1123],{},"The module was renamed from ",[612,1465,1466],{},"@frigear-nu\u002Ffrigear-resource"," in 0.1.6. The config key is now ",[612,1469,1133],{}," (was\n",[612,1472,1473],{},"frigearResource","), the runtime config is ",[612,1476,1477],{},"runtimeConfig.oauthResource",", and the env vars are ",[612,1480,1481],{},"NUXT_OAUTH_RESOURCE_*","\n(were ",[612,1484,1485],{},"NUXT_FRIGEAR_RESOURCE_*",[620,1487,1489],{"id":1488},"server","Server",[593,1491,1492],{},"The module adds no routes. It plugs into Auth, so your existing routes accept the issuer's tokens.",[710,1494,1496],{"id":1495},"how-a-token-is-checked","How a token is checked",[593,1498,1499,1500,1503,1504,1507],{},"The module adds a token resolver to Auth (",[612,1501,1502],{},"addAuthResolver",", order ",[612,1505,1506],{},"50","). It looks at every JSON Web Token (JWT) and\naccepts it only when all of these hold:",[625,1509,1510,1521,1527,1535,1541],{},[628,1511,1512,1513,1516,1517,1520],{},"its header ",[612,1514,1515],{},"typ"," is ",[612,1518,1519],{},"at+jwt",";",[628,1522,1523,1524,1520],{},"it is signed with RS256 by a key in the issuer's ",[597,1525,1526],{"href":87},"JWKS",[628,1528,1529,1531,1532,1534],{},[612,1530,1181],{}," is the issuer, and ",[612,1533,1202],{}," contains the audience;",[628,1536,1537,1540],{},[612,1538,1539],{},"exp"," has not passed;",[628,1542,1543,1544,754,1546,890],{},"it has ",[612,1545,1305],{},[612,1547,1548],{},"client_id",[593,1550,1551,1552,1554,1555,1558],{},"The keys are cached for ",[612,1553,1313],{},". A token with an unknown ",[612,1556,1557],{},"kid"," means the issuer rotated its key, so the keys\nare fetched again, at most every 30 seconds.",[593,1560,1561,1562,1564],{},"Any other token is left to the next resolver, such as the service's own API tokens, and ends in ",[612,1563,1119],{},". ID tokens are\nnever accepted.",[593,1566,1567,1568,615,1571,1574,1575,1578,1579,1582,1583,890],{},"The principal is ",[612,1569,1570],{},"{ kind: 'oauth', user, scopes, permissions, foreign: true, client: { id } }",[612,1572,1573],{},"scopes"," is the token's\n",[612,1576,1577],{},"scope",", and ",[612,1580,1581],{},"client.id"," is the app's ",[612,1584,1548],{},[710,1586,1588],{"id":1587},"what-the-token-may-do","What the token may do",[593,1590,1591,1592,1595,1596,1599],{},"The token is ",[631,1593,1594],{},"foreign",": it comes from another issuer, so the service does not ask its own roles what the user may do.\n",[612,1597,1598],{},"principal.permissions"," replaces the role check. It is the first of these that applies:",[1601,1602,1603,1612,1620],"ol",{},[628,1604,1605,1608,1609,1611],{},[631,1606,1607],{},"The introspection answer",", when ",[597,1610,1434],{"href":1437}," is on.",[628,1613,1614,1619],{},[631,1615,1178,1616,1618],{},[612,1617,644],{}," claim"," the issuer froze in the token.",[628,1621,1622,1627],{},[631,1623,1624,1625],{},"The token's ",[612,1626,1577],{},", expanded against this service's permissions, for a token without the claim (an older issuer).",[593,1629,1630,1631,1633],{},"A malformed ",[612,1632,644],{}," claim is no claim: the token is refused.",[593,1635,1636,1637,1640,1641,1644,1645,1648,1649,1651,1652,1655],{},"A check then runs the same way as for any caller. An unknown permission is ",[612,1638,1639],{},"403 forbidden",". A ",[612,1642,1643],{},"sessionOnly"," permission is\n",[612,1646,1647],{},"403 session_required",", since a token never holds one. A permission the token lacks is ",[612,1650,1111],{},". The\n",[612,1653,1654],{},"authorization:before"," hook still runs, so it can deny a token. Policies on the permission refine it too. Nothing the\nuser's roles at this service hold can widen the token.",[884,1657,1658,1659,1662],{},"Without introspection, the service cannot revoke an access token or see a role change. A token lives as long as the\nissuer says, so keep ",[612,1660,1661],{},"accessTokenTtl"," at 5 to 15 minutes there. A user who loses access at the issuer stops getting new\ntokens at the next refresh. Turn on introspection when that is too slow.",[710,1664,1438],{"id":1434},[593,1666,1667,1668,1670,1671,1674,1675,890],{},"With ",[612,1669,1434],{},", the service asks the issuer what the token may do now, by ",[612,1672,1673],{},"POST \u002Foauth\u002Fintrospect"," (RFC 7662), and\nuses the answer instead of the frozen claim. A user who lost a role at the issuer loses it here within ",[612,1676,1677],{},"cacheTtl",[715,1679,1681],{"className":761,"code":1680,"filename":748,"language":763,"meta":720,"style":720},"export default defineNuxtConfig({\n  oauthResource: {\n    issuer: 'https:\u002F\u002Ffrigear.nu',\n    audience: 'frigear-services',\n    introspection: { clientId: 'app_resource', cacheTtl: 30 },\n  },\n})\n",[612,1682,1683,1695,1703,1717,1731,1766,1770],{"__ignoreMap":720},[724,1684,1685,1687,1689,1691,1693],{"class":726,"line":727},[724,1686,771],{"class":770},[724,1688,774],{"class":770},[724,1690,778],{"class":777},[724,1692,782],{"class":781},[724,1694,786],{"class":785},[724,1696,1697,1699,1701],{"class":726,"line":789},[724,1698,828],{"class":792},[724,1700,796],{"class":785},[724,1702,833],{"class":785},[724,1704,1705,1707,1709,1711,1713,1715],{"class":726,"line":825},[724,1706,839],{"class":792},[724,1708,796],{"class":785},[724,1710,812],{"class":785},[724,1712,846],{"class":734},[724,1714,802],{"class":785},[724,1716,822],{"class":785},[724,1718,1719,1721,1723,1725,1727,1729],{"class":726,"line":836},[724,1720,856],{"class":792},[724,1722,796],{"class":785},[724,1724,812],{"class":785},[724,1726,863],{"class":734},[724,1728,802],{"class":785},[724,1730,822],{"class":785},[724,1732,1733,1736,1738,1740,1743,1745,1747,1750,1752,1754,1757,1759,1763],{"class":726,"line":853},[724,1734,1735],{"class":792},"    introspection",[724,1737,796],{"class":785},[724,1739,1032],{"class":785},[724,1741,1742],{"class":792}," clientId",[724,1744,796],{"class":785},[724,1746,812],{"class":785},[724,1748,1749],{"class":734},"app_resource",[724,1751,802],{"class":785},[724,1753,809],{"class":785},[724,1755,1756],{"class":792}," cacheTtl",[724,1758,796],{"class":785},[724,1760,1762],{"class":1761},"sbssI"," 30",[724,1764,1765],{"class":785}," },\n",[724,1767,1768],{"class":726,"line":870},[724,1769,873],{"class":785},[724,1771,1772,1774],{"class":726,"line":876},[724,1773,879],{"class":785},[724,1775,882],{"class":781},[593,1777,1778,1779,1781],{},"Set ",[612,1780,1442],{}," to the client's secret.",[625,1783,1784,1797,1808,1814],{},[628,1785,1786,1789,1790,1792,1793,890],{},[631,1787,1788],{},"The client."," A confidential application at the issuer. The tokens must be issued to it, or its client ID must be in the\ntokens' audience. Add it to the issuer's ",[612,1791,1407],{},". See\n",[597,1794,1796],{"href":1795},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Froutes-and-clients#token-introspection","Token introspection",[628,1798,1799,1802,1803,1805,1806,890],{},[631,1800,1801],{},"Inactive means refused."," A token the issuer calls inactive, or one whose ",[612,1804,1305],{}," differs from the token's, answers ",[612,1807,1119],{},[628,1809,1810,1813],{},[631,1811,1812],{},"Fail closed."," When the issuer cannot be reached, the token is refused and the failure is not cached. The next request\nasks again.",[628,1815,1816,1819,1820,1822],{},[631,1817,1818],{},"Cached briefly."," Answers are kept per token for ",[612,1821,1677],{}," seconds, at most until the token expires.",[710,1824,191],{"id":1825},"users",[593,1827,1828,1829,1831],{},"The user is the token's ",[612,1830,1305],{},", the user ID at the issuer.",[625,1833,1834,1845],{},[628,1835,1836,1840,1841,1844],{},[631,1837,1667,1838],{},[612,1839,1289],{},", the local user has the same ID, so tokens and sign-ins find the same user. This is recommended.\nIt is the default when ",[597,1842,1843],{"href":87},"Sign in with frigear.nu"," uses it.",[628,1846,1847,1850],{},[631,1848,1849],{},"Without it",", users are matched by email. The issuer's ID is remembered per server instance.",[593,1852,1853,1854,1857],{},"Either way an existing user is matched by email only when userinfo has ",[612,1855,1856],{},"email_verified: true",", and a local user\nholding the ID under another email is never taken over; the token is refused instead.",[593,1859,1860],{},"A user the service has not seen is created the first time a token arrives. Known users are not written to on\nrequests.",[884,1862,1863,1864,1867,1868,687,1870,687,1873,1876,1877,1879],{},"The app must ask for the ",[612,1865,1866],{},"email"," scope. The module reads the profile (",[612,1869,1866],{},[612,1871,1872],{},"name",[612,1874,1875],{},"picture",") from the issuer's\nuserinfo endpoint for a user it has not seen, and without ",[612,1878,1289],{}," once per server instance. A profile without an\nemail gets the token refused.",[710,1881,1883],{"id":1882},"cors","CORS",[593,1885,1886,1887,1889,1890,1892],{},"A webview of the mobile app runs on ",[612,1888,1258],{}," on iOS or ",[612,1891,1261],{}," on Android. So its calls to\nthe service are cross-origin.",[593,1894,1895,1896,1898,1899,1902,1903,1909,1910,890],{},"The module answers preflights and adds the CORS headers for the configured origins on ",[612,1897,1271],{},". It never sends\n",[612,1900,1901],{},"Access-Control-Allow-Credentials",", so a cross-origin call is always a bearer call. The headers come from the shared\n",[597,1904,1906],{"href":1905},"\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fserver-toolkit#bearer-cors",[612,1907,1908],{},"applyBearerCors",", the same helper behind OAuth Server's\n",[597,1911,1913],{"href":1912},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Froutes-and-clients#calling-from-a-browser-on-another-origin",[612,1914,1915],{},"cors.paths",[620,1917,1919],{"id":1918},"next-steps","Next steps",[625,1921,1922,1929,1937,1942,1952],{},[628,1923,1924,1928],{},[597,1925,1927],{"href":1926},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver#one-app-for-every-service","One app for every service"," – set up the issuer\nside: the shared audience and the scopes.",[628,1930,1931,1933,1934,1936],{},[597,1932,199],{"href":200}," – permissions, roles and the ",[612,1935,1654],{}," hook.",[628,1938,1939,1941],{},[597,1940,457],{"href":458}," – run a mobile app that signs in and calls a service.",[628,1943,1944,1948,1949,890],{},[597,1945,1947],{"href":1946},"\u002Fguide\u002Fconcepts\u002Fauth-and-scopes#the-principal","Auth and scopes"," – see what your routes get from ",[612,1950,1951],{},"authorize",[628,1953,1954,1956],{},[597,1955,482],{"href":483}," – require a signed-in user and a permission in a route.",[1958,1959,1960],"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 .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 .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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":720,"searchDepth":789,"depth":789,"links":1962},[1963,1964,1971,1974,1981],{"id":622,"depth":789,"text":623},{"id":704,"depth":789,"text":705,"children":1965},[1966,1967,1969,1970],{"id":712,"depth":825,"text":713},{"id":744,"depth":825,"text":1968},"Configure it in nuxt.config.ts",{"id":893,"depth":825,"text":894},{"id":984,"depth":825,"text":985},{"id":1126,"depth":789,"text":1127,"children":1972},[1973],{"id":1448,"depth":825,"text":1449},{"id":1488,"depth":789,"text":1489,"children":1975},[1976,1977,1978,1979,1980],{"id":1495,"depth":825,"text":1496},{"id":1587,"depth":825,"text":1588},{"id":1434,"depth":825,"text":1438},{"id":1825,"depth":825,"text":191},{"id":1882,"depth":825,"text":1883},{"id":1918,"depth":789,"text":1919},"Let a separate service accept the access tokens of an issuer, so one mobile app signs in once and calls every service.","md",null,{},true,{"title":241,"description":1982},"J5l4-nz2rP23Zdn94dlSrFu8kD1MwS1bFD9Eqk0zuq4",[1990,1992],{"title":179,"path":238,"stem":239,"description":1991,"children":-1},"Every route OAuth Server registers, how a sign-in runs, and how public and configured applications behave.",{"title":250,"path":251,"stem":252,"description":1993,"children":-1},"Gives your app one verified Stripe webhook and a Stripe customer per user, for Payments and Subscriptions.",1794503148161]