[{"data":1,"prerenderedAt":4403},["ShallowReactive",2],{"navigation_docs":3,"-modules-identity-oauth-server-routes-and-clients":587,"-modules-identity-oauth-server-routes-and-clients-surround":4398},[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":589,"body":590,"description":4391,"extension":4392,"links":4393,"meta":4394,"navigation":4395,"path":238,"seo":4396,"stem":239,"__hash__":4397},"docs\u002F3.modules\u002F1.identity\u002F6.oauth-server\u002F4.routes-and-clients.md","OAuth Server routes and clients",{"type":591,"value":592,"toc":4365},"minimark",[593,601,606,609,636,641,644,836,860,896,928,948,952,955,1332,1376,1397,1413,1441,1452,1480,1484,1487,1497,1695,1699,1719,1792,2003,2074,2094,2097,2104,2119,2123,2184,2188,2204,2223,2227,2233,2248,2251,2275,2280,2286,2290,2308,2417,2503,2516,2520,2532,2723,3014,3033,3043,3053,3071,3075,3078,3085,3095,3099,3114,3118,3130,3156,3160,3163,3169,3172,3179,3200,3207,3248,3420,3429,3439,3458,3462,3468,3524,3650,3654,3666,3993,4008,4133,4153,4156,4164,4175,4267,4277,4281,4284,4290,4330,4334,4361],[594,595,596,597,600],"p",{},"This page lists every route OAuth Server registers and walks through one sign-in. It then covers how public OAuth\napplications and configured clients behave, and the tables and translations the module adds. It is for app developers\nand for anyone connecting an app to the issuer, in server mode only. Set up the module first:\n",[598,599,173],"a",{"href":226},".",[602,603,605],"h2",{"id":604},"routes","Routes",[594,607,608],{},"Routes the module registers, and who may call them.",[594,610,611,612,616,617,620,621,624,625,620,628,631,632,635],{},"Paths are typed in ",[613,614,615],"code",{},"@frigear-nu\u002Foauth-server\u002Fapi"," and ",[613,618,619],{},"@frigear-nu\u002Foauth-server\u002Futils",". Use ",[613,622,623],{},"oauthServerApi"," for the\nmodule API, such as ",[613,626,627],{},"oauthServerApi.admin.application(id)",[613,629,630],{},"oidc"," for the standard endpoints, such as ",[613,633,634],{},"oidc.token",".\nUse them instead of string literals.",[637,638,640],"h3",{"id":639},"oauth-and-openid-connect-endpoints","OAuth and OpenID Connect endpoints",[594,642,643],{},"These endpoints keep their standard paths and answer in the shape the specifications define.",[645,646,647,669],"table",{},[648,649,650],"thead",{},[651,652,653,657,660,663,666],"tr",{},[654,655,656],"th",{},"Method",[654,658,659],{},"Path",[654,661,662],{},"Who may call",[654,664,665],{},"Status",[654,667,668],{},"Purpose",[670,671,672,694,712,732,754,780,798,817],"tbody",{},[651,673,674,680,685,688,691],{},[675,676,677],"td",{},[613,678,679],{},"GET",[675,681,682],{},[613,683,684],{},"\u002F.well-known\u002Fopenid-configuration",[675,686,687],{},"anyone",[675,689,690],{},"200",[675,692,693],{},"The discovery document.",[651,695,696,700,705,707,709],{},[675,697,698],{},[613,699,679],{},[675,701,702],{},[613,703,704],{},"\u002F.well-known\u002Fjwks.json",[675,706,687],{},[675,708,690],{},[675,710,711],{},"The public signing keys.",[651,713,714,718,723,726,729],{},[675,715,716],{},[613,717,679],{},[675,719,720],{},[613,721,722],{},"\u002Foauth\u002Fauthorize",[675,724,725],{},"anyone; signed-out people go to the login page",[675,727,728],{},"302",[675,730,731],{},"Checks the authorization request, then redirects to the consent page.",[651,733,734,739,743,746,748],{},[675,735,736],{},[613,737,738],{},"POST",[675,740,741],{},[613,742,722],{},[675,744,745],{},"session only",[675,747,690],{},[675,749,750,751,600],{},"The consent decision. Answers ",[613,752,753],{},"{ redirectUrl }",[651,755,756,760,765,768,770],{},[675,757,758],{},[613,759,738],{},[675,761,762],{},[613,763,764],{},"\u002Foauth\u002Ftoken",[675,766,767],{},"the application's credentials",[675,769,690],{},[675,771,772,773,616,776,779],{},"The ",[613,774,775],{},"authorization_code",[613,777,778],{},"refresh_token"," grants.",[651,781,782,786,791,793,795],{},[675,783,784],{},[613,785,738],{},[675,787,788],{},[613,789,790],{},"\u002Foauth\u002Frevoke",[675,792,767],{},[675,794,690],{},[675,796,797],{},"Revokes a refresh token and the rest of its family (RFC 7009).",[651,799,800,804,809,812,814],{},[675,801,802],{},[613,803,679],{},[675,805,806],{},[613,807,808],{},"\u002Foauth\u002Fuserinfo",[675,810,811],{},"a bearer access token",[675,813,690],{},[675,815,816],{},"The user's claims, filtered by scope.",[651,818,819,823,828,831,833],{},[675,820,821],{},[613,822,738],{},[675,824,825],{},[613,826,827],{},"\u002Foauth\u002Fintrospect",[675,829,830],{},"a confidential application's credentials",[675,832,690],{},[675,834,835],{},"Tells whether an access token is active and what it may do now (RFC 7662).",[594,837,838,844,845,848,849,852,853,856,857,600],{},[839,840,841],"strong",{},[613,842,843],{},"GET \u002Foauth\u002Fauthorize"," answers ",[613,846,847],{},"400"," for an invalid request and ",[613,850,851],{},"403"," when the user may not use the application. A\nscope the server does not know redirects back to the application with ",[613,854,855],{},"error=invalid_scope"," and the ",[613,858,859],{},"state",[594,861,862,867,868,616,870,872,873,876,877,880,881,884,885,888,889,616,892,895],{},[839,863,864],{},[613,865,866],{},"POST \u002Foauth\u002Fauthorize"," answers with the callback URL, carrying ",[613,869,613],{},[613,871,859],{},", or ",[613,874,875],{},"error=access_denied",". The body\nmust be JSON (",[613,878,879],{},"Content-Type: application\u002Fjson","), otherwise it answers ",[613,882,883],{},"415",". The consent page and the ",[613,886,887],{},"\u002Foauth\u002F*","\nresponses carry ",[613,890,891],{},"Content-Security-Policy: frame-ancestors 'none'",[613,893,894],{},"X-Frame-Options: DENY",", so they cannot be framed.",[594,897,898,904,905,616,908,911,912,916,917,919,920,923,924,927],{},[839,899,900,616,902],{},[613,901,764],{},[613,903,790],{}," take the client secret with HTTP Basic, or ",[613,906,907],{},"client_id",[613,909,910],{},"client_secret"," in the\nbody. A ",[598,913,915],{"href":914},"#public-applications","public application"," sends its ",[613,918,907],{}," only. Both reply\n",[613,921,922],{},"{ error, error_description }"," (RFC 6749 §5.2) to every failure, including a malformed body (",[613,925,926],{},"400 invalid_request",").",[594,929,930,934,935,938,939,943,944,947],{},[839,931,932],{},[613,933,764],{}," adds ",[613,936,937],{},"refresh_token_expires_in"," to its response: the seconds until the new refresh token expires\n(see ",[598,940,942],{"href":941},"#session-lifetimes","Session lifetimes","). Its ",[613,945,946],{},"scope"," is what the user granted, which can be less than what the\napplication asked for.",[637,949,951],{"id":950},"module-api","Module API",[594,953,954],{},"These routes serve the consent screen, the user's applications and sign-ins, and the admin screen.",[645,956,957,971],{},[648,958,959],{},[651,960,961,963,965,967,969],{},[654,962,656],{},[654,964,659],{},[654,966,662],{},[654,968,665],{},[654,970,668],{},[670,972,973,995,1016,1043,1071,1088,1111,1134,1155,1173,1190,1212,1230,1256,1276,1294,1315],{},[651,974,975,979,984,986,988],{},[675,976,977],{},[613,978,679],{},[675,980,981],{},[613,982,983],{},"\u002Fapi\u002Foauth-server\u002Fconsent",[675,985,745],{},[675,987,690],{},[675,989,990,991,994],{},"Data for the consent screen (",[613,992,993],{},"ConsentDto","). The query is the authorization request.",[651,996,997,1001,1006,1008,1010],{},[675,998,999],{},[613,1000,679],{},[675,1002,1003],{},[613,1004,1005],{},"\u002Fapi\u002Foauth-server\u002Fme\u002Fapplications",[675,1007,745],{},[675,1009,690],{},[675,1011,1012,1013,927],{},"The applications the user may sign in to (",[613,1014,1015],{},"UserApplicationDto[]",[651,1017,1018,1022,1027,1029,1031],{},[675,1019,1020],{},[613,1021,679],{},[675,1023,1024],{},[613,1025,1026],{},"\u002Fapi\u002Foauth-server\u002Fme\u002Fsessions",[675,1028,745],{},[675,1030,690],{},[675,1032,1033,1034,1038,1039,1042],{},"The applications the user is ",[598,1035,1037],{"href":1036},"#ending-sign-ins","signed in to"," (",[613,1040,1041],{},"OAuthSessionDto[]","), one entry per application, most recently active first.",[651,1044,1045,1050,1055,1057,1060],{},[675,1046,1047],{},[613,1048,1049],{},"DELETE",[675,1051,1052],{},[613,1053,1054],{},"\u002Fapi\u002Foauth-server\u002Fme\u002Fsessions\u002F:id",[675,1056,745],{},[675,1058,1059],{},"204",[675,1061,1062,1063,1066,1067,1070],{},"Signs the user out of one application on every device. ",[613,1064,1065],{},":id"," is the application id. ",[613,1068,1069],{},"404 not_found"," when they have no live sign-in in it.",[651,1072,1073,1077,1081,1083,1085],{},[675,1074,1075],{},[613,1076,1049],{},[675,1078,1079],{},[613,1080,1026],{},[675,1082,745],{},[675,1084,1059],{},[675,1086,1087],{},"Signs the user out of every application.",[651,1089,1090,1094,1099,1106,1108],{},[675,1091,1092],{},[613,1093,679],{},[675,1095,1096],{},[613,1097,1098],{},"\u002Fapi\u002Fadmin\u002Foauth-server\u002Fapplications",[675,1100,1101,1102,1105],{},"staff (session + ",[613,1103,1104],{},"oauth:manage",")",[675,1107,690],{},[675,1109,1110],{},"One page of applications.",[651,1112,1113,1117,1121,1124,1127],{},[675,1114,1115],{},[613,1116,738],{},[675,1118,1119],{},[613,1120,1098],{},[675,1122,1123],{},"staff",[675,1125,1126],{},"201",[675,1128,1129,1130,1133],{},"Creates an application. Returns ",[613,1131,1132],{},"CreatedApplicationDto",", with the secret shown once.",[651,1135,1136,1140,1145,1147,1149],{},[675,1137,1138],{},[613,1139,679],{},[675,1141,1142],{},[613,1143,1144],{},"\u002Fapi\u002Fadmin\u002Foauth-server\u002Fapplications\u002F:id",[675,1146,1123],{},[675,1148,690],{},[675,1150,1151,1152,927],{},"One application (",[613,1153,1154],{},"AdminApplicationDto",[651,1156,1157,1162,1166,1168,1170],{},[675,1158,1159],{},[613,1160,1161],{},"PATCH",[675,1163,1164],{},[613,1165,1144],{},[675,1167,1123],{},[675,1169,690],{},[675,1171,1172],{},"Updates an application and returns it. Disabling it signs everyone out.",[651,1174,1175,1179,1183,1185,1187],{},[675,1176,1177],{},[613,1178,1049],{},[675,1180,1181],{},[613,1182,1144],{},[675,1184,1123],{},[675,1186,1059],{},[675,1188,1189],{},"Deletes an application with its codes and refresh tokens.",[651,1191,1192,1196,1201,1203,1205],{},[675,1193,1194],{},[613,1195,738],{},[675,1197,1198],{},[613,1199,1200],{},"\u002Fapi\u002Fadmin\u002Foauth-server\u002Fapplications\u002F:id\u002Fsecret",[675,1202,1123],{},[675,1204,690],{},[675,1206,1207,1208,1211],{},"Replaces the client secret. Returns ",[613,1209,1210],{},"{ clientSecret }"," once.",[651,1213,1214,1218,1223,1225,1227],{},[675,1215,1216],{},[613,1217,738],{},[675,1219,1220],{},[613,1221,1222],{},"\u002Fapi\u002Fadmin\u002Foauth-server\u002Fapplications\u002F:id\u002Frevoke",[675,1224,1123],{},[675,1226,690],{},[675,1228,1229],{},"Signs everyone out of the application and returns it.",[651,1231,1232,1236,1241,1243,1245],{},[675,1233,1234],{},[613,1235,679],{},[675,1237,1238],{},[613,1239,1240],{},"\u002Fapi\u002Fadmin\u002Foauth-server\u002Fscopes",[675,1242,1123],{},[675,1244,690],{},[675,1246,1247,1248,1251,1252,1255],{},"Every named scope: the read-only ones of config and modules (",[613,1249,1250],{},"managed: true","), then the database ones (",[613,1253,1254],{},"AdminOAuthScopeDto[]","). Not paginated.",[651,1257,1258,1262,1266,1268,1270],{},[675,1259,1260],{},[613,1261,738],{},[675,1263,1264],{},[613,1265,1240],{},[675,1267,1123],{},[675,1269,1126],{},[675,1271,1272,1273,600],{},"Creates a database scope: ",[613,1274,1275],{},"{ name, permissions, label?, description?, icon?, isActive? }",[651,1277,1278,1282,1287,1289,1291],{},[675,1279,1280],{},[613,1281,679],{},[675,1283,1284],{},[613,1285,1286],{},"\u002Fapi\u002Fadmin\u002Foauth-server\u002Fscopes\u002F:name",[675,1288,1123],{},[675,1290,690],{},[675,1292,1293],{},"One scope, also a managed one.",[651,1295,1296,1300,1304,1306,1308],{},[675,1297,1298],{},[613,1299,1161],{},[675,1301,1302],{},[613,1303,1286],{},[675,1305,1123],{},[675,1307,690],{},[675,1309,1310,1311,1314],{},"Updates a database scope. Its ",[613,1312,1313],{},"version"," goes up when it stands for more permissions than before.",[651,1316,1317,1321,1325,1327,1329],{},[675,1318,1319],{},[613,1320,1049],{},[675,1322,1323],{},[613,1324,1286],{},[675,1326,1123],{},[675,1328,1059],{},[675,1330,1331],{},"Deletes a database scope.",[594,1333,1334,1339,1340,1343,1344,1346,1347,1350,1351,1354,1355,1358,1359,1038,1362,1350,1365,1368,1369,1372,1373,600],{},[839,1335,1336],{},[613,1337,1338],{},"GET \u002Fapi\u002Fadmin\u002Foauth-server\u002Fapplications"," returns ",[613,1341,1342],{},"{ items, total, page, pageSize }"," of ",[613,1345,1154],{},". It\ntakes ",[613,1348,1349],{},"page",", ",[613,1352,1353],{},"pageSize"," (at most 100), ",[613,1356,1357],{},"q"," (name or client ID), ",[613,1360,1361],{},"sort",[613,1363,1364],{},"createdAt",[613,1366,1367],{},"name"," or ",[613,1370,1371],{},"priority",") and\n",[613,1374,1375],{},"order",[594,1377,1378,1384,1385,1388,1389,1393,1394,1396],{},[839,1379,1380,616,1382],{},[613,1381,1161],{},[613,1383,1049],{}," answer ",[613,1386,1387],{},"409 managed_by_config"," for a config-owned field of a\n",[598,1390,1392],{"href":1391},"#configured-clients","configured client",". ",[613,1395,1049],{}," answers it for any configured client.",[594,1398,1399,1402,1403,1405,1406,1409,1410,600],{},[839,1400,1401],{},"Staff"," means a user with the ",[613,1404,1104],{}," permission, which is ",[613,1407,1408],{},"sessionOnly",": a bearer token answers\n",[613,1411,1412],{},"403 session_required",[594,1414,1415,1418,1419,1350,1421,616,1423,1384,1425,1427,1428,1368,1431,1433,1434,1436,1437,600],{},[839,1416,1417],{},"The scopes routes"," change only database scopes. ",[613,1420,738],{},[613,1422,1161],{},[613,1424,1049],{},[613,1426,1387],{}," for a\nscope that config or a module defines, or an OpenID Connect scope. A body with an unknown, ",[613,1429,1430],{},"sensitive",[613,1432,1408],{},"\npermission answers ",[613,1435,847],{},". See ",[598,1438,1440],{"href":1439},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver#named-scopes-in-the-database","Scopes",[594,1442,1443,844,1448,1451],{},[839,1444,1445],{},[613,1446,1447],{},"POST …\u002Fsecret",[613,1449,1450],{},"400 bad_request"," for a public application.",[594,1453,1454,1455,1458,1459,1462,1463,1466,1467,1469,1470,1350,1472,1350,1474,1476,1477,1479],{},"The module API fails with the ",[598,1456,1457],{"href":388},"toolkit errors",". They are ",[613,1460,1461],{},"401",",\n",[613,1464,1465],{},"403 forbidden"," (without ",[613,1468,1104],{},"), ",[613,1471,1412],{},[613,1473,1069],{},[613,1475,1450],{},", and h3's ",[613,1478,847],{}," validation error\nfor a malformed body or query.",[602,1481,1483],{"id":1482},"how-a-sign-in-works","How a sign-in works",[594,1485,1486],{},"An application sends the user to the issuer, the user signs in and approves, and the application exchanges a code for\ntokens:",[1488,1489,1495],"pre",{"className":1490,"code":1492,"language":1493,"meta":1494},[1491],"language-text","application ──▶ GET  \u002Foauth\u002Fauthorize?client_id&redirect_uri&response_type=code&state&code_challenge&code_challenge_method=S256[&scope&nonce]\n                 not signed in ──▶ \u003Clogin page>?redirect=\u002Foauth\u002Fauthorize?… ──▶ back here\n                 role\u002Faccess check (403 if denied) ──▶ \u002Fauthorize?… (consent page)\nuser approves ──▶ POST \u002Foauth\u002Fauthorize {…, approved: true} ──▶ redirect to the application's callback with ?code&state\napplication ──▶ POST \u002Foauth\u002Ftoken (code + code_verifier + client credentials) ──▶ access_token, id_token, refresh_token\n","text","",[613,1496,1492],{"__ignoreMap":1494},[1498,1499,1500,1511,1532,1569,1586,1592,1602,1644,1675],"ul",{},[1501,1502,1503,1506,1507,1510],"li",{},[839,1504,1505],{},"PKCE and a secret."," PKCE (Proof Key for Code Exchange) with S256 is required for every application. Applications\nare confidential by default, with a secret shown once when created. See ",[598,1508,1509],{"href":914},"Public applications","\nfor the type without one.",[1501,1512,1513,1516,1517,1520,1521,1524,1525,1528,1529,600],{},[839,1514,1515],{},"Redirect URIs are derived, not free-form."," An application accepts ",[613,1518,1519],{},"websiteUrl"," plus ",[613,1522,1523],{},"callbackPath",", its explicit\n",[613,1526,1527],{},"redirectUris",", and its ",[613,1530,1531],{},"previewUrlPattern",[1501,1533,1534,1537,1538,1368,1541,1544,1545,1548,1549,1551,1552,1350,1555,616,1558,1561,1562,1350,1565,1568],{},[839,1535,1536],{},"Preview patterns"," look like ",[613,1539,1540],{},"https:\u002F\u002F*.vercel.app",[613,1542,1543],{},"https:\u002F\u002Fmyapp-*.vercel.app",": https, one ",[613,1546,1547],{},"*"," in the first\nhostname label, then at least two literal labels, an optional port and path prefix. The ",[613,1550,1547],{}," matches one DNS label of\n",[613,1553,1554],{},"a-z",[613,1556,1557],{},"0-9",[613,1559,1560],{},"-",". A redirect URI is matched on its parsed parts (scheme, host, port, path), must serialise back to\nitself, and may not hold a query, fragment, userinfo, ",[613,1563,1564],{},"%",[613,1566,1567],{},"\\"," or non-ASCII characters. Saving any other pattern is a\n400. Public applications cannot have a pattern (400): with no secret, anyone who can deploy to the pattern's domain\ncould redeem their codes. A pattern already stored on a public application is ignored; list its redirect URIs instead.",[1501,1570,1571,1577,1578,1581,1582,600],{},[839,1572,1573,1576],{},[613,1574,1575],{},"https"," is required",", except for ",[613,1579,1580],{},"localhost",". Public applications may also list\n",[598,1583,1585],{"href":1584},"#native-redirect-uris","native redirect URIs",[1501,1587,1588,1591],{},[839,1589,1590],{},"Refresh tokens rotate."," Presenting a refresh token that was already used counts as theft, and revokes its whole\nfamily. Tokens stay bound to their application: another application cannot refresh or revoke them.",[1501,1593,1594,1597,1598,1601],{},[839,1595,1596],{},"Access is checked again"," on every token exchange, ",[613,1599,1600],{},"userinfo"," call and API call. Disabling an application or\nremoving a role also stops refreshes and API calls.",[1501,1603,1604,1607,1608,1350,1611,1350,1613,1350,1616,1350,1618,1350,1621,1350,1624,1350,1627,616,1630,1633,1634,1393,1637,1639,1640,600],{},[839,1605,1606],{},"Access tokens are bare."," They carry ",[613,1609,1610],{},"sub",[613,1612,946],{},[613,1614,1615],{},"permissions",[613,1617,907],{},[613,1619,1620],{},"aud",[613,1622,1623],{},"iss",[613,1625,1626],{},"exp",[613,1628,1629],{},"iat",[613,1631,1632],{},"jti",",\nwith the header ",[613,1635,1636],{},"typ: at+jwt",[613,1638,1615],{}," is what the granted scopes stand for, frozen when the token was issued. See\n",[598,1641,1643],{"href":1642},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver#the-frozen-permissions-claim","The frozen permissions claim",[1501,1645,1646,1038,1649,1350,1651,1350,1654,1657,1658,1660,1661,1663,1664,1350,1667,1670,1671,1674],{},[839,1647,1648],{},"Identity claims",[613,1650,1367],{},[613,1652,1653],{},"picture",[613,1655,1656],{},"email",") live in the ID token and ",[613,1659,1600],{},", per granted scope. ",[613,1662,1656],{},"\ncomes with ",[613,1665,1666],{},"email_verified",[613,1668,1669],{},"true"," once the user's ",[613,1672,1673],{},"emailVerifiedAt"," is set.",[1501,1676,1677,1680,1681,1684,1685,1687,1688,1691,1692,600],{},[839,1678,1679],{},"Scopes."," An unknown scope is answered with ",[613,1682,1683],{},"invalid_scope",", as a redirect to the application. A request with no\n",[613,1686,946],{}," gets ",[613,1689,1690],{},"openid profile email",". A scope the user may not grant is shown as unavailable on the consent screen and\nleft out of the token. Refreshes check scopes again, silently. See\n",[598,1693,1440],{"href":1694},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver#scopes",[602,1696,1698],{"id":1697},"token-introspection","Token introspection",[594,1700,1701,1704,1705,1711,1712,1714,1715,1718],{},[613,1702,1703],{},"POST \u002Foauth\u002Fintrospect"," lets a service ask the issuer whether an access token is still good and what it may do now\n(",[598,1706,1710],{"href":1707,"rel":1708},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc7662",[1709],"nofollow","RFC 7662","). The frozen ",[613,1713,1615],{}," claim in the token cannot change. The\nintrospection answer can: it is checked against the user's roles when you ask. ",[598,1716,241],{"href":1717},"\u002Fmodules\u002Fidentity\u002Foauth-resource#introspection","\nuses it when you turn it on.",[1488,1720,1724],{"className":1721,"code":1722,"language":1723,"meta":1494,"style":1494},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl -s https:\u002F\u002Fid.example.com\u002Foauth\u002Fintrospect \\\n  -u \"$CLIENT_ID:$CLIENT_SECRET\" \\\n  -d token=\"$ACCESS_TOKEN\" -d token_type_hint=access_token\n","bash",[613,1725,1726,1746,1770],{"__ignoreMap":1494},[1727,1728,1731,1735,1739,1742],"span",{"class":1729,"line":1730},"line",1,[1727,1732,1734],{"class":1733},"sBMFI","curl",[1727,1736,1738],{"class":1737},"sfazB"," -s",[1727,1740,1741],{"class":1737}," https:\u002F\u002Fid.example.com\u002Foauth\u002Fintrospect",[1727,1743,1745],{"class":1744},"sTEyZ"," \\\n",[1727,1747,1749,1752,1756,1759,1762,1765,1768],{"class":1729,"line":1748},2,[1727,1750,1751],{"class":1737},"  -u",[1727,1753,1755],{"class":1754},"sMK4o"," \"",[1727,1757,1758],{"class":1744},"$CLIENT_ID",[1727,1760,1761],{"class":1737},":",[1727,1763,1764],{"class":1744},"$CLIENT_SECRET",[1727,1766,1767],{"class":1754},"\"",[1727,1769,1745],{"class":1744},[1727,1771,1773,1776,1779,1781,1784,1786,1789],{"class":1729,"line":1772},3,[1727,1774,1775],{"class":1737},"  -d",[1727,1777,1778],{"class":1737}," token=",[1727,1780,1767],{"class":1754},[1727,1782,1783],{"class":1744},"$ACCESS_TOKEN",[1727,1785,1767],{"class":1754},[1727,1787,1788],{"class":1737}," -d",[1727,1790,1791],{"class":1737}," token_type_hint=access_token\n",[1488,1793,1797],{"className":1794,"code":1795,"language":1796,"meta":1494,"style":1494},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"active\": true,\n  \"scope\": \"openid tickets\",\n  \"client_id\": \"app_ab12\",\n  \"sub\": \"42\",\n  \"exp\": 1760000000,\n  \"iat\": 1759999100,\n  \"iss\": \"https:\u002F\u002Fid.example.com\",\n  \"aud\": \"frigear-services\",\n  \"token_type\": \"Bearer\",\n  \"permissions\": [\"tickets:read\"]\n}\n","json",[613,1798,1799,1804,1820,1839,1859,1879,1896,1912,1932,1952,1973,1997],{"__ignoreMap":1494},[1727,1800,1801],{"class":1729,"line":1730},[1727,1802,1803],{"class":1754},"{\n",[1727,1805,1806,1809,1813,1815,1817],{"class":1729,"line":1748},[1727,1807,1808],{"class":1754},"  \"",[1727,1810,1812],{"class":1811},"spNyl","active",[1727,1814,1767],{"class":1754},[1727,1816,1761],{"class":1754},[1727,1818,1819],{"class":1754}," true,\n",[1727,1821,1822,1824,1826,1828,1830,1832,1835,1837],{"class":1729,"line":1772},[1727,1823,1808],{"class":1754},[1727,1825,946],{"class":1811},[1727,1827,1767],{"class":1754},[1727,1829,1761],{"class":1754},[1727,1831,1755],{"class":1754},[1727,1833,1834],{"class":1737},"openid tickets",[1727,1836,1767],{"class":1754},[1727,1838,1462],{"class":1754},[1727,1840,1842,1844,1846,1848,1850,1852,1855,1857],{"class":1729,"line":1841},4,[1727,1843,1808],{"class":1754},[1727,1845,907],{"class":1811},[1727,1847,1767],{"class":1754},[1727,1849,1761],{"class":1754},[1727,1851,1755],{"class":1754},[1727,1853,1854],{"class":1737},"app_ab12",[1727,1856,1767],{"class":1754},[1727,1858,1462],{"class":1754},[1727,1860,1862,1864,1866,1868,1870,1872,1875,1877],{"class":1729,"line":1861},5,[1727,1863,1808],{"class":1754},[1727,1865,1610],{"class":1811},[1727,1867,1767],{"class":1754},[1727,1869,1761],{"class":1754},[1727,1871,1755],{"class":1754},[1727,1873,1874],{"class":1737},"42",[1727,1876,1767],{"class":1754},[1727,1878,1462],{"class":1754},[1727,1880,1882,1884,1886,1888,1890,1894],{"class":1729,"line":1881},6,[1727,1883,1808],{"class":1754},[1727,1885,1626],{"class":1811},[1727,1887,1767],{"class":1754},[1727,1889,1761],{"class":1754},[1727,1891,1893],{"class":1892},"sbssI"," 1760000000",[1727,1895,1462],{"class":1754},[1727,1897,1899,1901,1903,1905,1907,1910],{"class":1729,"line":1898},7,[1727,1900,1808],{"class":1754},[1727,1902,1629],{"class":1811},[1727,1904,1767],{"class":1754},[1727,1906,1761],{"class":1754},[1727,1908,1909],{"class":1892}," 1759999100",[1727,1911,1462],{"class":1754},[1727,1913,1915,1917,1919,1921,1923,1925,1928,1930],{"class":1729,"line":1914},8,[1727,1916,1808],{"class":1754},[1727,1918,1623],{"class":1811},[1727,1920,1767],{"class":1754},[1727,1922,1761],{"class":1754},[1727,1924,1755],{"class":1754},[1727,1926,1927],{"class":1737},"https:\u002F\u002Fid.example.com",[1727,1929,1767],{"class":1754},[1727,1931,1462],{"class":1754},[1727,1933,1935,1937,1939,1941,1943,1945,1948,1950],{"class":1729,"line":1934},9,[1727,1936,1808],{"class":1754},[1727,1938,1620],{"class":1811},[1727,1940,1767],{"class":1754},[1727,1942,1761],{"class":1754},[1727,1944,1755],{"class":1754},[1727,1946,1947],{"class":1737},"frigear-services",[1727,1949,1767],{"class":1754},[1727,1951,1462],{"class":1754},[1727,1953,1955,1957,1960,1962,1964,1966,1969,1971],{"class":1729,"line":1954},10,[1727,1956,1808],{"class":1754},[1727,1958,1959],{"class":1811},"token_type",[1727,1961,1767],{"class":1754},[1727,1963,1761],{"class":1754},[1727,1965,1755],{"class":1754},[1727,1967,1968],{"class":1737},"Bearer",[1727,1970,1767],{"class":1754},[1727,1972,1462],{"class":1754},[1727,1974,1976,1978,1980,1982,1984,1987,1989,1992,1994],{"class":1729,"line":1975},11,[1727,1977,1808],{"class":1754},[1727,1979,1615],{"class":1811},[1727,1981,1767],{"class":1754},[1727,1983,1761],{"class":1754},[1727,1985,1986],{"class":1754}," [",[1727,1988,1767],{"class":1754},[1727,1990,1991],{"class":1737},"tickets:read",[1727,1993,1767],{"class":1754},[1727,1995,1996],{"class":1754},"]\n",[1727,1998,2000],{"class":1729,"line":1999},12,[1727,2001,2002],{"class":1754},"}\n",[1498,2004,2005,2023,2039,2053,2061],{},[1501,2006,2007,2010,2011,616,2013,2015,2016,2018,2019,2022],{},[839,2008,2009],{},"Who may ask."," A confidential application, with HTTP Basic or ",[613,2012,907],{},[613,2014,910],{}," in the body. A public\napplication proves nothing with its ",[613,2017,907],{},", so it answers ",[613,2020,2021],{},"401 invalid_client",". The body is form encoded, and JSON is\naccepted too.",[1501,2024,2025,2028,2029,2031,2032,2034,2035,2038],{},[839,2026,2027],{},"Only for its own tokens."," The token must have been issued to the caller (its ",[613,2030,907],{},"), or the caller's client ID\nmust be in the token's ",[613,2033,1620],{},". For any other token the answer is ",[613,2036,2037],{},"{ \"active\": false }",". So is the answer for an invalid,\nexpired, foreign or refresh token. Nothing says why.",[1501,2040,2041,2044,2045,2048,2049,2052],{},[839,2042,2043],{},"Live."," The application must still be active and the user must still pass its ",[613,2046,2047],{},"allowedRoles"," check and the\n",[613,2050,2051],{},"oauth:application:access"," hook, as for an API call. Otherwise the token is inactive.",[1501,2054,2055,2060],{},[839,2056,2057,2059],{},[613,2058,1615],{}," now."," The permissions frozen in the token, limited to what the user may still delegate. A user who lost\na role sees the token shrink. A user who gained one does not widen it.",[1501,2062,2063,2066,2067,2070,2071,600],{},[839,2064,2065],{},"No caching."," The response carries ",[613,2068,2069],{},"Cache-Control: no-store",". Discovery lists the endpoint as\n",[613,2072,2073],{},"introspection_endpoint",[2075,2076,2077,2078,2081,2082,2084,2085,2087,2088,2090,2091,600],"warning",{},"With a shared ",[613,2079,2080],{},"accessTokenAudience"," such as ",[613,2083,1947],{},", the ",[613,2086,1620],{}," is not a client ID. A service can then only\nintrospect tokens issued to its own client. To let a resource service introspect every token, add its client ID to\n",[613,2089,2080],{},", for example ",[613,2092,2093],{},"['frigear-services', 'app_resource']",[602,2095,1509],{"id":2096},"public-applications",[594,2098,2099,2100,2103],{},"A native app or a single-page app cannot keep a secret, since anything compiled into the app is public. Register it as\na public OAuth application. ",[598,2101,81],{"href":2102},"\u002Fguide\u002Fconcepts\u002Fidentity#public-and-confidential-applications"," explains when to\npick which type. This section covers how a public application behaves.",[594,2105,2106,2107,2110,2111,2114,2115,2118],{},"Set ",[613,2108,2109],{},"clientType: 'public'"," with ",[613,2112,2113],{},"createApplication",", or pick ",[839,2116,2117],{},"Client type"," in the admin form.",[2120,2121,2122],"caution",{},"The type is fixed when the application is created. You cannot change a confidential application into a public one\nlater, or back.",[1498,2124,2125,2151,2160,2175],{},[1501,2126,2127,2130,2131,616,2133,2135,2136,2138,2139,2142,2143,1393,2146,1339,2148,600],{},[839,2128,2129],{},"No client secret."," ",[613,2132,764],{},[613,2134,790],{}," identify it by ",[613,2137,907],{}," alone. Discovery lists ",[613,2140,2141],{},"none"," in\n",[613,2144,2145],{},"token_endpoint_auth_methods_supported",[613,2147,2113],{},[613,2149,2150],{},"secret: null",[1501,2152,2153,2156,2157,600],{},[839,2154,2155],{},"PKCE and refresh token rotation"," protect it, as described in ",[598,2158,1483],{"href":2159},"#how-a-sign-in-works",[1501,2161,2162,2168,2169,2171,2172,2174],{},[839,2163,2164,2165,2167],{},"A ",[613,2166,910],{}," is refused."," Sending one to a public application answers ",[613,2170,2021],{},", because it means\nthe app was set up as the wrong type. An empty ",[613,2173,910],{}," field is accepted.",[1501,2176,2177,2180,2181,2183],{},[839,2178,2179],{},"The website URL is optional."," A native app often has none. Without one, the consent screen shows no website link,\nand only the listed ",[613,2182,1527],{}," are accepted. At least one is then required.",[637,2185,2187],{"id":2186},"unlisted-applications","Unlisted applications",[594,2189,2190,2191,2194,2195,872,2198,2201,2202,600],{},"Some applications are opened only by the user, such as a mobile app. Turn off ",[839,2192,2193],{},"Show in the users' application list","\nfor them. In code that is ",[613,2196,2197],{},"isListed: false",[613,2199,2200],{},"listed: false"," for a ",[598,2203,1392],{"href":1391},[594,2205,2206,2207,2211,2212,2215,2216,2219,2220,600],{},"An unlisted application is left out of the ",[598,2208,2210],{"href":2209},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver#app","applications list",", the\n",[613,2213,2214],{},"oauth-server:mine"," bootstrap key and the ",[613,2217,2218],{},"my-oauth-applications"," MCP tool. Signing in works as before. The admin table\nmarks it ",[839,2221,2222],{},"Unlisted",[637,2224,2226],{"id":2225},"native-redirect-uris","Native redirect URIs",[594,2228,2229,2230,2232],{},"A public application may add custom-scheme redirect URIs to ",[613,2231,1527],{},". The app receives the code on them after\nthe system browser step. Two kinds work:",[1498,2234,2235,2242],{},[1501,2236,2237,2238,2241],{},"a private-use scheme, preferably the reverse of a domain you control, such as ",[613,2239,2240],{},"com.example.app:\u002Fcallback","\n(RFC 8252 §7.1);",[1501,2243,2244,2245,600],{},"a webview origin, such as ",[613,2246,2247],{},"capacitor:\u002F\u002Flocalhost\u002Fauth\u002Fcallback",[594,2249,2250],{},"They are matched exactly: no prefixes, wildcards or query strings. These are rejected when the application is saved:",[1498,2252,2253,2269,2272],{},[1501,2254,2255,2256,1350,2259,1350,2262,616,2265,2268],{},"web and dangerous schemes, such as ",[613,2257,2258],{},"javascript:",[613,2260,2261],{},"data:",[613,2263,2264],{},"file:",[613,2266,2267],{},"blob:",";",[1501,2270,2271],{},"credentials and fragments in the URI;",[1501,2273,2274],{},"any custom scheme on a confidential application.",[594,2276,2277,2279],{},[613,2278,1575],{}," redirects, such as universal links or app links, work for both types.",[2075,2281,2282,2283,2285],{},"Any app on the device can claim a custom scheme. PKCE keeps the code itself safe, but prefer an ",[613,2284,1575],{}," universal link\nor app link as the redirect where the platform offers one.",[637,2287,2289],{"id":2288},"calling-from-a-browser-on-another-origin","Calling from a browser on another origin",[594,2291,2292,2293,2296,2297,2300,2301,2304,2305,1761],{},"The webview of a Capacitor app runs at ",[613,2294,2295],{},"capacitor:\u002F\u002Flocalhost"," on iOS and ",[613,2298,2299],{},"https:\u002F\u002Flocalhost"," on Android. So its\nrequests to this server are cross-origin. List those origins in ",[613,2302,2303],{},"cors.origins",". To open your own API to them too, list\nits path prefixes in ",[613,2306,2307],{},"cors.paths",[1488,2309,2314],{"className":2310,"code":2311,"filename":2312,"language":2313,"meta":1494,"style":1494},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default defineNuxtConfig({\n  oauthServer: {\n    cors: { origins: ['capacitor:\u002F\u002Flocalhost', 'https:\u002F\u002Flocalhost'], paths: ['\u002Fapi\u002F'] },\n  },\n})\n","nuxt.config.ts","ts",[613,2315,2316,2334,2345,2404,2409],{"__ignoreMap":1494},[1727,2317,2318,2322,2325,2329,2332],{"class":1729,"line":1730},[1727,2319,2321],{"class":2320},"s7zQu","export",[1727,2323,2324],{"class":2320}," default",[1727,2326,2328],{"class":2327},"s2Zo4"," defineNuxtConfig",[1727,2330,2331],{"class":1744},"(",[1727,2333,1803],{"class":1754},[1727,2335,2336,2340,2342],{"class":1729,"line":1748},[1727,2337,2339],{"class":2338},"swJcz","  oauthServer",[1727,2341,1761],{"class":1754},[1727,2343,2344],{"class":1754}," {\n",[1727,2346,2347,2350,2352,2355,2358,2360,2362,2365,2367,2369,2372,2375,2377,2379,2382,2384,2387,2389,2391,2393,2396,2398,2401],{"class":1729,"line":1772},[1727,2348,2349],{"class":2338},"    cors",[1727,2351,1761],{"class":1754},[1727,2353,2354],{"class":1754}," {",[1727,2356,2357],{"class":2338}," origins",[1727,2359,1761],{"class":1754},[1727,2361,1986],{"class":1744},[1727,2363,2364],{"class":1754},"'",[1727,2366,2295],{"class":1737},[1727,2368,2364],{"class":1754},[1727,2370,2371],{"class":1754},",",[1727,2373,2374],{"class":1754}," '",[1727,2376,2299],{"class":1737},[1727,2378,2364],{"class":1754},[1727,2380,2381],{"class":1744},"]",[1727,2383,2371],{"class":1754},[1727,2385,2386],{"class":2338}," paths",[1727,2388,1761],{"class":1754},[1727,2390,1986],{"class":1744},[1727,2392,2364],{"class":1754},[1727,2394,2395],{"class":1737},"\u002Fapi\u002F",[1727,2397,2364],{"class":1754},[1727,2399,2400],{"class":1744},"] ",[1727,2402,2403],{"class":1754},"},\n",[1727,2405,2406],{"class":1729,"line":1841},[1727,2407,2408],{"class":1754},"  },\n",[1727,2410,2411,2414],{"class":1729,"line":1861},[1727,2412,2413],{"class":1754},"}",[1727,2415,2416],{"class":1744},")\n",[1498,2418,2419,2437,2445,2458,2470,2485],{},[1501,2420,2421,2130,2424,1350,2426,1350,2428,2084,2430,2433,2434,2436],{},[839,2422,2423],{},"Which endpoints.",[613,2425,764],{},[613,2427,790],{},[613,2429,808],{},[613,2431,2432],{},"\u002F.well-known"," documents and the\nroutes under ",[613,2435,2307],{}," answer CORS requests, and only for listed origins.",[1501,2438,2439,2444],{},[839,2440,2441,2442,600],{},"Never ",[613,2443,722],{}," It is a top-level navigation with the user's cookie session. It stays closed even when\na prefix covers it.",[1501,2446,2447,2450,2451,2454,2455,2457],{},[839,2448,2449],{},"No cookies."," No ",[613,2452,2453],{},"Access-Control-Allow-Credentials"," is sent, so cookies never travel cross-origin. The app\nauthenticates with its client ID and bearer tokens. Session-only routes, such as account settings and admin, answer\n",[613,2456,1461],{}," to cross-origin calls.",[1501,2459,2460,2463,2464,1393,2467,2469],{},[839,2461,2462],{},"Exact origins."," An origin is ",[613,2465,2466],{},"scheme:\u002F\u002Fhost[:port]",[613,2468,1547],{}," is rejected when the app starts.",[1501,2471,2472,2130,2475,2477,2478,2481,2482,600],{},[839,2473,2474],{},"Prefix matching.",[613,2476,2395],{}," matches ",[613,2479,2480],{},"\u002Fapi"," and everything below it, not ",[613,2483,2484],{},"\u002Fapis",[1501,2486,2487,2130,2490,1462,2493,2496,2497,2500,2501,600],{},[839,2488,2489],{},"Headers on your API.",[613,2491,2492],{},"Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS",[613,2494,2495],{},"Access-Control-Allow-Headers: authorization, content-type, accept",", and ",[613,2498,2499],{},"Access-Control-Expose-Headers: www-authenticate"," so the app can read why a token was refused. Preflights are answered ",[613,2502,1059],{},[594,2504,2505,2506,2142,2509,2515],{},"The headers come from ",[613,2507,2508],{},"applyBearerCors",[598,2510,2512],{"href":2511},"\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fserver-toolkit#bearer-cors",[613,2513,2514],{},"@frigear-nu\u002Fmodule-kit\u002Fserver",". You can use it for a\nmiddleware of your own.",[602,2517,2519],{"id":2518},"configured-clients","Configured clients",[594,2521,2522,2523,2525,2526,2529,2530,600],{},"An app that ships with its ",[613,2524,907],{}," compiled in, such as a mobile app or a single-page app, needs that ID to be the\nsame in every environment. Define it in ",[613,2527,2528],{},"clients"," instead of the admin area, and it gets a fixed ID. Such an\napplication is a ",[598,2531,1392],{"href":87},[1488,2533,2535],{"className":2310,"code":2534,"filename":2312,"language":2313,"meta":1494,"style":1494},"export default defineNuxtConfig({\n  oauthServer: {\n    cors: { paths: ['\u002Fapi\u002F'] },\n    clients: [{\n      id: 'mobile',\n      name: 'Example mobile',\n      redirectUris: ['com.example.app:\u002Fauth\u002Fcallback'],\n      \u002F\u002F Only accepted under `nuxt dev`: the app's dev server in a desktop browser.\n      devRedirectUris: ['http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback'],\n      \u002F\u002F The webviews' origins (the redirect URIs have none to derive).\n      corsOrigins: ['capacitor:\u002F\u002Flocalhost', 'https:\u002F\u002Flocalhost'],\n    }],\n  },\n})\n",[613,2536,2537,2549,2557,2581,2592,2608,2624,2644,2650,2670,2675,2702,2711,2716],{"__ignoreMap":1494},[1727,2538,2539,2541,2543,2545,2547],{"class":1729,"line":1730},[1727,2540,2321],{"class":2320},[1727,2542,2324],{"class":2320},[1727,2544,2328],{"class":2327},[1727,2546,2331],{"class":1744},[1727,2548,1803],{"class":1754},[1727,2550,2551,2553,2555],{"class":1729,"line":1748},[1727,2552,2339],{"class":2338},[1727,2554,1761],{"class":1754},[1727,2556,2344],{"class":1754},[1727,2558,2559,2561,2563,2565,2567,2569,2571,2573,2575,2577,2579],{"class":1729,"line":1772},[1727,2560,2349],{"class":2338},[1727,2562,1761],{"class":1754},[1727,2564,2354],{"class":1754},[1727,2566,2386],{"class":2338},[1727,2568,1761],{"class":1754},[1727,2570,1986],{"class":1744},[1727,2572,2364],{"class":1754},[1727,2574,2395],{"class":1737},[1727,2576,2364],{"class":1754},[1727,2578,2400],{"class":1744},[1727,2580,2403],{"class":1754},[1727,2582,2583,2586,2588,2590],{"class":1729,"line":1841},[1727,2584,2585],{"class":2338},"    clients",[1727,2587,1761],{"class":1754},[1727,2589,1986],{"class":1744},[1727,2591,1803],{"class":1754},[1727,2593,2594,2597,2599,2601,2604,2606],{"class":1729,"line":1861},[1727,2595,2596],{"class":2338},"      id",[1727,2598,1761],{"class":1754},[1727,2600,2374],{"class":1754},[1727,2602,2603],{"class":1737},"mobile",[1727,2605,2364],{"class":1754},[1727,2607,1462],{"class":1754},[1727,2609,2610,2613,2615,2617,2620,2622],{"class":1729,"line":1881},[1727,2611,2612],{"class":2338},"      name",[1727,2614,1761],{"class":1754},[1727,2616,2374],{"class":1754},[1727,2618,2619],{"class":1737},"Example mobile",[1727,2621,2364],{"class":1754},[1727,2623,1462],{"class":1754},[1727,2625,2626,2629,2631,2633,2635,2638,2640,2642],{"class":1729,"line":1898},[1727,2627,2628],{"class":2338},"      redirectUris",[1727,2630,1761],{"class":1754},[1727,2632,1986],{"class":1744},[1727,2634,2364],{"class":1754},[1727,2636,2637],{"class":1737},"com.example.app:\u002Fauth\u002Fcallback",[1727,2639,2364],{"class":1754},[1727,2641,2381],{"class":1744},[1727,2643,1462],{"class":1754},[1727,2645,2646],{"class":1729,"line":1914},[1727,2647,2649],{"class":2648},"sHwdD","      \u002F\u002F Only accepted under `nuxt dev`: the app's dev server in a desktop browser.\n",[1727,2651,2652,2655,2657,2659,2661,2664,2666,2668],{"class":1729,"line":1934},[1727,2653,2654],{"class":2338},"      devRedirectUris",[1727,2656,1761],{"class":1754},[1727,2658,1986],{"class":1744},[1727,2660,2364],{"class":1754},[1727,2662,2663],{"class":1737},"http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback",[1727,2665,2364],{"class":1754},[1727,2667,2381],{"class":1744},[1727,2669,1462],{"class":1754},[1727,2671,2672],{"class":1729,"line":1954},[1727,2673,2674],{"class":2648},"      \u002F\u002F The webviews' origins (the redirect URIs have none to derive).\n",[1727,2676,2677,2680,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700],{"class":1729,"line":1975},[1727,2678,2679],{"class":2338},"      corsOrigins",[1727,2681,1761],{"class":1754},[1727,2683,1986],{"class":1744},[1727,2685,2364],{"class":1754},[1727,2687,2295],{"class":1737},[1727,2689,2364],{"class":1754},[1727,2691,2371],{"class":1754},[1727,2693,2374],{"class":1754},[1727,2695,2299],{"class":1737},[1727,2697,2364],{"class":1754},[1727,2699,2381],{"class":1744},[1727,2701,1462],{"class":1754},[1727,2703,2704,2707,2709],{"class":1729,"line":1999},[1727,2705,2706],{"class":1754},"    }",[1727,2708,2381],{"class":1744},[1727,2710,1462],{"class":1754},[1727,2712,2714],{"class":1729,"line":2713},13,[1727,2715,2408],{"class":1754},[1727,2717,2719,2721],{"class":1729,"line":2718},14,[1727,2720,2413],{"class":1754},[1727,2722,2416],{"class":1744},[645,2724,2725,2740],{},[648,2726,2727],{},[651,2728,2729,2732,2735,2738],{},[654,2730,2731],{},"Field",[654,2733,2734],{},"Type",[654,2736,2737],{},"Default",[654,2739,668],{},[670,2741,2742,2761,2776,2795,2820,2846,2863,2892,2912,2929,2953,2977,2995],{},[651,2743,2744,2749,2754,2757],{},[675,2745,2746],{},[613,2747,2748],{},"id",[675,2750,2751],{},[613,2752,2753],{},"string",[675,2755,2756],{},"required",[675,2758,772,2759,600],{},[613,2760,907],{},[651,2762,2763,2767,2771,2773],{},[675,2764,2765],{},[613,2766,1367],{},[675,2768,2769],{},[613,2770,2753],{},[675,2772,2756],{},[675,2774,2775],{},"Shown on the consent screen and in the admin area.",[651,2777,2778,2783,2788,2792],{},[675,2779,2780],{},[613,2781,2782],{},"type",[675,2784,2785],{},[613,2786,2787],{},"'public'",[675,2789,2790],{},[613,2791,2787],{},[675,2793,2794],{},"Only public applications can be configured.",[651,2796,2797,2801,2806,2808],{},[675,2798,2799],{},[613,2800,1527],{},[675,2802,2803],{},[613,2804,2805],{},"string[]",[675,2807,2756],{},[675,2809,2810,2811,1038,2813,2816,2817,600],{},"Exact redirect URIs: ",[613,2812,1575],{},[613,2814,2815],{},"http"," for localhost) or ",[598,2818,2819],{"href":1584},"native",[651,2821,2822,2827,2831,2836],{},[675,2823,2824],{},[613,2825,2826],{},"devRedirectUris",[675,2828,2829],{},[613,2830,2805],{},[675,2832,2833],{},[613,2834,2835],{},"[]",[675,2837,2838,2839,2841,2842,2845],{},"Added to ",[613,2840,1527],{}," under ",[613,2843,2844],{},"nuxt dev"," only.",[651,2847,2848,2852,2857,2860],{},[675,2849,2850],{},[613,2851,1519],{},[675,2853,2854],{},[613,2855,2856],{},"string | null",[675,2858,2859],{},"derived",[675,2861,2862],{},"The application's origin.",[651,2864,2865,2870,2875,2879],{},[675,2866,2867],{},[613,2868,2869],{},"listed",[675,2871,2872],{},[613,2873,2874],{},"boolean",[675,2876,2877],{},[613,2878,1669],{},[675,2880,2881,2882,1393,2884,2887,2888,600],{},"Shown in the users' ",[598,2883,2210],{"href":2209},[613,2885,2886],{},"false"," makes it ",[598,2889,2891],{"href":2890},"#unlisted-applications","unlisted",[651,2893,2894,2905,2907,2909],{},[675,2895,2896,1350,2899,1350,2902],{},[613,2897,2898],{},"description",[613,2900,2901],{},"icon",[613,2903,2904],{},"tags",[675,2906],{},[675,2908],{},[675,2910,2911],{},"As for any application.",[651,2913,2914,2918,2922,2926],{},[675,2915,2916],{},[613,2917,2047],{},[675,2919,2920],{},[613,2921,2805],{},[675,2923,2924],{},[613,2925,2835],{},[675,2927,2928],{},"Roles that may sign in, matched against the user's effective roles. Empty means every signed-in user.",[651,2930,2931,2936,2940,2944],{},[675,2932,2933],{},[613,2934,2935],{},"wildcardScopes",[675,2937,2938],{},[613,2939,2874],{},[675,2941,2942],{},[613,2943,2886],{},[675,2945,2946,2947,1350,2949,2952],{},"May ask for wildcard scopes (",[613,2948,1547],{},[613,2950,2951],{},"tickets:*","). Users still grant only what they may do.",[651,2954,2955,2960,2965,2970],{},[675,2956,2957],{},[613,2958,2959],{},"refreshToken",[675,2961,2962],{},[613,2963,2964],{},"{ idleTtl?, maxAge? }",[675,2966,2967,2968],{},"the global ",[613,2969,2959],{},[675,2971,2972,2973,2976],{},"This client's ",[598,2974,2975],{"href":941},"session lifetime",", in seconds.",[651,2978,2979,2984,2988,2992],{},[675,2980,2981],{},[613,2982,2983],{},"allowLongBrowserSessions",[675,2985,2986],{},[613,2987,2874],{},[675,2989,2990],{},[613,2991,2886],{},[675,2993,2994],{},"Silences the warning for a browser client above 7 days idle.",[651,2996,2997,3002,3007,3009],{},[675,2998,2999],{},[613,3000,3001],{},"corsOrigins",[675,3003,3004],{},[613,3005,3006],{},"string[] | false",[675,3008,2859],{},[675,3010,3011,3012,600],{},"Origins added to ",[613,3013,2303],{},[594,3015,3016,3020,3021,1350,3023,1368,3026,3028,3029,3032],{},[839,3017,3018],{},[613,3019,2748],{}," is 3 to 64 letters, digits, ",[613,3022,600],{},[613,3024,3025],{},"_",[613,3027,1560],{},", starting with a letter or digit. It may not start with ",[613,3030,3031],{},"app_",",\nthe prefix of created applications.",[594,3034,3035,3039,3040,3042],{},[839,3036,3037],{},[613,3038,2782],{}," is always ",[613,3041,2787],{},", since a secret does not belong in config.",[594,3044,3045,3049,3050,927],{},[839,3046,3047],{},[613,3048,1519],{}," defaults to the origin of the first web redirect URI. A native-only configured client has none\n(",[613,3051,3052],{},"null",[594,3054,3055,3059,3060,1462,3063,1368,3066,1393,3068,3070],{},[839,3056,3057],{},[613,3058,3001],{}," defaults to the origins of the redirect URIs that have one, such as ",[613,3061,3062],{},"https:\u002F\u002Fapp.example.com",[613,3064,3065],{},"http:\u002F\u002Flocalhost:3001",[613,3067,2295],{},[613,3069,2886],{}," adds none.",[637,3072,3074],{"id":3073},"validation-and-runtime-override","Validation and runtime override",[594,3076,3077],{},"The list is checked when the app starts. An invalid ID, redirect URI or origin is a build error.",[594,3079,3080,3081,3084],{},"It is checked again at runtime, where ",[613,3082,3083],{},"NUXT_OAUTH_SERVER_CLIENTS"," (JSON) can replace it. The CORS origins are added at\nruntime too, so a configured client the override removes takes its origins with it.",[594,3086,3087,3088,3091,3092,3094],{},"An invalid override is logged once. Application lookups then answer ",[613,3089,3090],{},"500 not_configured",". CORS falls back to\n",[613,3093,2303],{},", and same-origin requests are untouched.",[637,3096,3098],{"id":3097},"config-is-the-source-of-truth","Config is the source of truth",[594,3100,3101,3102,3105,3106,3108,3109,3111,3112,600],{},"Each server instance creates missing configured clients in ",[613,3103,3104],{},"oauth_applications"," on the first OAuth request. Every\nlookup then reads the name, website, redirect URIs, roles, tags, listing and session lifetime from that instance's\nconfig, not the row.\nSo a rolling deploy, a preview or ",[613,3107,2844],{}," on the same database never overwrite each other, and production never\naccepts ",[613,3110,2826],{},". A native-only configured client is left out of the user's\n",[598,3113,2210],{"href":2209},[637,3115,3117],{"id":3116},"in-the-admin-area","In the admin area",[594,3119,3120,3121,3124,3125,3127,3128,600],{},"Configured clients carry a ",[839,3122,3123],{},"Managed in nuxt.config"," badge, and ",[613,3126,1250],{}," in ",[613,3129,1154],{},[1498,3131,3132,3138,3147],{},[1501,3133,3134,3137],{},[839,3135,3136],{},"What staff can change."," Staff can disable a configured client, and change its priority and login URL.",[1501,3139,3140,2130,3143,3146],{},[839,3141,3142],{},"Disabling sticks.",[613,3144,3145],{},"isActive"," is set only when the application is created, so a disabled one stays disabled.",[1501,3148,3149,3152,3153,3155],{},[839,3150,3151],{},"What they cannot change."," Changing a config-owned field or deleting the application answers ",[613,3154,1387],{},". Sending the current value is fine.",[637,3157,3159],{"id":3158},"conflicts-and-removal","Conflicts and removal",[594,3161,3162],{},"An ID that already belongs to a confidential application is left alone. The configured client is ignored: it is not\nmanaged, adds no CORS origins and stays editable as usual. The server log shows a warning.",[594,3164,3165,3166,3168],{},"A configured client you remove from ",[613,3167,2528],{}," is not deleted, so its users stay signed in. The server warns once. The\napplication becomes an ordinary one that you can edit or delete in the admin area.",[602,3170,942],{"id":3171},"session-lifetimes",[594,3173,3174,3175,3178],{},"How long a user stays signed in to an application depends on its refresh tokens. Access tokens stay short\n(",[613,3176,3177],{},"accessTokenTtl",", 15 minutes by default). Two limits apply, both in seconds:",[1498,3180,3181,3189],{},[1501,3182,3183,3188],{},[839,3184,3185],{},[613,3186,3187],{},"idleTtl"," is how long a refresh token lives unused. Every refresh starts a fresh window, so an app that is\nopened regularly stays signed in.",[1501,3190,3191,3196,3197,3199],{},[839,3192,3193],{},[613,3194,3195],{},"maxAge"," is how long a sign-in lives in total, counted from when the user signed in. ",[613,3198,3052],{}," means no cap.",[594,3201,3202,3203,3206],{},"A new refresh token expires at ",[613,3204,3205],{},"min(now + idleTtl, authTime + maxAge)",". The first setting that exists wins:",[3208,3209,3210,3231,3242],"ol",{},[1501,3211,3212,3213,616,3216,3219,3220,3223,3224,3227,3228,3230],{},"The application's ",[839,3214,3215],{},"Session length",[839,3217,3218],{},"Sign in again after"," in the admin area (",[613,3221,3222],{},"refreshIdleTtl"," and\n",[613,3225,3226],{},"refreshMaxAge"," in the admin API; ",[613,3229,3052],{}," inherits).",[1501,3232,3233,3234,3236,3237,3239,3240,600],{},"For a ",[598,3235,1392],{"href":1391},", its ",[613,3238,2959],{},". Config owns it, so editing it answers\n",[613,3241,1387],{},[1501,3243,3244,3245,3247],{},"The server's ",[613,3246,2959],{}," option: 30 days idle and no cap by default.",[1488,3249,3251],{"className":2310,"code":3250,"filename":2312,"language":2313,"meta":1494,"style":1494},"export default defineNuxtConfig({\n  oauthServer: {\n    refreshToken: { idleTtl: 60 * 60 * 24 * 30 }, \u002F\u002F everyone else: 30 days unused\n    clients: [{\n      id: 'com.example.app',\n      name: 'Example',\n      redirectUris: ['com.example.app:\u002Fauth\u002Fcallback'],\n      refreshToken: { idleTtl: 60 * 60 * 24 * 180 }, \u002F\u002F the native app: signed in while opened every six months\n    }],\n  },\n})\n",[613,3252,3253,3265,3273,3311,3321,3336,3351,3369,3402,3410,3414],{"__ignoreMap":1494},[1727,3254,3255,3257,3259,3261,3263],{"class":1729,"line":1730},[1727,3256,2321],{"class":2320},[1727,3258,2324],{"class":2320},[1727,3260,2328],{"class":2327},[1727,3262,2331],{"class":1744},[1727,3264,1803],{"class":1754},[1727,3266,3267,3269,3271],{"class":1729,"line":1748},[1727,3268,2339],{"class":2338},[1727,3270,1761],{"class":1754},[1727,3272,2344],{"class":1754},[1727,3274,3275,3278,3280,3282,3285,3287,3290,3293,3295,3297,3300,3302,3305,3308],{"class":1729,"line":1772},[1727,3276,3277],{"class":2338},"    refreshToken",[1727,3279,1761],{"class":1754},[1727,3281,2354],{"class":1754},[1727,3283,3284],{"class":2338}," idleTtl",[1727,3286,1761],{"class":1754},[1727,3288,3289],{"class":1892}," 60",[1727,3291,3292],{"class":1754}," *",[1727,3294,3289],{"class":1892},[1727,3296,3292],{"class":1754},[1727,3298,3299],{"class":1892}," 24",[1727,3301,3292],{"class":1754},[1727,3303,3304],{"class":1892}," 30",[1727,3306,3307],{"class":1754}," },",[1727,3309,3310],{"class":2648}," \u002F\u002F everyone else: 30 days unused\n",[1727,3312,3313,3315,3317,3319],{"class":1729,"line":1841},[1727,3314,2585],{"class":2338},[1727,3316,1761],{"class":1754},[1727,3318,1986],{"class":1744},[1727,3320,1803],{"class":1754},[1727,3322,3323,3325,3327,3329,3332,3334],{"class":1729,"line":1861},[1727,3324,2596],{"class":2338},[1727,3326,1761],{"class":1754},[1727,3328,2374],{"class":1754},[1727,3330,3331],{"class":1737},"com.example.app",[1727,3333,2364],{"class":1754},[1727,3335,1462],{"class":1754},[1727,3337,3338,3340,3342,3344,3347,3349],{"class":1729,"line":1881},[1727,3339,2612],{"class":2338},[1727,3341,1761],{"class":1754},[1727,3343,2374],{"class":1754},[1727,3345,3346],{"class":1737},"Example",[1727,3348,2364],{"class":1754},[1727,3350,1462],{"class":1754},[1727,3352,3353,3355,3357,3359,3361,3363,3365,3367],{"class":1729,"line":1898},[1727,3354,2628],{"class":2338},[1727,3356,1761],{"class":1754},[1727,3358,1986],{"class":1744},[1727,3360,2364],{"class":1754},[1727,3362,2637],{"class":1737},[1727,3364,2364],{"class":1754},[1727,3366,2381],{"class":1744},[1727,3368,1462],{"class":1754},[1727,3370,3371,3374,3376,3378,3380,3382,3384,3386,3388,3390,3392,3394,3397,3399],{"class":1729,"line":1914},[1727,3372,3373],{"class":2338},"      refreshToken",[1727,3375,1761],{"class":1754},[1727,3377,2354],{"class":1754},[1727,3379,3284],{"class":2338},[1727,3381,1761],{"class":1754},[1727,3383,3289],{"class":1892},[1727,3385,3292],{"class":1754},[1727,3387,3289],{"class":1892},[1727,3389,3292],{"class":1754},[1727,3391,3299],{"class":1892},[1727,3393,3292],{"class":1754},[1727,3395,3396],{"class":1892}," 180",[1727,3398,3307],{"class":1754},[1727,3400,3401],{"class":2648}," \u002F\u002F the native app: signed in while opened every six months\n",[1727,3403,3404,3406,3408],{"class":1729,"line":1934},[1727,3405,2706],{"class":1754},[1727,3407,2381],{"class":1744},[1727,3409,1462],{"class":1754},[1727,3411,3412],{"class":1729,"line":1954},[1727,3413,2408],{"class":1754},[1727,3415,3416,3418],{"class":1729,"line":1975},[1727,3417,2413],{"class":1754},[1727,3419,2416],{"class":1744},[594,3421,3422,3423,3425,3426,927],{},"The policy is read whenever a refresh token is created. A changed policy reaches existing sign-ins at their next\nrefresh, and a sign-in already past a new, shorter ",[613,3424,3195],{}," is refused then (",[613,3427,3428],{},"400 invalid_grant",[2075,3430,3431,3432,3435,3436,600],{},"Both values are capped at two years, so a forgotten device stops working by itself. A ",[839,3433,3434],{},"browser"," application keeps\nits refresh token where any XSS can read it, so the admin area warns above 7 days idle. A configured client gets the\nwarning in the server log unless it sets ",[613,3437,3438],{},"allowLongBrowserSessions: true",[594,3440,3441,3442,3127,3445,3447,3448,3450,3451,3453,3454,3457],{},"The server sorts applications by what it already knows, shown as ",[613,3443,3444],{},"kind",[613,3446,1154],{},": ",[839,3449,2819],{}," is a\npublic application whose redirect URIs are all custom-scheme or loopback, ",[839,3452,3434],{}," is any other public\napplication, and ",[839,3455,3456],{},"confidential"," is a server.",[637,3459,3461],{"id":3460},"ending-sign-ins","Ending sign-ins",[594,3463,3464,3465,600],{},"Long sign-ins need ways to end early. Each one fires ",[613,3466,3467],{},"oauth:token:revoked",[1498,3469,3470,3491,3512],{},[1501,3471,3472,3475,3476,3479,3480,3482,3483,3486,3487,3490],{},[839,3473,3474],{},"The user"," sees the applications they are signed in to as ",[839,3477,3478],{},"Signed-in apps"," on the\n",[598,3481,195],{"href":196}," security page, one entry per application, and can sign out of one (on every\ndevice) or of all of them (reason ",[613,3484,3485],{},"signed_out","). The ",[613,3488,3489],{},"me\u002Fsessions"," routes above serve that list. It is per\napplication, not per sign-in: an application that signs in again without revoking its old token leaves that sign-in\nlive until it expires, and nothing about the device is stored to tell the two apart.",[1501,3492,3493,3496,3497,3500,3501,3504,3505,3508,3509,600],{},[839,3494,3495],{},"A credential change"," ends every sign-in of the user when ",[598,3498,168],{"href":3499},"\u002Fmodules\u002Fidentity\u002Fauth\u002Freference#hooks"," fires\n",[613,3502,3503],{},"auth:credentials:changed",": a password change or reset, an email change, or its revert (reason\n",[613,3506,3507],{},"credentials_changed","). Turn it off with ",[613,3510,3511],{},"revokeOnCredentialChange: false",[1501,3513,3514,3516,3517,3520,3521,600],{},[839,3515,1401],{}," disable the application or sign everyone out of it (",[613,3518,3519],{},"POST …\u002Fapplications\u002F:id\u002Frevoke","). Your own code can\ncall ",[613,3522,3523],{},"revokeUserGrants(userId)",[1488,3525,3527],{"className":2310,"code":3526,"language":2313,"meta":1494,"style":1494},"type OAuthSessionDto = {\n  id: string \u002F\u002F the application id\n  application: { id: string, name: string, icon: string | null }\n  signedInAt: string \u002F\u002F the earliest live sign-in\n  lastActiveAt: string \u002F\u002F the last refresh, on any device\n  expiresAt: string \u002F\u002F when the last live sign-in ends\n  sessions: number \u002F\u002F live sign-ins (devices, browsers, app installs)\n}\n",[613,3528,3529,3541,3554,3597,3609,3621,3633,3646],{"__ignoreMap":1494},[1727,3530,3531,3533,3536,3539],{"class":1729,"line":1730},[1727,3532,2782],{"class":1811},[1727,3534,3535],{"class":1733}," OAuthSessionDto",[1727,3537,3538],{"class":1754}," =",[1727,3540,2344],{"class":1754},[1727,3542,3543,3546,3548,3551],{"class":1729,"line":1748},[1727,3544,3545],{"class":2338},"  id",[1727,3547,1761],{"class":1754},[1727,3549,3550],{"class":1733}," string",[1727,3552,3553],{"class":2648}," \u002F\u002F the application id\n",[1727,3555,3556,3559,3561,3563,3566,3568,3570,3572,3575,3577,3579,3581,3584,3586,3588,3591,3594],{"class":1729,"line":1772},[1727,3557,3558],{"class":2338},"  application",[1727,3560,1761],{"class":1754},[1727,3562,2354],{"class":1754},[1727,3564,3565],{"class":2338}," id",[1727,3567,1761],{"class":1754},[1727,3569,3550],{"class":1733},[1727,3571,2371],{"class":1754},[1727,3573,3574],{"class":2338}," name",[1727,3576,1761],{"class":1754},[1727,3578,3550],{"class":1733},[1727,3580,2371],{"class":1754},[1727,3582,3583],{"class":2338}," icon",[1727,3585,1761],{"class":1754},[1727,3587,3550],{"class":1733},[1727,3589,3590],{"class":1754}," |",[1727,3592,3593],{"class":1733}," null",[1727,3595,3596],{"class":1754}," }\n",[1727,3598,3599,3602,3604,3606],{"class":1729,"line":1841},[1727,3600,3601],{"class":2338},"  signedInAt",[1727,3603,1761],{"class":1754},[1727,3605,3550],{"class":1733},[1727,3607,3608],{"class":2648}," \u002F\u002F the earliest live sign-in\n",[1727,3610,3611,3614,3616,3618],{"class":1729,"line":1861},[1727,3612,3613],{"class":2338},"  lastActiveAt",[1727,3615,1761],{"class":1754},[1727,3617,3550],{"class":1733},[1727,3619,3620],{"class":2648}," \u002F\u002F the last refresh, on any device\n",[1727,3622,3623,3626,3628,3630],{"class":1729,"line":1881},[1727,3624,3625],{"class":2338},"  expiresAt",[1727,3627,1761],{"class":1754},[1727,3629,3550],{"class":1733},[1727,3631,3632],{"class":2648}," \u002F\u002F when the last live sign-in ends\n",[1727,3634,3635,3638,3640,3643],{"class":1729,"line":1898},[1727,3636,3637],{"class":2338},"  sessions",[1727,3639,1761],{"class":1754},[1727,3641,3642],{"class":1733}," number",[1727,3644,3645],{"class":2648}," \u002F\u002F live sign-ins (devices, browsers, app installs)\n",[1727,3647,3648],{"class":1729,"line":1914},[1727,3649,2002],{"class":1754},[602,3651,3653],{"id":3652},"the-application-side","The application side",[594,3655,3656,3657,3659,3660,3663,3664,1761],{},"To connect an application, register it in the admin area or with ",[613,3658,2113],{},". Then configure it with the\nclient ID (",[613,3661,3662],{},"app_…",") and the client secret. The whole flow with ",[613,3665,1734],{},[1488,3667,3669],{"className":1721,"code":3668,"language":1723,"meta":1494,"style":1494},"# 1. PKCE pair\nVERIFIER=$(openssl rand -base64 48 | tr -d '=+\u002F' | cut -c1-64)\nCHALLENGE=$(printf %s \"$VERIFIER\" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+\u002F' '-_' | tr -d '=')\n\n# 2. Open in a browser, sign in and approve. You are redirected to the callback with ?code=…&state=…\necho \"https:\u002F\u002Fid.example.com\u002Foauth\u002Fauthorize?client_id=$CLIENT_ID&redirect_uri=https:\u002F\u002Fapp.example.com\u002Fauth\u002Ffrigear&response_type=code&state=xyz&scope=openid%20profile%20email&nonce=n1&code_challenge=$CHALLENGE&code_challenge_method=S256\"\n\n# 3. Exchange the code\ncurl -s https:\u002F\u002Fid.example.com\u002Foauth\u002Ftoken \\\n  -u \"$CLIENT_ID:$CLIENT_SECRET\" \\\n  -d grant_type=authorization_code -d code=\"$CODE\" -d code_verifier=\"$VERIFIER\" \\\n  -d redirect_uri=https:\u002F\u002Fapp.example.com\u002Fauth\u002Ffrigear\n\n# 4. Refresh (the returned refresh_token replaces the old one)\ncurl -s https:\u002F\u002Fid.example.com\u002Foauth\u002Ftoken -u \"$CLIENT_ID:$CLIENT_SECRET\" \\\n  -d grant_type=refresh_token -d refresh_token=\"$REFRESH_TOKEN\"\n\n# 5. Userinfo\ncurl -s https:\u002F\u002Fid.example.com\u002Foauth\u002Fuserinfo -H \"Authorization: Bearer $ACCESS_TOKEN\"\n",[613,3670,3671,3676,3720,3797,3803,3808,3832,3836,3841,3852,3868,3900,3907,3911,3916,3940,3960,3965,3971],{"__ignoreMap":1494},[1727,3672,3673],{"class":1729,"line":1730},[1727,3674,3675],{"class":2648},"# 1. PKCE pair\n",[1727,3677,3678,3681,3684,3687,3690,3693,3696,3698,3701,3703,3705,3708,3710,3712,3715,3718],{"class":1729,"line":1748},[1727,3679,3680],{"class":1744},"VERIFIER",[1727,3682,3683],{"class":1754},"=$(",[1727,3685,3686],{"class":1733},"openssl",[1727,3688,3689],{"class":1737}," rand",[1727,3691,3692],{"class":1737}," -base64",[1727,3694,3695],{"class":1892}," 48",[1727,3697,3590],{"class":1754},[1727,3699,3700],{"class":1733}," tr",[1727,3702,1788],{"class":1737},[1727,3704,2374],{"class":1754},[1727,3706,3707],{"class":1737},"=+\u002F",[1727,3709,2364],{"class":1754},[1727,3711,3590],{"class":1754},[1727,3713,3714],{"class":1733}," cut",[1727,3716,3717],{"class":1737}," -c1-64",[1727,3719,2416],{"class":1754},[1727,3721,3722,3725,3727,3730,3733,3735,3738,3740,3742,3745,3748,3751,3754,3756,3758,3761,3764,3766,3768,3770,3773,3775,3777,3780,3782,3784,3786,3788,3790,3793,3795],{"class":1729,"line":1772},[1727,3723,3724],{"class":1744},"CHALLENGE",[1727,3726,3683],{"class":1754},[1727,3728,3729],{"class":2327},"printf",[1727,3731,3732],{"class":1737}," %s",[1727,3734,1755],{"class":1754},[1727,3736,3737],{"class":1744},"$VERIFIER",[1727,3739,1767],{"class":1754},[1727,3741,3590],{"class":1754},[1727,3743,3744],{"class":1733}," openssl",[1727,3746,3747],{"class":1737}," dgst",[1727,3749,3750],{"class":1737}," -sha256",[1727,3752,3753],{"class":1737}," -binary",[1727,3755,3590],{"class":1754},[1727,3757,3744],{"class":1733},[1727,3759,3760],{"class":1737}," base64",[1727,3762,3763],{"class":1737}," -A",[1727,3765,3590],{"class":1754},[1727,3767,3700],{"class":1733},[1727,3769,2374],{"class":1754},[1727,3771,3772],{"class":1737},"+\u002F",[1727,3774,2364],{"class":1754},[1727,3776,2374],{"class":1754},[1727,3778,3779],{"class":1737},"-_",[1727,3781,2364],{"class":1754},[1727,3783,3590],{"class":1754},[1727,3785,3700],{"class":1733},[1727,3787,1788],{"class":1737},[1727,3789,2374],{"class":1754},[1727,3791,3792],{"class":1737},"=",[1727,3794,2364],{"class":1754},[1727,3796,2416],{"class":1754},[1727,3798,3799],{"class":1729,"line":1841},[1727,3800,3802],{"emptyLinePlaceholder":3801},true,"\n",[1727,3804,3805],{"class":1729,"line":1861},[1727,3806,3807],{"class":2648},"# 2. Open in a browser, sign in and approve. You are redirected to the callback with ?code=…&state=…\n",[1727,3809,3810,3813,3815,3818,3820,3823,3826,3829],{"class":1729,"line":1881},[1727,3811,3812],{"class":2327},"echo",[1727,3814,1755],{"class":1754},[1727,3816,3817],{"class":1737},"https:\u002F\u002Fid.example.com\u002Foauth\u002Fauthorize?client_id=",[1727,3819,1758],{"class":1744},[1727,3821,3822],{"class":1737},"&redirect_uri=https:\u002F\u002Fapp.example.com\u002Fauth\u002Ffrigear&response_type=code&state=xyz&scope=openid%20profile%20email&nonce=n1&code_challenge=",[1727,3824,3825],{"class":1744},"$CHALLENGE",[1727,3827,3828],{"class":1737},"&code_challenge_method=S256",[1727,3830,3831],{"class":1754},"\"\n",[1727,3833,3834],{"class":1729,"line":1898},[1727,3835,3802],{"emptyLinePlaceholder":3801},[1727,3837,3838],{"class":1729,"line":1914},[1727,3839,3840],{"class":2648},"# 3. Exchange the code\n",[1727,3842,3843,3845,3847,3850],{"class":1729,"line":1934},[1727,3844,1734],{"class":1733},[1727,3846,1738],{"class":1737},[1727,3848,3849],{"class":1737}," https:\u002F\u002Fid.example.com\u002Foauth\u002Ftoken",[1727,3851,1745],{"class":1744},[1727,3853,3854,3856,3858,3860,3862,3864,3866],{"class":1729,"line":1954},[1727,3855,1751],{"class":1737},[1727,3857,1755],{"class":1754},[1727,3859,1758],{"class":1744},[1727,3861,1761],{"class":1737},[1727,3863,1764],{"class":1744},[1727,3865,1767],{"class":1754},[1727,3867,1745],{"class":1744},[1727,3869,3870,3872,3875,3877,3880,3882,3885,3887,3889,3892,3894,3896,3898],{"class":1729,"line":1975},[1727,3871,1775],{"class":1737},[1727,3873,3874],{"class":1737}," grant_type=authorization_code",[1727,3876,1788],{"class":1737},[1727,3878,3879],{"class":1737}," code=",[1727,3881,1767],{"class":1754},[1727,3883,3884],{"class":1744},"$CODE",[1727,3886,1767],{"class":1754},[1727,3888,1788],{"class":1737},[1727,3890,3891],{"class":1737}," code_verifier=",[1727,3893,1767],{"class":1754},[1727,3895,3737],{"class":1744},[1727,3897,1767],{"class":1754},[1727,3899,1745],{"class":1744},[1727,3901,3902,3904],{"class":1729,"line":1999},[1727,3903,1775],{"class":1737},[1727,3905,3906],{"class":1737}," redirect_uri=https:\u002F\u002Fapp.example.com\u002Fauth\u002Ffrigear\n",[1727,3908,3909],{"class":1729,"line":2713},[1727,3910,3802],{"emptyLinePlaceholder":3801},[1727,3912,3913],{"class":1729,"line":2718},[1727,3914,3915],{"class":2648},"# 4. Refresh (the returned refresh_token replaces the old one)\n",[1727,3917,3919,3921,3923,3925,3928,3930,3932,3934,3936,3938],{"class":1729,"line":3918},15,[1727,3920,1734],{"class":1733},[1727,3922,1738],{"class":1737},[1727,3924,3849],{"class":1737},[1727,3926,3927],{"class":1737}," -u",[1727,3929,1755],{"class":1754},[1727,3931,1758],{"class":1744},[1727,3933,1761],{"class":1737},[1727,3935,1764],{"class":1744},[1727,3937,1767],{"class":1754},[1727,3939,1745],{"class":1744},[1727,3941,3943,3945,3948,3950,3953,3955,3958],{"class":1729,"line":3942},16,[1727,3944,1775],{"class":1737},[1727,3946,3947],{"class":1737}," grant_type=refresh_token",[1727,3949,1788],{"class":1737},[1727,3951,3952],{"class":1737}," refresh_token=",[1727,3954,1767],{"class":1754},[1727,3956,3957],{"class":1744},"$REFRESH_TOKEN",[1727,3959,3831],{"class":1754},[1727,3961,3963],{"class":1729,"line":3962},17,[1727,3964,3802],{"emptyLinePlaceholder":3801},[1727,3966,3968],{"class":1729,"line":3967},18,[1727,3969,3970],{"class":2648},"# 5. Userinfo\n",[1727,3972,3974,3976,3978,3981,3984,3986,3989,3991],{"class":1729,"line":3973},19,[1727,3975,1734],{"class":1733},[1727,3977,1738],{"class":1737},[1727,3979,3980],{"class":1737}," https:\u002F\u002Fid.example.com\u002Foauth\u002Fuserinfo",[1727,3982,3983],{"class":1737}," -H",[1727,3985,1755],{"class":1754},[1727,3987,3988],{"class":1737},"Authorization: Bearer ",[1727,3990,1783],{"class":1744},[1727,3992,3831],{"class":1754},[594,3994,3995,3996,3999,4000,1350,4002,4004,4005,1761],{},"Verify the ID token with any JOSE library against the ",[598,3997,3998],{"href":87},"JWKS",". Check ",[613,4001,1623],{},[613,4003,1620],{}," (the client\nID) and ",[613,4006,4007],{},"nonce",[1488,4009,4011],{"className":2310,"code":4010,"language":2313,"meta":1494,"style":1494},"import { createRemoteJWKSet, jwtVerify } from 'jose'\n\nconst jwks = createRemoteJWKSet(new URL('https:\u002F\u002Fid.example.com\u002F.well-known\u002Fjwks.json'))\nconst { payload } = await jwtVerify(idToken, jwks, { issuer: 'https:\u002F\u002Fid.example.com', audience: clientId })\n",[613,4012,4013,4042,4046,4078],{"__ignoreMap":1494},[1727,4014,4015,4018,4020,4023,4025,4028,4031,4034,4036,4039],{"class":1729,"line":1730},[1727,4016,4017],{"class":2320},"import",[1727,4019,2354],{"class":1754},[1727,4021,4022],{"class":1744}," createRemoteJWKSet",[1727,4024,2371],{"class":1754},[1727,4026,4027],{"class":1744}," jwtVerify",[1727,4029,4030],{"class":1754}," }",[1727,4032,4033],{"class":2320}," from",[1727,4035,2374],{"class":1754},[1727,4037,4038],{"class":1737},"jose",[1727,4040,4041],{"class":1754},"'\n",[1727,4043,4044],{"class":1729,"line":1748},[1727,4045,3802],{"emptyLinePlaceholder":3801},[1727,4047,4048,4051,4054,4056,4058,4060,4063,4066,4068,4070,4073,4075],{"class":1729,"line":1772},[1727,4049,4050],{"class":1811},"const",[1727,4052,4053],{"class":1744}," jwks ",[1727,4055,3792],{"class":1754},[1727,4057,4022],{"class":2327},[1727,4059,2331],{"class":1744},[1727,4061,4062],{"class":1754},"new",[1727,4064,4065],{"class":2327}," URL",[1727,4067,2331],{"class":1744},[1727,4069,2364],{"class":1754},[1727,4071,4072],{"class":1737},"https:\u002F\u002Fid.example.com\u002F.well-known\u002Fjwks.json",[1727,4074,2364],{"class":1754},[1727,4076,4077],{"class":1744},"))\n",[1727,4079,4080,4082,4084,4087,4089,4091,4094,4096,4099,4101,4104,4106,4108,4111,4113,4115,4117,4119,4121,4124,4126,4129,4131],{"class":1729,"line":1841},[1727,4081,4050],{"class":1811},[1727,4083,2354],{"class":1754},[1727,4085,4086],{"class":1744}," payload ",[1727,4088,2413],{"class":1754},[1727,4090,3538],{"class":1754},[1727,4092,4093],{"class":2320}," await",[1727,4095,4027],{"class":2327},[1727,4097,4098],{"class":1744},"(idToken",[1727,4100,2371],{"class":1754},[1727,4102,4103],{"class":1744}," jwks",[1727,4105,2371],{"class":1754},[1727,4107,2354],{"class":1754},[1727,4109,4110],{"class":2338}," issuer",[1727,4112,1761],{"class":1754},[1727,4114,2374],{"class":1754},[1727,4116,1927],{"class":1737},[1727,4118,2364],{"class":1754},[1727,4120,2371],{"class":1754},[1727,4122,4123],{"class":2338}," audience",[1727,4125,1761],{"class":1754},[1727,4127,4128],{"class":1744}," clientId ",[1727,4130,2413],{"class":1754},[1727,4132,2416],{"class":1744},[4134,4135,4138,4141,4142,4145,4146,4149,4150,4152],"callout",{"color":4136,"icon":4137},"success","i-lucide-check",[839,4139,4140],{},"What you should see:"," step 3 answers with an ",[613,4143,4144],{},"access_token",", an ",[613,4147,4148],{},"id_token"," and a ",[613,4151,778],{},". Step 5 answers\nwith the user's claims for the granted scopes.",[602,4154,41],{"id":4155},"database",[594,4157,4158,4159,4163],{},"Tables the module adds. Generate and commit a ",[598,4160,4162],{"href":4161},"\u002Fget-started\u002Fdatabase#migrations","migration"," after installing.",[594,4165,4166,4167,4170,4171,4174],{},"The module registers four tables through ",[613,4168,4169],{},"registerDatabaseTable",". It leaves ",[613,4172,4173],{},"users"," untouched. The tables store the\nuser ID without a foreign key.",[645,4176,4177,4187],{},[648,4178,4179],{},[651,4180,4181,4184],{},[654,4182,4183],{},"Table",[654,4185,4186],{},"Holds",[670,4188,4189,4221,4247,4257],{},[651,4190,4191,4195],{},[675,4192,4193],{},[613,4194,3104],{},[675,4196,4197,4198,4201,4202,1350,4204,1350,4206,1350,4208,1350,4210,1350,4212,4215,4216,1350,4218,4220],{},"Name, ",[613,4199,4200],{},"clientType",", website and login URL, preview pattern, extra redirect URIs, ",[613,4203,2047],{},[613,4205,2904],{},[613,4207,2935],{},[613,4209,1371],{},[613,4211,3145],{},[613,4213,4214],{},"isListed",", the session lifetime (",[613,4217,3222],{},[613,4219,3226],{},") and the hashed secret.",[651,4222,4223,4228],{},[675,4224,4225],{},[613,4226,4227],{},"oauth_scopes",[675,4229,4230,4231,4233,4234,1350,4237,1350,4239,1350,4241,1350,4243,616,4245,600],{},"The named scopes staff define: ",[613,4232,1367],{}," (the key), ",[613,4235,4236],{},"label",[613,4238,2898],{},[613,4240,2901],{},[613,4242,1615],{},[613,4244,1313],{},[613,4246,3145],{},[651,4248,4249,4254],{},[675,4250,4251],{},[613,4252,4253],{},"oauth_authorization_codes",[675,4255,4256],{},"Single-use codes (hash only), the PKCE challenge, the nonce, the versions of the database scopes consented to and a 10 minute lifetime.",[651,4258,4259,4264],{},[675,4260,4261],{},[613,4262,4263],{},"oauth_refresh_tokens",[675,4265,4266],{},"Hashed refresh tokens, grouped in families for rotation and reuse detection, with the scope versions consented to.",[594,4268,4269,4273,4274,600],{},[839,4270,4271],{},[613,4272,3104],{}," keeps the website empty for a public application without one. A public application stores\nthe hash of a random value nobody sees, so the secret column stays ",[613,4275,4276],{},"NOT NULL",[4278,4279,4280],"note",{},"Client secrets, codes and refresh tokens are stored only as SHA-256 hashes.",[602,4282,395],{"id":4283},"translations",[594,4285,4286,4287,600],{},"Override any string in your app's ",[613,4288,4289],{},"i18n\u002Flocales\u002F\u003Ccode>.json",[594,4291,4292,4293,616,4296,4299,4300,4303,4304,1350,4307,1350,4310,1350,4313,1350,4316,1350,4319,616,4322,4325,4326,600],{},"The module registers ",[613,4294,4295],{},"da",[613,4297,4298],{},"en"," locale files under the ",[613,4301,4302],{},"oauthServer.*"," namespace. They cover the consent, admin, scopes and\napplications pages, in the groups ",[613,4305,4306],{},"menu",[613,4308,4309],{},"admin",[613,4311,4312],{},"form",[613,4314,4315],{},"credentials",[613,4317,4318],{},"applications",[613,4320,4321],{},"scopes",[613,4323,4324],{},"authorize",". Every string\non those pages comes from them. See\n",[598,4327,4329],{"href":4328},"\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fi18n#overriding-module-strings-in-your-app","overriding module strings",[602,4331,4333],{"id":4332},"next-steps","Next steps",[1498,4335,4336,4341,4346,4351,4356],{},[1501,4337,4338,4340],{},[598,4339,136],{"href":137}," – register an app and connect it, step by step.",[1501,4342,4343,4345],{},[598,4344,183],{"href":184}," – sign a single-page or Capacitor app in as a public application.",[1501,4347,4348,4350],{},[598,4349,241],{"href":242}," – let your services accept the access tokens this issuer signs.",[1501,4352,4353,4355],{},[598,4354,1440],{"href":1694}," – what applications may ask users for.",[1501,4357,4358,4360],{},[598,4359,234],{"href":235}," – add claims, veto access and audit tokens.",[4362,4363,4364],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .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 .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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":1494,"searchDepth":1748,"depth":1748,"links":4366},[4367,4371,4372,4373,4378,4384,4387,4388,4389,4390],{"id":604,"depth":1748,"text":605,"children":4368},[4369,4370],{"id":639,"depth":1772,"text":640},{"id":950,"depth":1772,"text":951},{"id":1482,"depth":1748,"text":1483},{"id":1697,"depth":1748,"text":1698},{"id":2096,"depth":1748,"text":1509,"children":4374},[4375,4376,4377],{"id":2186,"depth":1772,"text":2187},{"id":2225,"depth":1772,"text":2226},{"id":2288,"depth":1772,"text":2289},{"id":2518,"depth":1748,"text":2519,"children":4379},[4380,4381,4382,4383],{"id":3073,"depth":1772,"text":3074},{"id":3097,"depth":1772,"text":3098},{"id":3116,"depth":1772,"text":3117},{"id":3158,"depth":1772,"text":3159},{"id":3171,"depth":1748,"text":942,"children":4385},[4386],{"id":3460,"depth":1772,"text":3461},{"id":3652,"depth":1748,"text":3653},{"id":4155,"depth":1748,"text":41},{"id":4283,"depth":1748,"text":395},{"id":4332,"depth":1748,"text":4333},"Every route OAuth Server registers, how a sign-in runs, and how public and configured applications behave.","md",null,{},{"title":179},{"title":589,"description":4391},"HuqvRMzbI6KeWLznbQnF-4vVq6mDBmouZ4jbCK0kYnU",[4399,4401],{"title":234,"path":235,"stem":236,"description":4400,"children":-1},"Decide who may manage OAuth applications, which roles may sign in where and what goes into tokens.",{"title":241,"path":242,"stem":243,"description":4402,"children":-1},"Let a separate service accept the access tokens of an issuer, so one mobile app signs in once and calls every service.",1794503152157]