PageSourceSearch

https://docs-blueprint.vercel.app/assets/js/a05fc616.181e0907.js

js docs-blueprint.vercel.app collected 2026-10-03 06:44:37 UTC 16,586 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[667],{4783:(e,t,n)=>{n.d(t,{A:()=>k});var r=n(9430),o=n(851),a=n(5457),i=n(9322),s=n(2039),u=n(9874),l=n(7071),c=n(1588),d=n(3062);function h(e){var t,n;return null!=(t=null==(n=r.Children.toArray(e).filter(function(e){return"\n"!==e}).map(function(e){if(!e||(0,r.isValidElement)(e)&&((t=e.props)&&"object"==typeof t&&"value"in t))return e;var t;throw new Error("Docusaurus error: Bad <Tabs> child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.')}))?void 0:n.filter(Boolean))?t:[]}function p(e){var t=e.values,n=e.children;return(0,r.useMemo)(function(){var e=null!=t?t:function(e){return h(e).map(function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}})}(n);return function(e){var t=(0,c.XI)(e,function(e,t){return e.value===t.value});if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map(function(e){return e.value}).join(", ")+'" found in <Tabs>. Every value needs to be unique.')}(e),e},[t,n])}function g(e){var t=e.value;return e.tabValues.some(function(e){return e.value===t})}function f(e){var t=e.queryString,n=void 0!==t&&t,o=e.groupId,a=(0,s.W6)(),i=function(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!r)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:n,groupId:o});return[(0,l.aZ)(i),(0,r.useCallback)(function(e){if(i){var t=new URLSearchParams(a.location.search);t.set(i,e),a.replace(Object.assign({},a.location,{search:t.toString()}))}},[i,a])]}function v(e){var t,n,o,a,i=e.defaultValue,s=e.queryString,l=void 0!==s&&s,c=e.groupId,h=p(e),v=(0,r.useState)(function(){return function(e){var t,n=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(n){if(!g({value:n,tabValues:r}))throw new Error('Docusaurus error: The <Tabs> has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+r.map(function(e){return e.value}).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var o=null!=(t=r.find(function(e){return e.default}))?t:r[0];if(!o)throw new Error("Unexpected error: 0 tabValues");return o.value}({defaultValue:i,tabValues:h})}),m=v[0],x=v[1],b=f({queryString:l,groupId:c}),j=b[0],y=b[1],A=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:c}.groupId),n=(0,d.Dv)(t),o=n[0],a=n[1],[o,(0,r.useCallback)(function(e){t&&a.set(e)},[t,a])]),k=A[0],w=A[1],S=function(){var e=null!=j?j:k;return g({value:e,tabValues:h})?e:null}();return(0,u.A)(function(){S&&x(S)},[S]),{selectedValue:m,selectValue:(0,r.useCallback)(function(e){if(!g({value:e,tabValues:h}))throw new Error("Can't select invalid tab value="+e);x(e),y(e),w(e)},[y,w,h]),tabValues:h}}var m=n(1194);const x={tabList:"tabList_sOqc",tabItem:"tabItem_XJt0"};var b=n(5270);function j(e){var t=e.className,n=e.block,r=e.selectedValue,a=e.selectValue,s=e.tabValues,u=[],l=(0,i.a_)().blockElementScrollPositionUntilNextRender,c=function(e){var t=e.currentTarget,n=u.indexOf(t),o=s[n].value;o!==r&&(l(t),a(o))},d=function(e){var t,n=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":var r,o=u.indexOf(e.currentTarget)+1;n=null!=(r=u[o])?r:u[0];break;case"ArrowLeft":var a,i=u.indexOf(e.currentTarget)-1;n=null!=(a=u[i])?a:u[u.length-1]}null==(t=n)||t.focus()};return(0,b.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t),children:s.map(function(e){var t=e.value,n=e.label,a=e.attributes;return(0,b.jsx)("li",Object.assign({role:"tab",tabIndex:r===t?0:-1,"aria-selected":r===t,ref:function(e){u.push(e)},onKeyDown:d,onClick:c},a,{className:(0,o.A)("tabs__item",x.tabItem,null==a?void 0:a.className,{"tabs__item--active":r===t}),children:null!=n?n:t}),t)})})}function y(e){var t=e.lazy,n=e.children,a=e.selectedValue,i=(Array.isArray(n)?n:[n]).filter(
1Boolean);if(t){var s=i.find(function(e){return e.props.value===a});return s?(0,r.cloneElement)(s,{className:(0,o.A)("margin-top--md",s.props.className)}):null}return(0,b.jsx)("div",{className:"margin-top--md",children:i.map(function(e,t){return(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a})})})}function A(e){var t=v(e);return(0,b.jsxs)("div",{className:(0,o.A)(a.G.tabs.container,"tabs-container",x.tabList),children:[(0,b.jsx)(j,Object.assign({},t,e)),(0,b.jsx)(y,Object.assign({},t,e))]})}function k(e){var t=(0,m.A)();return(0,b.jsx)(A,Object.assign({},e,{children:h(e.children)}),String(t))}},5907:(e,t,n)=>{n.d(t,{R:()=>i,x:()=>s});var r=n(9430);const o={},a=r.createContext(o);function i(e){const t=r.useContext(a);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function s(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),r.createElement(a.Provider,{value:t},e.children)}},6230:(e,t,n)=>{n.d(t,{A:()=>i});n(9430);var r=n(851);const o={tabItem:"tabItem_ZHyB"};var a=n(5270);function i(e){var t=e.children,n=e.hidden,i=e.className;return(0,a.jsx)("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n,children:t})}},9084:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>l,default:()=>p,frontMatter:()=>u,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"guides/authentication/sessions-and-access/logout","title":"Logout","description":"Add logout with a Server Action or client hook and understand redirects and cleanup","source":"@site/docs/guides/authentication/sessions-and-access/logout.mdx","sourceDirName":"guides/authentication/sessions-and-access","slug":"/guides/authentication/sessions-and-access/logout","permalink":"/docs/guides/authentication/sessions-and-access/logout","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"sidebar_position":3,"sidebar_label":"Logout","description":"Add logout with a Server Action or client hook and understand redirects and cleanup"},"sidebar":"docsSidebar","previous":{"title":"Route protection","permalink":"/docs/guides/authentication/sessions-and-access/route-protection"},"next":{"title":"Advanced guides","permalink":"/docs/guides/authentication/advanced-guides"}}');var o=n(5270),a=n(5907),i=n(4783),s=n(6230);const u={sidebar_position:3,sidebar_label:"Logout",description:"Add logout with a Server Action or client hook and understand redirects and cleanup"},l="Logout",c={},d=[{value:"Add a logout button",id:"add-a-logout-button",level:2},{value:"Redirects",id:"redirects",level:2},{value:"API request requirements",id:"api-request-requirements",level:2},{value:"Cookies and client state",id:"cookies-and-client-state",level:2},{value:"Error handling",id:"error-handling",level:2},{value:"Security considerations",id:"security-considerations",level:2}];function h(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.header,{children:(0,o.jsx)(t.h1,{id:"logout",children:"Logout"})}),"\n",(0,o.jsxs)(t.p,{children:["Logout clears Blueprint-managed authentication cookies in the current browser. Use a Server Action\nin App Router, or ",(0,o.jsx)(t.code,{children:"useLogout"})," with an API route in Pages Router. These examples use the exports from\nthe ",(0,o.jsx)(t.a,{href:"/docs/guides/authentication/getting-started/app-router",children:"App Router setup"})," and\n",(0,o.jsx)(t.a,{href:"/docs/guides/authentication/getting-started/pages-router",children:"Pages Router setup"}),"."]}),"\n",(0,o.jsx)(t.h2,{id:"add-a-logout-button",children:"Add a logout button"}),"\n",(0,o.jsxs)(i.A,{groupId:"nextjs-router",queryString:"router",defaultValue:"app-router",children:[(0,o.jsxs)(s.A,{value:"app-router",label:"App Router",children:[(0,o.jsxs)(t.p,{children:["Call ",(0,o.jsx)(t.code,{children:"logout"})," inside a Server Action, not while rendering a Server Component. This form needs\nneither an API route nor React Query:"]}),(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-tsx",metastring:'title="app/components/logout-form.tsx"',children:'import { logout } from "@/lib/auth/server";
1\n\nexport function LogoutForm() {\n  async function logoutAction() {\n    "use server";\n    await logout();\n  }\n\n  return (\n    <form action={logoutAction}>\n      <button type="submit">Log out</button>\n    </form>\n  );\n}\n'})}),(0,o.jsx)(t.p,{children:"Keep this component server-side. The action redirects to your configured home page after logout."}),(0,o.jsx)(t.p,{children:"If you need the client hook instead, configure the client exports and providers from the setup\nguide, then expose a Route Handler:"}),(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",metastring:'title="app/api/auth/logout/route.ts"',children:'import { createLogoutHandler } from "@krakentech/blueprint-auth/server";\nimport { authConfig } from "@/lib/auth/config";\n\nexport const POST = createLogoutHandler(authConfig);\n'})}),(0,o.jsxs)(t.p,{children:["Use the Pages Router tab's button in a file with ",(0,o.jsx)(t.code,{children:'"use client"'})," at the top. The same API request\nrequirements below apply."]})]}),(0,o.jsxs)(s.A,{value:"pages-router",label:"Pages Router",children:[(0,o.jsxs)(t.p,{children:["Create the API route at the path configured by ",(0,o.jsx)(t.code,{children:"apiRoutes.logout"}),":"]}),(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",metastring:'title="pages/api/auth/logout.ts"',children:'import { createLogoutHandler } from "@krakentech/blueprint-auth/server";\nimport { authConfig } from "@/lib/auth/config";\n\nexport default createLogoutHandler(authConfig);\n'})}),(0,o.jsxs)(t.p,{children:["Render the button under the setup guide's ",(0,o.jsx)(t.code,{children:"AuthProvider"})," and ",(0,o.jsx)(t.code,{children:"QueryClientProvider"}),". The hook sends\nthe request and handles navigation:"]}),(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-tsx",metastring:'title="components/logout-button.tsx"',children:'import { useLogout } from "@/lib/auth/client";\n\nexport function LogoutButton() {\n  const logout = useLogout();\n\n  return (\n    <div>\n      <button type="button" disabled={logout.isPending} onClick={() => logout.mutate()}>\n        {logout.isPending ? "Logging out\u2026" : "Log out"}\n      </button>\n      {logout.isError && <p role="alert">Logout failed. Please try again.</p>}\n    </div>\n  );\n}\n'})})]})]}),"\n",(0,o.jsx)(t.h2,{id:"redirects",children:"Redirects"}),"\n",(0,o.jsxs)(t.p,{children:["Both ",(0,o.jsx)(t.code,{children:"logout"})," and ",(0,o.jsx)(t.code,{children:"useLogout"})," accept ",(0,o.jsx)(t.code,{children:"nextPage"}),":"]}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsxs)(t.li,{children:["Omit it to use ",(0,o.jsx)(t.code,{children:"appRoutes.home.pathname"}),", localized when configured."]}),"\n",(0,o.jsxs)(t.li,{children:["Set it to an app path such as ",(0,o.jsx)(t.code,{children:'"/login"'})," to choose the destination."]}),"\n",(0,o.jsxs)(t.li,{children:["Set it to ",(0,o.jsx)(t.code,{children:"null"})," to skip the success redirect: ",(0,o.jsx)(t.code,{children:"await logout({ nextPage: null })"})," or\n",(0,o.jsx)(t.code,{children:"useLogout({ nextPage: null })"}),". Update the signed-out UI yourself."]}),"\n"]}),"\n",(0,o.jsx)(t.p,{children:"Redirects are limited to safe, same-origin destinations. An unsafe or invalid destination produces\nno redirect rather than sending the user to another site."}),"\n",(0,o.jsxs)(t.p,{children:["The API handler returns JSON containing ",(0,o.jsx)(t.code,{children:"data.redirectUrl"})," by default; the hook uses that URL to\nnavigate. Direct API callers can send ",(0,o.jsx)(t.code,{children:"enableRedirect: true"})," for an HTTP redirect instead. The hook\ndoes not need this option."]}),"\n",(0,o.jsx)(t.h2,{id:"api-request-requirements",children:"API request requirements"}),"\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.code,{children:"createLogoutHandler"})," accepts ",(0,o.jsx)(t.strong,{children:"POST"}),", not GET, and requires ",(0,o.jsx)(t.code,{children:"Content-Type: application/json"}),".\n",(0,o.jsx)(t.code,{children:"useLogout"})," sends this automatically. Do not link to the logout endpoint or submit a plain HTML form\ndirectly to it."]}),"\n",(0,o.jsxs)(t.p,{children:["Configure ",(0,o.jsx)(t.code,{children:"val
1idation.allowedRequestOrigins"})," or ",(0,o.jsx)(t.code,{children:"ALLOWED_REQUEST_ORIGINS"})," with trusted app origins,\nincluding the scheme and any port. Each request must have an ",(0,o.jsx)(t.code,{children:"Origin"})," or ",(0,o.jsx)(t.code,{children:"Referer"})," header; when both\nare present, both must be trusted. See\n",(0,o.jsx)(t.a,{href:"/docs/guides/authentication/getting-started/pages-router#trusted-request-origins",children:"trusted request origins"}),"\nfor configuration examples."]}),"\n",(0,o.jsx)(t.p,{children:"The Server Action form does not call this handler or run its JSON/origin validation. It uses Next.js\nServer Action request protections instead. Blueprint's API origin allowlist does not configure\nNext.js Server Action origin checks."}),"\n",(0,o.jsx)(t.h2,{id:"cookies-and-client-state",children:"Cookies and client state"}),"\n",(0,o.jsx)(t.p,{children:"When an OAuth ID-token cookie is present, logout first attempts Kraken OAuth logout, then\nrefresh-token revocation if a refresh token is available and the upstream logout succeeds. Upstream\nnetwork or service failures are best effort: they are logged, and local cookie cleanup still runs."}),"\n",(0,o.jsx)(t.p,{children:"Configuration or customization errors still propagate after cleanup is attempted. Cookie lookup or\nremoval failures can prevent complete local logout; do not report these as success."}),"\n",(0,o.jsxs)(t.p,{children:["Only a successful ",(0,o.jsx)(t.code,{children:"useLogout"}
1)," mutation invalidates the session query and then clears its entire\nReact Query client cache. A Server Action or direct API call does not clear that browser cache. If\nyou use either alongside cached private client data, arrange your own cleanup, especially when\nstaying on the page."]}),"\n",(0,o.jsx)(t.h2,{id:"error-handling",children:"Error handling"}),"\n",(0,o.jsxs)(t.p,{children:["The Server Action example lets errors propagate. If you add a ",(0,o.jsx)(t.code,{children:"try/catch"}),", do not swallow Next.js\nredirect errors: call ",(0,o.jsx)(t.code,{children:"unstable_rethrow(error)"})," from ",(0,o.jsx)(t.code,{children:"next/navigation"})," before handling application\nerrors on Next.js 15+. See the\n",(0,o.jsx)(t.a,{href:"/docs/guides/authentication/getting-started/app-router#handling-redirect-errors",children:"setup guide's redirect handling"}),"\nand its Next.js 14 compatibility notes."]}),"\n",(0,o.jsxs)(t.p,{children:["By default, a hook failure replaces the current URL with an ",(0,o.jsx)(t.code,{children:"error"})," query parameter.\n",(0,o.jsx)(t.code,{children:"nextPage: null"})," disables only the success redirect. To customize this behavior, pass\n",(0,o.jsx)(t.code,{children:"onLogoutError"})," to ",(0,o.jsx)(t.code,{children:"AuthProvider"}),"; its ",(0,o.jsx)(t.code,{children:"onLogoutSuccess"})," prop customizes successful navigation.\nThese callbacks receive a function for default navigation. Call it if you want to retain that\nnavigation behavior. See ",(0,o.jsx)(t.a,{href:"/docs/guides/authentication/api-reference#uselogout",children:(0,o.jsx)(t.code,{children:"useLogout"})}),"."]}),"\n",(0,o.jsx)(t.h2,{id:"security-considerations",children:"Security considerations"}),"\n",(0,o.jsx)(t.p,{children:"Clearing cookies ends this browser's local session; it does not guarantee that every previously\nissued JWT is revoked or that every device is signed out. OAuth revocation depends on the upstream\nservice and is best effort. Continue to enforce token verification and expiry on protected requests;\nlogout is not a substitute for those checks."})]})}function p(e={}){const{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.