[{"data":1,"prerenderedAt":2141},["ShallowReactive",2],{"navigation_docs":3,"-modules-identity-auth-oauth-providers":587,"-modules-identity-auth-oauth-providers-surround":2136},[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":187,"body":589,"description":2129,"extension":2130,"links":2131,"meta":2132,"navigation":2133,"path":188,"seo":2134,"stem":189,"__hash__":2135},"docs\u002F3.modules\u002F1.identity\u002F1.auth\u002F5.oauth-providers.md",{"type":590,"value":591,"toc":2122},"minimark",[592,604,609,628,636,728,739,746,972,1046,1050,1060,1092,1104,1244,1258,1261,1274,1298,1540,1550,1567,1584,1593,1604,1607,1703,1759,1861,1868,1892,1902,2062,2065,2068,2096,2100,2118],[593,594,595,596,600,601,603],"p",{},"This page shows how to turn on sign-in through an ",[597,598,599],"a",{"href":87},"OAuth provider",", including Sign in with\nfrigear.nu, and how to keep the provider's tokens. For app developers; server mode only. Set up the module first:\n",[597,602,173],{"href":169},".",[605,606,608],"h2",{"id":607},"turn-on-a-provider","Turn on a provider",[593,610,611,612,618,619,623,624,627],{},"Every ",[597,613,617],{"href":614,"rel":615},"https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils#supported-oauth-providers",[616],"nofollow","nuxt-auth-utils provider"," signs in at\n",[620,621,622],"code",{},"GET \u002Fapi\u002Fauth\u002F\u003Cprovider>",". That covers GitHub, Google, Auth0, Okta, Keycloak and Microsoft, among others. ",[620,625,626],{},"oidc"," covers\nany other OpenID Connect provider.",[593,629,630,631,635],{},"A provider is off by default. It turns on, ",[632,633,634],"strong",{},"per request",", once nuxt-auth-utils has its credentials. These are the\nenvironment variables nuxt-auth-utils documents for the provider:",[637,638,643],"pre",{"className":639,"code":640,"language":641,"meta":642,"style":642},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","NUXT_OAUTH_AUTH0_CLIENT_ID=...\nNUXT_OAUTH_AUTH0_CLIENT_SECRET=...\nNUXT_OAUTH_AUTH0_DOMAIN=example.eu.auth0.com\n\n# Any OpenID Connect provider\nNUXT_OAUTH_OIDC_CLIENT_ID=...\nNUXT_OAUTH_OIDC_CLIENT_SECRET=...\nNUXT_OAUTH_OIDC_OPENID_CONFIG=https:\u002F\u002Fsso.example.com\u002F.well-known\u002Fopenid-configuration\n","bash","",[620,644,645,662,672,683,690,697,707,717],{"__ignoreMap":642},[646,647,650,654,658],"span",{"class":648,"line":649},"line",1,[646,651,653],{"class":652},"sTEyZ","NUXT_OAUTH_AUTH0_CLIENT_ID",[646,655,657],{"class":656},"sMK4o","=",[646,659,661],{"class":660},"sfazB","...\n",[646,663,665,668,670],{"class":648,"line":664},2,[646,666,667],{"class":652},"NUXT_OAUTH_AUTH0_CLIENT_SECRET",[646,669,657],{"class":656},[646,671,661],{"class":660},[646,673,675,678,680],{"class":648,"line":674},3,[646,676,677],{"class":652},"NUXT_OAUTH_AUTH0_DOMAIN",[646,679,657],{"class":656},[646,681,682],{"class":660},"example.eu.auth0.com\n",[646,684,686],{"class":648,"line":685},4,[646,687,689],{"emptyLinePlaceholder":688},true,"\n",[646,691,693],{"class":648,"line":692},5,[646,694,696],{"class":695},"sHwdD","# Any OpenID Connect provider\n",[646,698,700,703,705],{"class":648,"line":699},6,[646,701,702],{"class":652},"NUXT_OAUTH_OIDC_CLIENT_ID",[646,704,657],{"class":656},[646,706,661],{"class":660},[646,708,710,713,715],{"class":648,"line":709},7,[646,711,712],{"class":652},"NUXT_OAUTH_OIDC_CLIENT_SECRET",[646,714,657],{"class":656},[646,716,661],{"class":660},[646,718,720,723,725],{"class":648,"line":719},8,[646,721,722],{"class":652},"NUXT_OAUTH_OIDC_OPENID_CONFIG",[646,724,657],{"class":656},[646,726,727],{"class":660},"https:\u002F\u002Fsso.example.com\u002F.well-known\u002Fopenid-configuration\n",[593,729,730,731,734,735,738],{},"Without credentials the route answers ",[620,732,733],{},"404"," and the sign-in page shows no button. Register\n",[620,736,737],{},"\u003Corigin>\u002Fapi\u002Fauth\u002F\u003Cprovider>"," as the callback URL at the provider.",[593,740,741,742,745],{},"List a provider in ",[620,743,744],{},"settings.oauth"," only to change it:",[637,747,751],{"className":748,"code":749,"language":750,"meta":642,"style":642},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","authentication: {\n  settings: {\n    oauth: {\n      auth0: {\n        label: 'Company SSO', \u002F\u002F button label (default: the provider's name, translatable as `auth.oauth.\u003Cprovider>.label`)\n        icon: 'i-simple-icons-auth0', \u002F\u002F iconify name\n        signUp: false, \u002F\u002F only sign in existing users; unknown emails go back to the sign-in page\n        config: { scope: ['openid', 'profile', 'email'] }, \u002F\u002F passed to the nuxt-auth-utils handler\n        keepTokens: true, \u002F\u002F keep its access and refresh tokens, see \"Keeping provider tokens\"\n        trustEmail: false, \u002F\u002F `true` trusts emails the provider does not flag as verified (see below)\n      },\n      github: false, \u002F\u002F off, even with credentials\n    },\n  },\n}\n","ts",[620,752,753,765,774,783,792,814,833,849,902,918,933,939,954,960,966],{"__ignoreMap":642},[646,754,755,759,762],{"class":648,"line":649},[646,756,758],{"class":757},"sBMFI","authentication",[646,760,761],{"class":656},":",[646,763,764],{"class":656}," {\n",[646,766,767,770,772],{"class":648,"line":664},[646,768,769],{"class":757},"  settings",[646,771,761],{"class":656},[646,773,764],{"class":656},[646,775,776,779,781],{"class":648,"line":674},[646,777,778],{"class":757},"    oauth",[646,780,761],{"class":656},[646,782,764],{"class":656},[646,784,785,788,790],{"class":648,"line":685},[646,786,787],{"class":757},"      auth0",[646,789,761],{"class":656},[646,791,764],{"class":656},[646,793,794,797,799,802,805,808,811],{"class":648,"line":692},[646,795,796],{"class":757},"        label",[646,798,761],{"class":656},[646,800,801],{"class":656}," '",[646,803,804],{"class":660},"Company SSO",[646,806,807],{"class":656},"'",[646,809,810],{"class":656},",",[646,812,813],{"class":695}," \u002F\u002F button label (default: the provider's name, translatable as `auth.oauth.\u003Cprovider>.label`)\n",[646,815,816,819,821,823,826,828,830],{"class":648,"line":699},[646,817,818],{"class":757},"        icon",[646,820,761],{"class":656},[646,822,801],{"class":656},[646,824,825],{"class":660},"i-simple-icons-auth0",[646,827,807],{"class":656},[646,829,810],{"class":656},[646,831,832],{"class":695}," \u002F\u002F iconify name\n",[646,834,835,838,840,844,846],{"class":648,"line":709},[646,836,837],{"class":757},"        signUp",[646,839,761],{"class":656},[646,841,843],{"class":842},"sfNiH"," false",[646,845,810],{"class":656},[646,847,848],{"class":695}," \u002F\u002F only sign in existing users; unknown emails go back to the sign-in page\n",[646,850,851,854,856,859,862,864,868,870,873,875,877,879,882,884,886,888,891,893,896,899],{"class":648,"line":719},[646,852,853],{"class":757},"        config",[646,855,761],{"class":656},[646,857,858],{"class":656}," {",[646,860,861],{"class":757}," scope",[646,863,761],{"class":656},[646,865,867],{"class":866},"swJcz"," [",[646,869,807],{"class":656},[646,871,872],{"class":660},"openid",[646,874,807],{"class":656},[646,876,810],{"class":656},[646,878,801],{"class":656},[646,880,881],{"class":660},"profile",[646,883,807],{"class":656},[646,885,810],{"class":656},[646,887,801],{"class":656},[646,889,890],{"class":660},"email",[646,892,807],{"class":656},[646,894,895],{"class":866},"] ",[646,897,898],{"class":656},"},",[646,900,901],{"class":695}," \u002F\u002F passed to the nuxt-auth-utils handler\n",[646,903,905,908,910,913,915],{"class":648,"line":904},9,[646,906,907],{"class":757},"        keepTokens",[646,909,761],{"class":656},[646,911,912],{"class":842}," true",[646,914,810],{"class":656},[646,916,917],{"class":695}," \u002F\u002F keep its access and refresh tokens, see \"Keeping provider tokens\"\n",[646,919,921,924,926,928,930],{"class":648,"line":920},10,[646,922,923],{"class":757},"        trustEmail",[646,925,761],{"class":656},[646,927,843],{"class":842},[646,929,810],{"class":656},[646,931,932],{"class":695}," \u002F\u002F `true` trusts emails the provider does not flag as verified (see below)\n",[646,934,936],{"class":648,"line":935},11,[646,937,938],{"class":656},"      },\n",[646,940,942,945,947,949,951],{"class":648,"line":941},12,[646,943,944],{"class":757},"      github",[646,946,761],{"class":656},[646,948,843],{"class":842},[646,950,810],{"class":656},[646,952,953],{"class":695}," \u002F\u002F off, even with credentials\n",[646,955,957],{"class":648,"line":956},13,[646,958,959],{"class":656},"    },\n",[646,961,963],{"class":648,"line":962},14,[646,964,965],{"class":656},"  },\n",[646,967,969],{"class":648,"line":968},15,[646,970,971],{"class":656},"}\n",[973,974,975,991,997,1007,1010,1032],"ul",{},[976,977,978,979,982,983,986,987,990],"li",{},"Users are matched by email, and only when the provider says the address is verified: ",[620,980,981],{},"email_verified: true"," (OIDC\nproviders, Google, Auth0, Okta, ...), ",[620,984,985],{},"verified: true"," (Discord), or a GitHub address (GitHub only shows verified\nones). Anything else, including a provider that sends no flag (Facebook, Microsoft, Spotify, ...), is sent back to\nthe sign-in page with ",[620,988,989],{},"?error=oauth-unverified",": nothing is linked or created.",[976,992,993,996],{},[620,994,995],{},"trustEmail: true"," on a provider accepts its emails without a flag. Set it only for a provider that never hands out\nan address its user does not own, since a sign-in then opens the local account with that email.",[976,998,999,1000,1003,1004,603],{},"An unknown email gets a new account, marked verified, unless ",[620,1001,1002],{},"signUp"," is ",[620,1005,1006],{},"false",[976,1008,1009],{},"A local account that was never verified (someone registered the address with a password or a passkey and never\nfollowed the mail) loses that password, its passkeys, API tokens and kept tokens at the first verified sign-in.\nThe same happens on a sign-in link (magic link) to it. Its own verification mail keeps them.",[976,1011,1012,1013,1016,1017,1020,1021,1025,1026,1028,1029,603],{},"With ",[620,1014,1015],{},"oauth.frigear"," on, the ",[620,1018,1019],{},"NUXT_OAUTH_OIDC_*"," credentials belong to\n",[597,1022,1024],{"href":1023},"#sign-in-with-frigearnu","Sign in with frigear.nu",". The plain ",[620,1027,626],{}," provider then needs listing too, for example\n",[620,1030,1031],{},"oidc: { label: 'Company SSO' }",[976,1033,1034,1037,1038,1041,1042,1045],{},[620,1035,1036],{},"GET \u002Fapi\u002Fauth\u002Fproviders"," returns the buttons for the request: ",[620,1039,1040],{},"[{ key, to, label?, icon?, external? }]",".\n",[620,1043,1044],{},"useLogin().oauthProviders"," fills in the default labels and icons.",[605,1047,1049],{"id":1048},"keeping-provider-tokens","Keeping provider tokens",[593,1051,1052,1053,1056,1057,1059],{},"By default a sign-in keeps none of the provider's tokens. Set ",[620,1054,1055],{},"keepTokens: true"," on a provider (",[620,1058,1015],{}," too) to\nkeep them. Your app can then call the provider as the user, also from a scheduled task or a bearer request, where there\nis no session.",[973,1061,1062,1065,1076,1079,1082],{},[976,1063,1064],{},"Auth keeps the access token, the refresh token, the expiry and the granted scopes.",[976,1066,1067,1068,1071,1072,1075],{},"They live in the ",[620,1069,1070],{},"auth_oauth_tokens"," table, one row per user and provider. ",[620,1073,1074],{},"keepTokens"," on any provider adds that\ntable, so generate a migration for it.",[976,1077,1078],{},"Every sign-in with the provider replaces the row. A step-up sign-in replaces the plain one.",[976,1080,1081],{},"The token columns are encrypted with AES-GCM (WebCrypto) and bound to the provider.",[976,1083,1084,1087,1088,1091],{},[620,1085,1086],{},"deleteUserAuthData"," removes them with the user. A re-keyed user (",[620,1089,1090],{},"forceId",") keeps them.",[1093,1094,1095,1096,1099,1100,1103],"caution",{},"The encryption key is derived from ",[620,1097,1098],{},"NUXT_AUTH_TOKENS_KEY"," (",[620,1101,1102],{},"runtimeConfig.authTokensKey","), else from the session\npassword. Changing it makes the kept tokens unreadable, and users have to sign in again.",[637,1105,1107],{"className":748,"code":1106,"language":750,"meta":642,"style":642},"\u002F\u002F The caller's tokens (event), or a user's by id (a task syncing with an admin's token). `null` when none are kept.\nconst tokens = await getOAuthTokens(event, 'frigear') \u002F\u002F { accessToken, refreshToken?, tokenType?, expiresAt, scopes }\n\n\u002F\u002F A usable access token: refreshed first when it expired, `null` when there is none, it lacks one of `scopes`,\n\u002F\u002F or it cannot be refreshed.\nconst token = await getOAuthAccessToken(adminId, 'frigear', { scopes: ['users:read'] })\n\nawait revokeOAuthTokens(userId, 'frigear') \u002F\u002F forget them (\"disconnect\")\n",[620,1108,1109,1114,1151,1155,1160,1165,1216,1220],{"__ignoreMap":642},[646,1110,1111],{"class":648,"line":649},[646,1112,1113],{"class":695},"\u002F\u002F The caller's tokens (event), or a user's by id (a task syncing with an admin's token). `null` when none are kept.\n",[646,1115,1116,1120,1123,1125,1129,1133,1136,1138,1140,1143,1145,1148],{"class":648,"line":664},[646,1117,1119],{"class":1118},"spNyl","const",[646,1121,1122],{"class":652}," tokens ",[646,1124,657],{"class":656},[646,1126,1128],{"class":1127},"s7zQu"," await",[646,1130,1132],{"class":1131},"s2Zo4"," getOAuthTokens",[646,1134,1135],{"class":652},"(event",[646,1137,810],{"class":656},[646,1139,801],{"class":656},[646,1141,1142],{"class":660},"frigear",[646,1144,807],{"class":656},[646,1146,1147],{"class":652},") ",[646,1149,1150],{"class":695},"\u002F\u002F { accessToken, refreshToken?, tokenType?, expiresAt, scopes }\n",[646,1152,1153],{"class":648,"line":674},[646,1154,689],{"emptyLinePlaceholder":688},[646,1156,1157],{"class":648,"line":685},[646,1158,1159],{"class":695},"\u002F\u002F A usable access token: refreshed first when it expired, `null` when there is none, it lacks one of `scopes`,\n",[646,1161,1162],{"class":648,"line":692},[646,1163,1164],{"class":695},"\u002F\u002F or it cannot be refreshed.\n",[646,1166,1167,1169,1172,1174,1176,1179,1182,1184,1186,1188,1190,1192,1194,1197,1199,1201,1203,1206,1208,1210,1213],{"class":648,"line":699},[646,1168,1119],{"class":1118},[646,1170,1171],{"class":652}," token ",[646,1173,657],{"class":656},[646,1175,1128],{"class":1127},[646,1177,1178],{"class":1131}," getOAuthAccessToken",[646,1180,1181],{"class":652},"(adminId",[646,1183,810],{"class":656},[646,1185,801],{"class":656},[646,1187,1142],{"class":660},[646,1189,807],{"class":656},[646,1191,810],{"class":656},[646,1193,858],{"class":656},[646,1195,1196],{"class":866}," scopes",[646,1198,761],{"class":656},[646,1200,867],{"class":652},[646,1202,807],{"class":656},[646,1204,1205],{"class":660},"users:read",[646,1207,807],{"class":656},[646,1209,895],{"class":652},[646,1211,1212],{"class":656},"}",[646,1214,1215],{"class":652},")\n",[646,1217,1218],{"class":648,"line":709},[646,1219,689],{"emptyLinePlaceholder":688},[646,1221,1222,1225,1228,1231,1233,1235,1237,1239,1241],{"class":648,"line":719},[646,1223,1224],{"class":1127},"await",[646,1226,1227],{"class":1131}," revokeOAuthTokens",[646,1229,1230],{"class":652},"(userId",[646,1232,810],{"class":656},[646,1234,801],{"class":656},[646,1236,1142],{"class":660},[646,1238,807],{"class":656},[646,1240,1147],{"class":652},[646,1242,1243],{"class":695},"\u002F\u002F forget them (\"disconnect\")\n",[593,1245,1246,1247,1250,1251,1253,1254,1257],{},"An expired token is refreshed with its refresh token at the provider's token endpoint. Auth finds that endpoint through\nOIDC discovery: frigear.nu's, or the ",[620,1248,1249],{},"openidConfig"," of an ",[620,1252,626],{},"-style provider\n(",[620,1255,1256],{},"NUXT_OAUTH_\u003CPROVIDER>_OPENID_CONFIG","). Other providers' tokens are used until they expire. One refresh runs at a time\nper user and provider, because providers rotate refresh tokens and treat a replayed one as theft.",[605,1259,1024],{"id":1260},"sign-in-with-frigearnu",[593,1262,1263,1265,1266,1269,1270,1273],{},[597,1264,1024],{"href":87}," lets people sign in with their frigear.nu account on pull request\npreviews and in local development. Turn it on with ",[620,1267,1268],{},"settings.oauth.frigear",". It registers ",[620,1271,1272],{},"GET \u002Fauth\u002Ffrigear",", an OIDC\nsign-in against frigear.nu.",[593,1275,1276,1277,1279,1280,1003,1283,1286,1287,1290,1291,1293,1294,1297],{},"It is decided ",[632,1278,634],{},". It is active when ",[620,1281,1282],{},"runtimeConfig.previewDeployment",[620,1284,1285],{},"true",", or when\n",[620,1288,1289],{},"runtimeConfig.frigear.sso"," has a client id and secret. Otherwise the route answers ",[620,1292,733],{}," and the button is hidden.\nRegister ",[620,1295,1296],{},"\u003Corigin>\u002Fauth\u002Ffrigear"," as the callback on frigear.nu.",[1299,1300,1301,1317],"table",{},[1302,1303,1304],"thead",{},[1305,1306,1307,1311,1314],"tr",{},[1308,1309,1310],"th",{},"Runtime config",[1308,1312,1313],{},"Env var",[1308,1315,1316],{},"Purpose",[1318,1319,1320,1336,1355,1374,1389,1407,1426,1447,1466,1484,1507,1525],"tbody",{},[1305,1321,1322,1328,1333],{},[1323,1324,1325],"td",{},[620,1326,1327],{},"previewDeployment",[1323,1329,1330],{},[620,1331,1332],{},"NUXT_PREVIEW_DEPLOYMENT",[1323,1334,1335],{},"Turns the sign-in on without credentials. Set it yourself, for example from a CI marker.",[1305,1337,1338,1343,1348],{},[1323,1339,1340],{},[620,1341,1342],{},"frigear.sso.url",[1323,1344,1345],{},[620,1346,1347],{},"NUXT_FRIGEAR_SSO_URL",[1323,1349,1350,1351,1354],{},"The ",[597,1352,1353],{"href":87},"issuer"," origin. Defaults to the frigear.nu origin.",[1305,1356,1357,1362,1367],{},[1323,1358,1359],{},[620,1360,1361],{},"frigear.sso.clientId",[1323,1363,1364],{},[620,1365,1366],{},"NUXT_FRIGEAR_SSO_CLIENT_ID",[1323,1368,1369,1370,1373],{},"The id of your ",[597,1371,1372],{"href":87},"OAuth application"," at frigear.nu.",[1305,1375,1376,1381,1386],{},[1323,1377,1378],{},[620,1379,1380],{},"frigear.sso.clientSecret",[1323,1382,1383],{},[620,1384,1385],{},"NUXT_FRIGEAR_SSO_CLIENT_SECRET",[1323,1387,1388],{},"Its client secret.",[1305,1390,1391,1396,1401],{},[1323,1392,1393],{},[620,1394,1395],{},"frigear.sso.scopes",[1323,1397,1398],{},[620,1399,1400],{},"NUXT_FRIGEAR_SSO_SCOPES",[1323,1402,1403,1404,603],{},"Scopes every sign-in asks for, space separated. Default ",[620,1405,1406],{},"openid profile email",[1305,1408,1409,1414,1419],{},[1323,1410,1411],{},[620,1412,1413],{},"frigear.sso.stepUpScopes",[1323,1415,1416],{},[620,1417,1418],{},"NUXT_FRIGEAR_SSO_STEP_UP_SCOPES",[1323,1420,1421,1422,1425],{},"Extra scopes a sign-in may ask for with ",[620,1423,1424],{},"\u002Fauth\u002Ffrigear?scope=...",". Default none.",[1305,1427,1428,1433,1438],{},[1323,1429,1430],{},[620,1431,1432],{},"frigear.forceId",[1323,1434,1435],{},[620,1436,1437],{},"NUXT_FRIGEAR_FORCE_ID",[1323,1439,1440,1441,1444,1445,603],{},"Make the local ",[620,1442,1443],{},"users.id"," equal the frigear.nu id. Default ",[620,1446,1006],{},[1305,1448,1449,1454,1459],{},[1323,1450,1451],{},[620,1452,1453],{},"frigear.roleMap",[1323,1455,1456],{},[620,1457,1458],{},"NUXT_FRIGEAR_ROLE_MAP",[1323,1460,1461,1462,1465],{},"frigear.nu role to local role, as an object (JSON in the env var). Default ",[620,1463,1464],{},"{}",": no role is synced.",[1305,1467,1468,1473,1478],{},[1323,1469,1470],{},[620,1471,1472],{},"frigear.defaultRole",[1323,1474,1475],{},[620,1476,1477],{},"NUXT_FRIGEAR_DEFAULT_ROLE",[1323,1479,1480,1481,603],{},"The local role for a missing or unmapped role claim. Default ",[620,1482,1483],{},"user",[1305,1485,1486,1491,1494],{},[1323,1487,1488],{},[620,1489,1490],{},"frigear.syncRole",[1323,1492,1493],{},"–",[1323,1495,1496,1497,1499,1500,1503,1504,1506],{},"Set ",[620,1498,1006],{}," to stop syncing roles while keeping ",[620,1501,1502],{},"roleMap",". On whenever ",[620,1505,1502],{}," maps a role.",[1305,1508,1509,1514,1519],{},[1323,1510,1511],{},[620,1512,1513],{},"frigear.keepTokens",[1323,1515,1516],{},[620,1517,1518],{},"NUXT_FRIGEAR_KEEP_TOKENS",[1323,1520,1521,1522,603],{},"Keep the frigear.nu tokens. Defaults to ",[620,1523,1524],{},"oauth.frigear.keepTokens",[1305,1526,1527,1532,1537],{},[1323,1528,1529],{},[620,1530,1531],{},"public.authentication.frigearSso",[1323,1533,1534],{},[620,1535,1536],{},"NUXT_PUBLIC_AUTHENTICATION_FRIGEAR_SSO",[1323,1538,1539],{},"Whether the build saw the credentials.",[593,1541,1542,1546,1547,1549],{},[632,1543,1544,603],{},[620,1545,1395],{}," ",[620,1548,872],{}," is always added.",[593,1551,1552,1556,1557,1560,1561,1563,1564,1566],{},[632,1553,1554,603],{},[620,1555,1490],{}," Declare it in your ",[620,1558,1559],{},"runtimeConfig"," to override it. Without a ",[620,1562,1502],{}," it does nothing (the\nbuild prints a warning when it is ",[620,1565,1285],{},").",[593,1568,1569,1573,1574,1577,1578,1580,1581,1583],{},[632,1570,1571,603],{},[620,1572,1513],{}," See ",[597,1575,1049],{"href":1576},"#keeping-provider-tokens",". ",[620,1579,1524],{}," also adds\nthe table, so turning ",[620,1582,1513],{}," on at runtime only works with the table.",[593,1585,1586,1590,1591,603],{},[632,1587,1588,603],{},[620,1589,1531],{}," The button follows the route, through ",[620,1592,1036],{},[593,1594,1595,1596,1598,1599,1601,1602,603],{},"The credentials can also come from nuxt-auth-utils' ",[620,1597,702],{},", ",[620,1600,712],{}," and\n",[620,1603,722],{},[593,1605,1606],{},"What happens at sign-in:",[973,1608,1609,1623,1637,1646,1663,1666,1675,1681],{},[976,1610,1611,1612,1615,1616,1619,1620,603],{},"A callback without a ",[620,1613,1614],{},"state"," that matches the browser's state cookie is sent back to the sign-in page with\n",[620,1617,1618],{},"?error=oauth-state",". The same holds for every provider at ",[620,1621,1622],{},"\u002Fapi\u002Fauth\u002F\u003Cprovider>",[976,1624,1625,1626,1629,1630,1633,1634,603],{},"The token response must carry an ",[620,1627,1628],{},"id_token",", and userinfo must be about the same ",[620,1631,1632],{},"sub","; otherwise the sign-in fails\nwith ",[620,1635,1636],{},"401",[976,1638,1350,1639,1641,1642,1645],{},[620,1640,1628],{}," is verified against the provider's ",[620,1643,1644],{},"jwks_uri",", including issuer and audience. The keys are cached for\nan hour.",[976,1647,1648,1649,1651,1652,1655,1656,1659,1660,1662],{},"Users are matched by email only with ",[620,1650,981],{}," (a ",[620,1653,1654],{},"@frigear-nu\u002Foauth-server"," issuer sends it from\n",[620,1657,1658],{},"emailVerifiedAt","). An unverified address that an account here already has is sent back to the sign-in page with\n",[620,1661,989],{},". A free address gets a new account, verified only with the claim.",[976,1664,1665],{},"A matched account that was never verified loses the credentials set before (password, passkeys, API tokens, kept\ntokens) and is marked verified.",[976,1667,1012,1668,1670,1671,1674],{},[620,1669,1090],{},", Auth makes the local id match in one of three ways. It updates the user with that id and email,\nre-keys an existing user with the same (verified) email, or creates a user with that id. A local user that holds the\nid under another email is never taken over: the sign-in fails with ",[620,1672,1673],{},"409",", and an admin has to sort the two out.",[976,1676,1677,1678,1680],{},"On a re-key, rows that reference ",[620,1679,1443],{}," follow. A different user that holds the id moves to the next free id.",[976,1682,1683,1684,1687,1688,1598,1691,1694,1695,1698,1699,1702],{},"The optional ",[620,1685,1686],{},"color_mode"," claim (",[620,1689,1690],{},"light",[620,1692,1693],{},"dark"," or ",[620,1696,1697],{},"system",") is kept on the session as ",[620,1700,1701],{},"frigear.colorMode",". It is\napplied once per sign-in.",[593,1704,1705,1708,1709,1711,1712,1715,1716,1718,1719,1722,1723,1726,1727,1730,1731,1734,1735,1737,1738,1741,1742,1745,1746,1748,1749,1752,1753,1756,1757,603],{},[632,1706,1707],{},"The role claim."," With a ",[620,1710,1502],{},", Auth sets the user's single sign-on roles at ",[632,1713,1714],{},"every sign-in",". It reads the role claims of the\nverified ",[620,1717,1628],{}," only, never userinfo: the ",[620,1720,1721],{},"role"," claim, the namespaced ",[620,1724,1725],{},"https:\u002F\u002Ffrigear.nu\u002Fclaims\u002Frole",", then the\nentries of ",[620,1728,1729],{},"roles",". Every claimed role the map lists becomes a local role. A role the map does not list, or no claim\nat all, gives ",[620,1732,1733],{},"defaultRole"," (nothing when it is ",[620,1736,1483],{},", which every user holds anyway). The roles are written through\nthe role provider with ",[620,1739,1740],{},"source = 'sso'",": they replace the user's earlier ",[620,1743,1744],{},"sso"," rows, so a role removed at frigear.nu is\nremoved here at the next sign-in, and roles an admin assigned by hand are never touched. Without a ",[620,1747,1502],{}," no role is\nsynced. It needs ",[597,1750,199],{"href":1751},"\u002Fmodules\u002Fidentity\u002Fauthorization\u002Froles-and-requirements#assignment-and-its-rules"," for\nthe table that stores them; without it nothing is stored. These claim names follow the current frigear.nu and will change when\nthis app becomes frigear.nu. On the issuer side, the host app adds the claim in the ",[620,1754,1755],{},"oauth:claims"," hook of\n",[597,1758,225],{"href":226},[637,1760,1763],{"className":748,"code":1761,"filename":1762,"language":750,"meta":642,"style":642},"export default defineNuxtConfig({\n  runtimeConfig: {\n    frigear: {\n      \u002F\u002F Only these frigear.nu roles carry over; every other one, or none, becomes `user`.\n      roleMap: { admin: 'admin', 'frigear-staff': 'manager' },\n    },\n  },\n})\n","nuxt.config.ts",[620,1764,1765,1782,1791,1800,1805,1847,1851,1855],{"__ignoreMap":642},[646,1766,1767,1770,1773,1776,1779],{"class":648,"line":649},[646,1768,1769],{"class":1127},"export",[646,1771,1772],{"class":1127}," default",[646,1774,1775],{"class":1131}," defineNuxtConfig",[646,1777,1778],{"class":652},"(",[646,1780,1781],{"class":656},"{\n",[646,1783,1784,1787,1789],{"class":648,"line":664},[646,1785,1786],{"class":866},"  runtimeConfig",[646,1788,761],{"class":656},[646,1790,764],{"class":656},[646,1792,1793,1796,1798],{"class":648,"line":674},[646,1794,1795],{"class":866},"    frigear",[646,1797,761],{"class":656},[646,1799,764],{"class":656},[646,1801,1802],{"class":648,"line":685},[646,1803,1804],{"class":695},"      \u002F\u002F Only these frigear.nu roles carry over; every other one, or none, becomes `user`.\n",[646,1806,1807,1810,1812,1814,1817,1819,1821,1824,1826,1828,1830,1833,1835,1837,1839,1842,1844],{"class":648,"line":692},[646,1808,1809],{"class":866},"      roleMap",[646,1811,761],{"class":656},[646,1813,858],{"class":656},[646,1815,1816],{"class":866}," admin",[646,1818,761],{"class":656},[646,1820,801],{"class":656},[646,1822,1823],{"class":660},"admin",[646,1825,807],{"class":656},[646,1827,810],{"class":656},[646,1829,801],{"class":656},[646,1831,1832],{"class":866},"frigear-staff",[646,1834,807],{"class":656},[646,1836,761],{"class":656},[646,1838,801],{"class":656},[646,1840,1841],{"class":660},"manager",[646,1843,807],{"class":656},[646,1845,1846],{"class":656}," },\n",[646,1848,1849],{"class":648,"line":699},[646,1850,959],{"class":656},[646,1852,1853],{"class":648,"line":709},[646,1854,965],{"class":656},[646,1856,1857,1859],{"class":648,"line":719},[646,1858,1212],{"class":656},[646,1860,1215],{"class":652},[1862,1863,1864,1865,1867],"warning",{},"Map only the roles you trust frigear.nu (or the issuer behind ",[620,1866,1019],{},") to grant here. A role the map lists\nis granted to anyone the issuer gives it to.",[593,1869,1870,1873,1874,1876,1877,1880,1881,1884,1885,1888,1889,1891],{},[632,1871,1872],{},"Step-up."," A sign-in asks for ",[620,1875,1395],{},". To ask for more only when needed, such as an admin connecting for\nsync, allow the scope in ",[620,1878,1879],{},"stepUpScopes",". Then send the user to ",[620,1882,1883],{},"\u002Fauth\u002Ffrigear?scope=users:read",", optionally with\n",[620,1886,1887],{},"&redirect=\u002Fadmin\u002Fsync",". Requested scopes outside the allowlist are ignored. With ",[620,1890,1074],{},", the step-up sign-in's\ntokens replace the ones kept before.",[1862,1893,1894,1895,1897,1898,1901],{},"frigear.nu decides what it grants. A scope for a permission the user does not hold, such as ",[620,1896,1205],{},", is dropped. Check the kept\n",[620,1899,1900],{},"scopes"," rather than assuming the step-up worked.",[637,1903,1905],{"className":748,"code":1904,"language":750,"meta":642,"style":642},"\u002F\u002F oauth: { frigear: { keepTokens: true } }, NUXT_FRIGEAR_SSO_STEP_UP_SCOPES=users:read\nconst token = await getOAuthAccessToken(event, 'frigear', { scopes: ['users:read'] })\nif (!token) throw apiError(403, 'frigear_not_connected', 'Connect frigear.nu for sync: \u002Fauth\u002Ffrigear?scope=users:read')\nconst users = await $fetch('https:\u002F\u002Ffrigear.nu\u002Fapi\u002Fadmin\u002Fusers', { headers: { Authorization: `Bearer ${token}` } })\n",[620,1906,1907,1912,1956,2001],{"__ignoreMap":642},[646,1908,1909],{"class":648,"line":649},[646,1910,1911],{"class":695},"\u002F\u002F oauth: { frigear: { keepTokens: true } }, NUXT_FRIGEAR_SSO_STEP_UP_SCOPES=users:read\n",[646,1913,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954],{"class":648,"line":664},[646,1915,1119],{"class":1118},[646,1917,1171],{"class":652},[646,1919,657],{"class":656},[646,1921,1128],{"class":1127},[646,1923,1178],{"class":1131},[646,1925,1135],{"class":652},[646,1927,810],{"class":656},[646,1929,801],{"class":656},[646,1931,1142],{"class":660},[646,1933,807],{"class":656},[646,1935,810],{"class":656},[646,1937,858],{"class":656},[646,1939,1196],{"class":866},[646,1941,761],{"class":656},[646,1943,867],{"class":652},[646,1945,807],{"class":656},[646,1947,1205],{"class":660},[646,1949,807],{"class":656},[646,1951,895],{"class":652},[646,1953,1212],{"class":656},[646,1955,1215],{"class":652},[646,1957,1958,1961,1963,1966,1969,1972,1975,1977,1981,1983,1985,1988,1990,1992,1994,1997,1999],{"class":648,"line":674},[646,1959,1960],{"class":1127},"if",[646,1962,1099],{"class":652},[646,1964,1965],{"class":656},"!",[646,1967,1968],{"class":652},"token) ",[646,1970,1971],{"class":1127},"throw",[646,1973,1974],{"class":1131}," apiError",[646,1976,1778],{"class":652},[646,1978,1980],{"class":1979},"sbssI","403",[646,1982,810],{"class":656},[646,1984,801],{"class":656},[646,1986,1987],{"class":660},"frigear_not_connected",[646,1989,807],{"class":656},[646,1991,810],{"class":656},[646,1993,801],{"class":656},[646,1995,1996],{"class":660},"Connect frigear.nu for sync: \u002Fauth\u002Ffrigear?scope=users:read",[646,1998,807],{"class":656},[646,2000,1215],{"class":652},[646,2002,2003,2005,2008,2010,2012,2015,2017,2019,2022,2024,2026,2028,2031,2033,2035,2038,2040,2043,2046,2049,2052,2055,2058,2060],{"class":648,"line":685},[646,2004,1119],{"class":1118},[646,2006,2007],{"class":652}," users ",[646,2009,657],{"class":656},[646,2011,1128],{"class":1127},[646,2013,2014],{"class":1131}," $fetch",[646,2016,1778],{"class":652},[646,2018,807],{"class":656},[646,2020,2021],{"class":660},"https:\u002F\u002Ffrigear.nu\u002Fapi\u002Fadmin\u002Fusers",[646,2023,807],{"class":656},[646,2025,810],{"class":656},[646,2027,858],{"class":656},[646,2029,2030],{"class":866}," headers",[646,2032,761],{"class":656},[646,2034,858],{"class":656},[646,2036,2037],{"class":866}," Authorization",[646,2039,761],{"class":656},[646,2041,2042],{"class":656}," `",[646,2044,2045],{"class":660},"Bearer ",[646,2047,2048],{"class":656},"${",[646,2050,2051],{"class":652},"token",[646,2053,2054],{"class":656},"}`",[646,2056,2057],{"class":656}," }",[646,2059,2057],{"class":656},[646,2061,1215],{"class":652},[605,2063,1310],{"id":2064},"runtime-config",[593,2066,2067],{},"Runtime config for kept provider tokens.",[1299,2069,2070,2080],{},[1302,2071,2072],{},[1305,2073,2074,2076,2078],{},[1308,2075,1310],{},[1308,2077,1313],{},[1308,2079,1316],{},[1318,2081,2082],{},[1305,2083,2084,2089,2093],{},[1323,2085,2086],{},[620,2087,2088],{},"authTokensKey",[1323,2090,2091],{},[620,2092,1098],{},[1323,2094,2095],{},"Encrypts kept provider tokens. Falls back to a key derived from the session password.",[605,2097,2099],{"id":2098},"next-steps","Next steps",[973,2101,2102,2112],{},[976,2103,2104,2108,2109,2111],{},[597,2105,2107],{"href":2106},"\u002Fmodules\u002Fidentity\u002Fauth\u002Freference#routes","Auth reference"," – the sign-in routes and the ",[620,2110,1070],{}," table.",[976,2113,2114,2117],{},[597,2115,81],{"href":2116},"\u002Fguide\u002Fconcepts\u002Fidentity#sign-in-with-frigearnu"," – where Sign in with frigear.nu fits among the apps.",[2119,2120,2121],"style",{},"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":642,"searchDepth":664,"depth":664,"links":2123},[2124,2125,2126,2127,2128],{"id":607,"depth":664,"text":608},{"id":1048,"depth":664,"text":1049},{"id":1260,"depth":664,"text":1024},{"id":2064,"depth":664,"text":1310},{"id":2098,"depth":664,"text":2099},"Let people sign in with GitHub, Google, frigear.nu or any OpenID Connect provider, and keep their tokens to call it.","md",null,{},{"title":187},{"title":187,"description":2129},"XIi5vx1dfWoLMrjM_kkOlDb6jF95HWoUFoi5Db6pPW8",[2137,2139],{"title":183,"path":184,"stem":185,"description":2138,"children":-1},"Sign people in from an app without its own server, such as a browser SPA or a Capacitor app, and call your API as them.",{"title":191,"path":192,"stem":193,"description":2140,"children":-1},"Stores your app's users, lets staff manage them and lets each user change their own name, email and password.",1794503151863]