{"id":222,"date":"2021-06-28T05:32:39","date_gmt":"2021-06-28T11:32:39","guid":{"rendered":"https:\/\/developers.wpengine.com\/blog\/?p=222"},"modified":"2026-02-05T21:00:26","modified_gmt":"2026-02-06T03:00:26","slug":"headless-wordpress-authentication-native-cookies","status":"publish","type":"post","link":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/","title":{"rendered":"Headless WordPress Authentication with Native Cookies"},"content":{"rendered":"\n<p>When developing a traditional, monolithic WordPress site, you typically don&#8217;t give authentication a second thought. WordPress already provides a native cookie-based authentication system that works out of the box.<\/p>\n\n\n\n<p>What you may <em>not<\/em> know is that for a headless WordPress project, it&#8217;s possible to leverage that same authentication system in your decoupled JavaScript frontend app!<\/p>\n\n\n\n<p>In this post, we&#8217;ll explore <a href=\"https:\/\/github.com\/kellenmace\/headless-wordpress-authentication-native-cookies\">a Next.js app<\/a> that uses WordPress&#8217; own native authentication cookies to provide the following functionality:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Log in<\/li><li>Log out<\/li><li>New user sign-ups<\/li><li>Password resets<\/li><li>User profile page<\/li><li>A &#8220;Members&#8221; page with gated content that only authenticated users can access<\/li><li>A &#8220;Create Post&#8221; page where users with the <code>publish_posts<\/code> capability can create new posts, but other users can&#8217;t.<\/li><li>A <code>useAuth()<\/code> custom hook that provides the user&#8217;s <code>loggedIn<\/code> status and user details to the rest of the app via React context.<\/li><li>Helper components to limit certain content to only authenticated\/unauthenticated users.<\/li><\/ol>\n\n\n\n<p>(A <a href=\"https:\/\/www.gatsbyjs.com\/\">Gatsby.js<\/a> port of the codebase also exists, <a href=\"https:\/\/github.com\/kellenmace\/headless-wordpress-authentication-native-cookies-gatsby\">here<\/a>)<\/p>\n\n\n\n<p>To accomplish this, we&#8217;ll leverage the <a href=\"https:\/\/github.com\/funkhaus\/wp-graphql-cors\">WPGraphQL CORS<\/a> plugin, which allows us to tell WordPress to not only accept cookies from the domain where WP is installed, but also from the decoupled frontend app&#8217;s domain. That way, the same cookies can be used to authenticate users on both domains.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Getting Started<\/h2>\n\n\n\n<p>To benefit from this post, you should be familiar with the basics of local <a href=\"https:\/\/wordpress.org\/\">WordPress<\/a> development, <a href=\"https:\/\/www.wpgraphql.com\/\">WPGraphQL<\/a>, <a href=\"https:\/\/reactjs.org\/\">React<\/a>, and <a href=\"https:\/\/www.apollographql.com\/docs\/react\/\">Apollo Client<\/a>.<\/p>\n\n\n\n<p>Here are the steps for getting set up:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">WordPress Backend Setup<\/h3>\n\n\n\n<ol class=\"wp-block-list\" id=\"block-9a8d66a8-e248-42a1-9fd3-4cac36319588\"><li>Set up a local WordPress site and get it running.<\/li><li>Install and activate the <a href=\"https:\/\/www.wpgraphql.com\/\">WPGraphQL<\/a>, <a href=\"https:\/\/github.com\/funkhaus\/wp-graphql-cors\">WPGraphQL CORS<\/a>, and <a href=\"https:\/\/github.com\/kellenmace\/headless-wordpress-email-settings\">Headless WordPress Email Settings<\/a> WordPress plugins.<\/li><li>From the WordPress admin sidebar, go to <code>GraphQL<\/code> &gt; <code>Settings<\/code> and click the <code>CORS Settings<\/code> tab.<\/li><li>Check the checkboxes next to these options:<br>&#8211; <code>Send site credentials<\/code><br>&#8211; <code>Enable login mutation<\/code><br>&#8211; <code>Enable logout mutation<\/code><\/li><li>In the <code>Extend \"Access-Control-Allow-Origin\u201d header<\/code> field, enter <code>http:\/\/localhost:3000<\/code> and click the button to save your changes.<\/li><\/ol>\n\n\n\n<p>The CORS Settings page should now look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2021\/06\/cors-settings-1024x1014.png\" alt=\"Screenshot of CORS Settings page\" class=\"wp-image-225\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Next.js App Setup<\/h3>\n\n\n\n<ol class=\"wp-block-list\" id=\"block-9a8d66a8-e248-42a1-9fd3-4cac36319588\"><li>Clone down the <a href=\"https:\/\/github.com\/kellenmace\/headless-wordpress-authentication-native-cookies\">Next.js app<\/a> repo.<\/li><li>Create a <code>.env.local<\/code> file inside of the app&#8217;s root folder. Open that file in a text editor and paste in <code>NEXT_PUBLIC_WORDPRESS_API_URL=https:\/\/headlesswpcookieauth.local\/graphql<\/code>, replacing <code>headlesswpcookieauth.local<\/code> with the domain for your local WordPress site. This is the endpoint that Apollo Client will use when it sends requests to your WordPress backend.<\/li><li>Run <code>npm install<\/code> (or <code>yarn<\/code>) to install the app&#8217;s NPM dependencies.<\/li><li>Run <code>npm run dev<\/code> to get the server running locally.<\/li><li>You should now be able to visit <a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000\/<\/a> in a web browser and see the app&#8217;s homepage.<\/li><\/ol>\n\n\n\n<p>Alternatively, you can follow the steps for getting set up with <a href=\"https:\/\/github.com\/kellenmace\/headless-wordpress-authentication-native-cookies-gatsby\">the Gatsby.js port of this codebase<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Try it Out!<\/h2>\n\n\n\n<p>Take a minute to explore the app. Try logging in and logging out. You&#8217;ll notice that the top navigation bar re-renders to show different things when you&#8217;re logged in vs. logged out, and that some pages are only accessible when you&#8217;re logged in\/out.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How it Works<\/h2>\n\n\n\n<p>At a high level, our authentication system works like this:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>An unauthenticated user navigates to the Log In page and attempts to log in.<\/li><li>Our app fires off a GraphQL request to the WordPress backend with that user&#8217;s email address and password.<\/li><li>If the credentials are valid, WordPress sends an HttpOnly <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Cookies\">cookie<\/a> back to the client, along with a success response.<\/li><li>Knowing that the user has been logged in successfully, our app is then able to fire off a request to get some basic info about that user and re-render the app to display links and pages meant for authenticated users.<\/li><li>Requests made to the WordPress backend after that point include the auth cookie in the request headers, which is used to authenticate the user.<\/li><\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">A Note on Security<\/h3>\n\n\n\n<p>That &#8220;HttpOnly&#8221; cookie part is important. Some other authentication strategies involve storing an authentication token in <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\">local storage<\/a> or in a cookie that does not have the <code>HttpOnly<\/code> attribute. That means that the site would be vulnerable to a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cross-site_scripting\">cross-site scripting<\/a> (XSS) attack where some rogue client-side JavaScript code would be able to &#8220;reach&#8221; into local storage\/the non-HttpOnly cookie to get the auth token, then make authenticated requests on behalf of the user without their permission. More info on that type of vulnerability can be found <a href=\"https:\/\/blog.codinghorror.com\/protecting-your-cookies-httponly\/\">here<\/a>.<\/p>\n\n\n\n<p>Since our cookie <em>does<\/em> have the <code>HttpOnly<\/code> attribute, it is not accessible at all via client-side JavaScript, and therefore not vulnerable to the type of cross-site scripting (XSS) attack mentioned above. This results in more robust security.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Apollo Client Configuration<\/h3>\n\n\n\n<p>Apollo Client is initialized in <code>lib\/apolloClient.ts<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code language-js\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { ApolloClient, InMemoryCache, createHttpLink } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@apollo\/client\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> link = createHttpLink({\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">uri<\/span>: process.env.NEXT_PUBLIC_WORDPRESS_API_URL,\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">credentials<\/span>: <span class=\"hljs-string\">'include'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>});\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> client = <span class=\"hljs-keyword\">new<\/span> ApolloClient({\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">cache<\/span>: <span class=\"hljs-keyword\">new<\/span> InMemoryCache(),\n<\/span><\/span><span class='shcb-loc'><span>  link\n<\/span><\/span><span class='shcb-loc'><span>});\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>credentials: 'include',<\/code> option is what tells Apollo to send the auth cookie along with every request.<\/p>\n\n\n\n<p>The <code>client<\/code> from that file is then passed as a prop into the <code>ApolloProvider<\/code> component in <code>pages\/_app.tsx<\/code>, which allows us to make GraphQL network requests from anywhere in our app.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>useAuth()<\/code> Hook<\/h3>\n\n\n\n<p>Take a look at <code>hooks\/useAuth.tsx<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> AuthContext = createContext(DEFAULT_STATE);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> GET_USER = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  query getUser {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    viewer {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      databaseId<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      firstName<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      lastName<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      email<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      capabilities<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AuthProvider<\/span>(<span class=\"hljs-params\">{ children }: { children: ReactNode }<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { data, loading, error } = useQuery(GET_USER);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> user = data?.viewer;\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> loggedIn = <span class=\"hljs-built_in\">Boolean<\/span>(user);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> value = {\n<\/span><\/span><span class='shcb-loc'><span>    loggedIn,\n<\/span><\/span><span class='shcb-loc'><span>    user,\n<\/span><\/span><span class='shcb-loc'><span>    loading,\n<\/span><\/span><span class='shcb-loc'><span>    error,\n<\/span><\/span><span class='shcb-loc'><span>  };\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AuthContext.Provider<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{value}<\/span>&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">AuthContext.Provider<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> useAuth = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> useContext(AuthContext);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> useAuth;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When our app first boots up, the <code>GET_USER<\/code> query defined here is fired off to see if the user is authenticated. If they are, <code>loggedIn<\/code> is toggled to <code>true<\/code>. It, along with the <code>user<\/code> data and <code>loading<\/code> and <code>error<\/code> states are passed down to the rest of our app via React <a href=\"https:\/\/reactjs.org\/docs\/context.html\">context<\/a>. This way, any component or hook in our app can use the <code>useAuth()<\/code> hook to access that authentication-related data.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">AuthContent &amp; UnAuthContent Components<\/h3>\n\n\n\n<p>These two components are used to conditionally render parts of our application that should only be shown to authenticated or unauthenticated users. They also redirect users who don&#8217;t meet that criterion to the proper page.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">AuthContent<\/h4>\n\n\n\n<p>Open up <code>components\/AuthContent.tsx<\/code> to see how this component works.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AuthContent<\/span>(<span class=\"hljs-params\">{ children }: { children: ReactNode }<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { loggedIn, loading } = useAuth();\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-comment\">\/\/ Navigate unauthenticated users to Log In page.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!loading &amp;&amp; !loggedIn) {\n<\/span><\/span><span class='shcb-loc'><span>      router.push(<span class=\"hljs-string\">'\/log-in'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  }, &#91;loggedIn, loading, router]);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (loggedIn) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The logic works like this:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>If the app just booted up and we don&#8217;t know whether or not the user is logged in yet, display a &#8220;Loading&#8230;&#8221; message.<\/li><li>If we have determined the user is logged in, display the children nested inside of this component and allow the user to stay on this page.<\/li><li>If we have determined the user is NOT logged in, redirect the user to the Log In page. The children nested inside of this component are never rendered.<\/li><\/ul>\n\n\n\n<p>To see that in action, you can head over to the <code>\/profile<\/code> page. The component for that page (<code>pages\/profile.tsx<\/code>) does this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AuthContent<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ProfileForm<\/span> \/&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">AuthContent<\/span>&gt;<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you try to navigate directly to that page as a logged-in user, you&#8217;ll see a loading message momentarily, followed by the user profile form.<\/p>\n\n\n\n<p>If you try to navigate there directly as a logged-out user though, you&#8217;ll see the loading message momentarily, then you will be redirected to the Log In page to log in.<\/p>\n\n\n\n<p>Obviously, you shouldn&#8217;t hardcode any sensitive information into your JavaScript code, since then it would be accessible to anyone whether they&#8217;re authenticated or not. This component is merely helpful for knowing when it&#8217;s safe to proceed with rendering components that depend on the user being logged in to work properly, such as the <code>ProfileForm<\/code> component.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">UnAuthContent<\/h4>\n\n\n\n<p>This component simply does the opposite of the <code>AuthContent<\/code> component. If the user is unauthenticated, the children nested inside of this component are rendered and the user is allowed to stay on this page. Authenticated users are redirected to the <code>\/members<\/code> page.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Features<\/h2>\n\n\n\n<p>Now that we understand how authentication is being done and how the <code>useAuth()<\/code> hook and <code>AuthContent<\/code> &amp; <code>UnAuthContent<\/code> components can help us, let&#8217;s peruse the app&#8217;s features.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Nav<\/h3>\n\n\n\n<p>This component lives in <code>components\/Nav.tsx<\/code>. it calls <code>useAuth()<\/code> to get the <code>loggedIn<\/code> value for the current user. It uses that to do some conditional rendering; logged-out users are shown some navigation links, whereas logged-in users are shown others.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Log In<\/h3>\n\n\n\n<p>You can navigate to <code>\/log-in<\/code> to see this page, and open <code>pages\/log-in.tsx<\/code> to view its code.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2021\/06\/log-in.gif\" alt=\"Screencast of log in flow\" class=\"wp-image-226\"\/><\/figure>\n\n\n\n<p>The page contents are wrapped in the <code>&lt;UnAuthContent&gt;<\/code> component to ensure that only unauthenticated users can view the Log In form.<\/p>\n\n\n\n<p>You can find the code for the <code>LogInForm<\/code> component in <code>components\/LogInForm.tsx<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> LOG_IN = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  mutation logIn($login: String!, $password: String!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    loginWithCookies(input: {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      login: $login<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      password: $password<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      status<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">LogInForm<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;logIn, { loading, error }] = useMutation(LOG_IN, {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">refetchQueries<\/span>: &#91;\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">query<\/span>: GET_USER }\n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>  });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleSubmit<\/span>(<span class=\"hljs-params\">event: React.FormEvent&lt;HTMLFormElement&gt;<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    event.preventDefault();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> { email, password } = <span class=\"hljs-built_in\">Object<\/span>.fromEntries(data);\n<\/span><\/span><span class='shcb-loc'><span>    logIn({\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">variables<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">login<\/span>: email,\n<\/span><\/span><span class='shcb-loc'><span>        password,\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }).catch(<span class=\"hljs-function\"><span class=\"hljs-params\">error<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(error);\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">method<\/span>=<span class=\"hljs-string\">\"post\"<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      {\/* etc. *\/}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It renders out the form and fires off the <code>logIn<\/code> mutation when the user submits it. It also displays any errors that come back in the response.<\/p>\n\n\n\n<p>If the log in is successful, it tells Apollo Client to refetch the <code>GET_USER<\/code> query (defined in <code>hooks\/useAuth.tsx<\/code>). This results in the user being navigated away from this unauthenticated-users-only page and over to the <code>\/members<\/code> page.<\/p>\n\n\n\n<p>This Log In form asks for the user&#8217;s email address, but you could modify that to instead ask for their WordPress login (a.k.a. their &#8220;username&#8221;) instead, if desired. Either one works with the <code>loginWithCookies<\/code> mutation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Log Out<\/h3>\n\n\n\n<p>If you visit <code>\/log-out<\/code>, the code in <code>pages\/log-out.tsx<\/code> runs.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> LOG_OUT = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  mutation logOut {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    logout(input: {}) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      status<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">LogOut<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;logOut, { called, loading, error, data }] = useMutation(LOG_OUT, {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">refetchQueries<\/span>: &#91;\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">query<\/span>: GET_USER }\n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>  });\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> loggedOut = <span class=\"hljs-built_in\">Boolean<\/span>(data?.logout?.status);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    logOut();\n<\/span><\/span><span class='shcb-loc'><span>  }, &#91;logOut]);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Layout<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Log Out<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      {!called || loading ? (<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Logging out...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      ) : error ? (<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{error.message}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      ) : !loggedOut ? (<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Unable to log out. Please reload the page and try again.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      ) : (<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>You have been logged out.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      )}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Layout<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When the <code>useEffect()<\/code> callback runs, the <code>logOut<\/code> mutation is executed to log the user out.<\/p>\n\n\n\n<p>Different messages are rendered to the page depending on whether the mutation has not yet completed, an error has occurred, or the user was successfully logged out.<\/p>\n\n\n\n<p>This code is implemented so that as soon as the user hits the Log Out page, they are logged out. As an alternative approach, you could have users click a <code>Log Out<\/code> button in your app, execute the <code>logOut<\/code> mutation, and then once they&#8217;re logged out, navigate them to another page and present them with a &#8220;You have been logged out&#8221; confirmation message. Either way works.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">New User Sign-up<\/h3>\n\n\n\n<p>You can visit <code>\/sign-up<\/code> as an unauthenticated user to test out this feature. It allows new users to register an account on the site.<\/p>\n\n\n\n<p>Note that users will only be able to sign up if the <code>Anyone can register<\/code> box is checked on the <code>Settings<\/code> &gt; <code>General<\/code> page in theWordPress admin. Otherwise, if new user registrations are disabled, users will see a &#8220;User registration is currently not allowed&#8221; error message when attempting to submit the form.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2021\/06\/sign-up.gif\" alt=\"\" class=\"wp-image-227\"\/><\/figure>\n\n\n\n<p>The user flow goes like this:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>User visits <code>\/sign-up<\/code>, fills out the form and clicks the button to sign up.<\/li><li>They see a message telling them a confirmation email has been sent to them.<\/li><li>User opens that email and clicks the link, which sends them back to the <code>\/set-password<\/code> page in the Next.js app. The link includes <code>key<\/code> and <code>login<\/code> query string parameters, which are required for setting a user password.<\/li><li>User types their password into the <code>Password<\/code> and <code>Confirm Password<\/code> fields and hits the button to set it.<\/li><li>If an error occurred, such as if the link is old and no longer valid, the user will see error text.<\/li><li>Otherwise, if the new user&#8217;s password was successfully set, they see a <code>Your new password has been set<\/code> confirmation message and a link they can click to go to the Log In page.<\/li><\/ol>\n\n\n\n<p>This is the <code>SignUpForm<\/code> component that provides that functionality:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> REGISTER_USER = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  mutation registerUser(<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $email: String!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $firstName: String!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $lastName: String!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  ) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    registerUser(<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      input: {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        username: $email<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        email: $email<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        firstName: $firstName<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        lastName: $lastName<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    ) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      user {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        databaseId<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">SignUpForm<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;register, { data, loading, error }] = useMutation(REGISTER_USER);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> wasSignUpSuccessful = <span class=\"hljs-built_in\">Boolean<\/span>(data?.registerUser?.user?.databaseId);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleSubmit<\/span>(<span class=\"hljs-params\">event: React.FormEvent&lt;HTMLFormElement&gt;<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    event.preventDefault();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> values = <span class=\"hljs-built_in\">Object<\/span>.fromEntries(data);\n<\/span><\/span><span class='shcb-loc'><span>    register({\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">variables<\/span>: values,\n<\/span><\/span><span class='shcb-loc'><span>    }).catch(<span class=\"hljs-function\"><span class=\"hljs-params\">error<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(error);\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (wasSignUpSuccessful) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">        Thanks! Check your email \u2013 an account confirmation link has been sent to you.<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    )<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  }<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  <span class=\"hljs-keyword\">return<\/span> (<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">method<\/span>=<span class=\"hljs-string\">\"post\"<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\">      {\/* etc. *\/}<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Set Password Link<\/h4>\n\n\n\n<p>The link in this email that users must click to set their password is generated by the <a href=\"https:\/\/github.com\/kellenmace\/headless-wordpress-email-settings\">Headless WordPress Email Settings<\/a> plugin. It follows this format:<\/p>\n\n\n<pre class=\"wp-block-code language-php\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-string\">\"https:\/\/frontend-js-app.com\/set-password\/?key={$key}&amp;login={$login}\"<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When clicked, it will send users to the <code>\/set-password<\/code> page of your frontend JS app where they can set their password. It also includes the <code>key<\/code> and <code>login<\/code> query string parameters, which WordPress requires to set a new password.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Set Password Form<\/h4>\n\n\n\n<p>The <code>SetPasswordForm<\/code> component (<code>components\/SetPasswordForm.tsx<\/code>) that allows the user to set a password looks like this:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> RESET_PASSWORD = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  mutation resetUserPassword(<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $key: String!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $login: String!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $password: String!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  ) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    resetUserPassword(<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      input: {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        key: $key<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        login: $login<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        password: $password<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    ) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      user {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        databaseId<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">SetPasswordForm<\/span>(<span class=\"hljs-params\">{ resetKey: key, login }: Props<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;password, setPassword] = useState(<span class=\"hljs-string\">''<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;passwordConfirm, setPasswordConfirm] = useState(<span class=\"hljs-string\">''<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;clientErrorMessage, setClientErrorMessage] = useState(<span class=\"hljs-string\">''<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;resetPassword, { data, loading, error }] = useMutation(RESET_PASSWORD);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> wasPasswordReset = <span class=\"hljs-built_in\">Boolean<\/span>(data?.resetUserPassword?.user?.databaseId);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleSubmit<\/span>(<span class=\"hljs-params\">event: React.FormEvent&lt;HTMLFormElement&gt;<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    event.preventDefault()\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> isValid = validate();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!isValid) <span class=\"hljs-keyword\">return<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    resetPassword({\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">variables<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        key,\n<\/span><\/span><span class='shcb-loc'><span>        login,\n<\/span><\/span><span class='shcb-loc'><span>        password,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>    }).catch(<span class=\"hljs-function\"><span class=\"hljs-params\">error<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(error);\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">validate<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ etc.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (wasPasswordReset) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Your new password has been set.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/log-in\"<\/span>&gt;<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>&gt;<\/span>Log in<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    );<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  }<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  <span class=\"hljs-keyword\">return<\/span> (<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">method<\/span>=<span class=\"hljs-string\">\"post\"<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\">      {\/* etc. *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When the form is submitted and the field values pass validation, the <code>resetUserPassword<\/code> mutation is executed to perform the password reset.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Password Strength Validation<\/h4>\n\n\n\n<p><code>SetPasswordForm<\/code> contains a <code>validate()<\/code> function that is called before the mutation gets fired off to set the user&#8217;s password. Currently, it ensures that the <code>Password<\/code> and <code>Confirm Password<\/code> values match, and that the password is at least 5 characters long. So just know that with the current implementation, a user would be able to set a very weak password, such as &#8220;12345&#8221;.<\/p>\n\n\n\n<p>If desired, you can use something like <a href=\"https:\/\/www.npmjs.com\/package\/zxcvbn\">zxcvbn<\/a> to enforce strong passwords on the client-side, and\/or a WordPress plugin that enforces strong passwords on the server-side.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Password Resets<\/h3>\n\n\n\n<p>As a logged-out user, click the &#8220;Forgot password?&#8221; link below the Log In form to be navigated to the <code>\/forgot-password<\/code> page.<\/p>\n\n\n\n<p>The password reset user flow goes like this:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>User enters their email address and clicks the <code>Send password reset email<\/code> button.<\/li><li>If an error occurs, such as if there is no user with that email address, error text will be displayed.<\/li><li>If the password reset email was successfully sent, the form is replaced with a success message telling the user to check their email.<\/li><li>User opens that email and clicks the link, which sends them back to the <code>\/set-password<\/code> page in the Next.js app. The link includes <code>key<\/code> and <code>login<\/code> query string parameters, which are required for setting a user password.<\/li><li>User types their password into the <code>Password<\/code> and <code>Confirm Password<\/code> fields and hits the button to set it.<\/li><li>If an error occurred, such as if the link is old and no longer valid, the user will see error text.<\/li><li>Otherwise, if the new user&#8217;s password was successfully set, they see a <code>Your new password has been set<\/code> confirmation message and a link they can click to go to the Log In page.<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2021\/06\/password-reset.gif\" alt=\"\" class=\"wp-image-228\"\/><\/figure>\n\n\n\n<p>You may have noticed that steps 4-7 on this list are identical to steps 3-6 of the New User Sign-up flow. That&#8217;s because the <code>\/set-password<\/code> page and the <code>SetPasswordForm<\/code> component are used for both new user signups and for password resets.<\/p>\n\n\n\n<p>The <code>\/forgot-password<\/code> page component (<code>pages\/forgot-password.tsx<\/code>) renders the <code>SendPasswordResetEmailForm<\/code> component (<code>components\/SendPasswordResetEmailForm.tsx<\/code>).<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> SEND_PASSWORD_RESET_EMAIL = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  mutation sendPasswordResetEmail($username: String!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    sendPasswordResetEmail(<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      input: { username: $username }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    ) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      user {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        databaseId<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">SendPasswordResetEmailForm<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;sendPasswordResetEmail, { loading, error, data }] = useMutation(\n<\/span><\/span><span class='shcb-loc'><span>    SEND_PASSWORD_RESET_EMAIL\n<\/span><\/span><span class='shcb-loc'><span>  );\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> wasEmailSent = <span class=\"hljs-built_in\">Boolean<\/span>(data?.sendPasswordResetEmail?.user?.databaseId);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleSubmit<\/span>(<span class=\"hljs-params\">event: React.FormEvent&lt;HTMLFormElement&gt;<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    event.preventDefault();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> { email } = <span class=\"hljs-built_in\">Object<\/span>.fromEntries(data);\n<\/span><\/span><span class='shcb-loc'><span>    sendPasswordResetEmail({\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">variables<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">username<\/span>: email,\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }).catch(<span class=\"hljs-function\"><span class=\"hljs-params\">error<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(error);\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (wasEmailSent) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span> Please check your email. A password reset link has been sent to you.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">method<\/span>=<span class=\"hljs-string\">\"post\"<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      {\/* etc. *\/}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can see that when the user submits the form, the <code>sendPasswordResetEmail<\/code> mutation is executed. If it is successful, the form gets replaced with the confirmation message telling the user to check their email. They can then click the link in the email to be sent to the <code>\/set-password<\/code> page and perform the reset.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">User Profile Page<\/h3>\n\n\n\n<p>As a logged in user, you can head over to the <code>\/profile<\/code> page to view and edit your user profile information.<\/p>\n\n\n\n<p>The <code>ProfileForm<\/code> component that provides this functionality is in <code>components\/ProfileForm.tsx<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> UPDATE_PROFILE = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  mutation updateProfile(<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $id: ID!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $firstName: String!,<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $lastName: String!,<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    $email: String!<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  ) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    updateUser(input: {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      id: $id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      firstName: $firstName<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      lastName: $lastName<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      email: $email<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      user {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        databaseId<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ProfileForm<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { user } = useAuth();\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { id, firstName, lastName, email } = user <span class=\"hljs-keyword\">as<\/span> User;\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;updateProfile, { data, loading, error }] = useMutation(UPDATE_PROFILE);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> wasProfileUpdated = <span class=\"hljs-built_in\">Boolean<\/span>(data?.updateUser?.user?.databaseId);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleSubmit<\/span>(<span class=\"hljs-params\">event: React.FormEvent&lt;HTMLFormElement&gt;<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    event.preventDefault();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> values = <span class=\"hljs-built_in\">Object<\/span>.fromEntries(data);\n<\/span><\/span><span class='shcb-loc'><span>    updateProfile({\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">variables<\/span>: { id, ...values, },\n<\/span><\/span><span class='shcb-loc'><span>    }).catch(<span class=\"hljs-function\"><span class=\"hljs-params\">error<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(error);\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">method<\/span>=<span class=\"hljs-string\">\"post\"<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      {wasProfileUpdated ? (<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"profile-update-confirmation\"<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          \u2705 Profile details have been updated.<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      ) : null}<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      {\/* etc. *\/}<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When the user submits this form, the <code>updateProfile<\/code> mutation is fired off, which updates their information in the WordPress database. A success message is then displayed at the top of the form.<\/p>\n\n\n\n<p>You can try submitting this form, then viewing that user&#8217;s profile page in the WordPress admin to see the modified first name, last name, and\/or email details reflected there.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Members Page<\/h3>\n\n\n\n<p>This page&#8217;s content is only visible to authenticated users. The app treats this page as the &#8220;home base&#8221; for logged-in users. Users are sent here immediately after logging in, and are also automatically redirected here if they attempt to visit a page that&#8217;s only for logged-out users, such as the <code>\/log-in<\/code> page.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create Post<\/h3>\n\n\n\n<p>You know how I said this blog post is only about authentication? I lied! ????<\/p>\n\n\n\n<p>The <code>\/create-post<\/code> page gets into <em>authorization<\/em>. That is, it checks to see if the user has the <code>publish_posts<\/code> capability. If they do, the <code>CreatePostForm<\/code> component is rendered. If not, a message is displayed to let them know they don&#8217;t have the permissions necessary to create posts.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">CreatePost<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { user } = useAuth();\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> canCreatePosts = <span class=\"hljs-built_in\">Boolean<\/span>(user?.capabilities?.includes(<span class=\"hljs-string\">'publish_posts'<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Layout<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AuthContent<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Create Post<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        {canCreatePosts ? (<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CreatePostForm<\/span> \/&gt;<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        ) : (<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>You don<span class=\"hljs-symbol\">&amp;#39;<\/span>t have the permissions necessary to create posts.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        )}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">AuthContent<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Layout<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>CreatePostForm<\/code> is in <code>components\/CreatePostForm.tsx<\/code> and looks like this:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> CREATE_POST = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  mutation createPost($title: String!, $content: String!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    createPost(input: {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      title: $title<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      content: $content<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      status: PUBLISH<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      post {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        databaseId<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">CreatePostForm<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;createPost, { data, loading, error }] = useMutation(CREATE_POST);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> wasPostCreated = <span class=\"hljs-built_in\">Boolean<\/span>(data?.createPost?.post?.databaseId);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleSubmit<\/span>(<span class=\"hljs-params\">event: React.FormEvent&lt;HTMLFormElement&gt;<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    event.preventDefault();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">new<\/span> FormData(event.currentTarget);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> values = <span class=\"hljs-built_in\">Object<\/span>.fromEntries(data);\n<\/span><\/span><span class='shcb-loc'><span>    createPost({\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">variables<\/span>: values\n<\/span><\/span><span class='shcb-loc'><span>    }).catch(<span class=\"hljs-function\"><span class=\"hljs-params\">error<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(error);\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (wasPostCreated) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Post successfully created.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">method<\/span>=<span class=\"hljs-string\">\"post\"<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      {\/* etc. *\/}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  )<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When the user submits the form, the <code>createPost<\/code> mutation is fired off to create the post.<\/p>\n\n\n\n<p>Go ahead and try to log in as a user with the <code>publish_posts<\/code> capability (WP&#8217;s built-in Author, Editor or Administrator roles should work fine), and submit the form to create a new post. You can then visit the Posts page in the WordPress admin to see your newly created post on the list.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">But&#8230;why?<\/h4>\n\n\n\n<p>You may be wondering: &#8220;Why would I want users who create blog posts to do so from my decoupled JS app? Shouldn&#8217;t they do that from the WordPress admin instead?&#8221;. You&#8217;re right\u2013 having content creators write blog posts from the WordPress admin makes much more sense. You may want users to be able to create some custom post types (CPT) posts from your frontend app, however. Here&#8217;s an example:<\/p>\n\n\n\n<p>Let&#8217;s say I have an educational site where students can create an account, take a course, then leave a review. I register a Review custom post type in WordPress, and register a Student user role, which is assigned the <code>publish_review<\/code> capability. In my decoupled JS application, I set it up so that logged-in students who have the <code>publish_review<\/code> capability are able to fill out a Course Review form. When they submit the form, a new Review CPT post is created in WordPress to capture that information. The student is then presented with a &#8220;Thanks for leaving a review!&#8221; message.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Shared Cookies &amp; Admin Access<\/h2>\n\n\n\n<p>You might have noticed that:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>When you&#8217;re logged into the frontend JS app, you&#8217;re also logged into the WordPress admin and vice versa.<\/li><li>When you&#8217;re logged out of the frontend JS app, you&#8217;re also logged out of the WordPress admin and vice versa.<\/li><\/ul>\n\n\n\n<p>This happens because the same cookie is being used in both places to authenticate you.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Control WordPress Admin Access<\/h3>\n\n\n\n<p>If you want to use native WordPress cookies to authenticate the users of your frontend JS app, but never want them to be able to log into the WordPress admin, you can use this WordPress plugin:<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/kellenmace\/headless-wp-admin-access\">Headless WordPress Admin Access<\/a><\/p>\n\n\n\n<p>You can follow the steps in the readme to lock down access to the WordPress admin to only users with a certain role or capability.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">CRUD Operations<\/h2>\n\n\n\n<p>We saw how on the <code>\/create-post<\/code> page, our app allows users to create new posts. If you want to go further than that and build an app where users can view, create, edit, and delete Custom Post Type posts, check out this other video of mine that covers how to do CRUD (create, read, update, delete) operations in headless WordPress:<br><a href=\"https:\/\/www.youtube.com\/watch?v=o-MQSKErREI\">Post Type CRUD Operations for WPGraphQL<\/a><br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to Use in Production<\/h2>\n\n\n\n<p>In order to use this authentication method in production, you need to do the following:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">WordPress Backend<\/h3>\n\n\n\n<ol class=\"wp-block-list\"><li>Install and activate the same plugins mentioned in the WordPress Backend Setup section, above.<\/li><li>From the WordPress admin sidebar, go to <code>Graphql<\/code> > <code>Settings<\/code> and click the <code>CORS Settings<\/code> tab.<\/li><li>Check the checkboxes next to these options:<br>&#8211; <code>Send site credentials<\/code><br>&#8211; <code>Enable login mutation<\/code><br>&#8211; <code>Enable logout mutation<\/code><\/li><li>In the <code>Extend \"Access-Control-Allow-Origin\u201d header<\/code> field, enter the URL of your decoupled frontend JS app and click the button to save your changes.<\/li><\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Next.js Frontend<\/h3>\n\n\n\n<ol class=\"wp-block-list\"><li>In the UI that your frontend JS app hosting company provides, define an environment variable named <code>NEXT_PUBLIC_WORDPRESS_API_URL<\/code>. Set its value to the GraphQL endpoint for your headless WordPress backend. For example: <code>https:\/\/api.my-cool-site.com\/graphql<\/code><\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>I hope this blog post and the code repos provided give you a strong foundation for building headless WordPress projects that use WordPress&#8217; native cookies for authentication.<\/p>\n\n\n\n<p>Please reach out to let us know what cool things you&#8217;re able to create!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When developing a traditional, monolithic WordPress site, you typically don&#8217;t give authentication a second thought. WordPress already provides a native cookie-based authentication system that works out of the box. What [&hellip;]<\/p>\n","protected":false},"author":8,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_EventAllDay":false,"_EventTimezone":"","_EventStartDate":"","_EventEndDate":"","_EventStartDateUTC":"","_EventEndDateUTC":"","_EventShowMap":false,"_EventShowMapLink":false,"_EventURL":"","_EventCost":"","_EventCostDescription":"","_EventCurrencySymbol":"","_EventCurrencyCode":"","_EventCurrencyPosition":"","_EventDateTimeSeparator":"","_EventTimeRangeSeparator":"","_EventOrganizerID":[],"_EventVenueID":[],"_OrganizerEmail":"","_OrganizerPhone":"","_OrganizerWebsite":"","_VenueAddress":"","_VenueCity":"","_VenueCountry":"","_VenueProvince":"","_VenueState":"","_VenueZip":"","_VenuePhone":"","_VenueURL":"","_VenueStateProvince":"","_VenueLat":"","_VenueLng":"","_VenueShowMap":false,"_VenueShowMapLink":false,"footnotes":""},"categories":[23],"tags":[],"class_list":["post-222","post","type-post","status-publish","format-standard","hentry","category-headless"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Headless WordPress Authentication with Native Cookies - Builders<\/title>\n<meta name=\"description\" content=\"Headless WordPress Authentication with Native Cookies\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Headless WordPress Authentication with Native Cookies - Builders\" \/>\n<meta property=\"og:description\" content=\"Headless WordPress Authentication with Native Cookies\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/\" \/>\n<meta property=\"og:site_name\" content=\"Builders\" \/>\n<meta property=\"article:published_time\" content=\"2021-06-28T11:32:39+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-06T03:00:26+00:00\" \/>\n<meta name=\"author\" content=\"Kellen Mace\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:site\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Kellen Mace\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/\"},\"author\":{\"name\":\"Kellen Mace\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/e6e62698d757a8421cc9723ffa8b1be3\"},\"headline\":\"Headless WordPress Authentication with Native Cookies\",\"datePublished\":\"2021-06-28T11:32:39+00:00\",\"dateModified\":\"2026-02-06T03:00:26+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/\"},\"wordCount\":2970,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2021\\\/06\\\/cors-settings-1024x1014.png\",\"articleSection\":[\"Headless\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/\",\"name\":\"Headless WordPress Authentication with Native Cookies - Builders\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2021\\\/06\\\/cors-settings-1024x1014.png\",\"datePublished\":\"2021-06-28T11:32:39+00:00\",\"dateModified\":\"2026-02-06T03:00:26+00:00\",\"description\":\"Headless WordPress Authentication with Native Cookies\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#primaryimage\",\"url\":\"\",\"contentUrl\":\"\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/headless-wordpress-authentication-native-cookies\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Headless WordPress Authentication with Native Cookies\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"name\":\"Builders\",\"description\":\"Reimagining the way we build with WordPress.\",\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\",\"name\":\"WP Engine\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"contentUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"width\":348,\"height\":68,\"caption\":\"WP Engine\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/wpebuilders\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCh1WuL54XFb9ZI6m6goFv1g\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/e6e62698d757a8421cc9723ffa8b1be3\",\"name\":\"Kellen Mace\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g\",\"caption\":\"Kellen Mace\"},\"description\":\"Kellen Mace is the Manager of the Developer Relations team at WP Engine. He likes building modern web apps with SvelteKit, TypeScript, Tailwind, and AI tools.\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/author\\\/kellen-macewpengine-com\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Headless WordPress Authentication with Native Cookies - Builders","description":"Headless WordPress Authentication with Native Cookies","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/","og_locale":"en_US","og_type":"article","og_title":"Headless WordPress Authentication with Native Cookies - Builders","og_description":"Headless WordPress Authentication with Native Cookies","og_url":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/","og_site_name":"Builders","article_published_time":"2021-06-28T11:32:39+00:00","article_modified_time":"2026-02-06T03:00:26+00:00","author":"Kellen Mace","twitter_card":"summary_large_image","twitter_creator":"@wpebuilders","twitter_site":"@wpebuilders","twitter_misc":{"Written by":"Kellen Mace","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#article","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/"},"author":{"name":"Kellen Mace","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/e6e62698d757a8421cc9723ffa8b1be3"},"headline":"Headless WordPress Authentication with Native Cookies","datePublished":"2021-06-28T11:32:39+00:00","dateModified":"2026-02-06T03:00:26+00:00","mainEntityOfPage":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/"},"wordCount":2970,"commentCount":0,"publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"image":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#primaryimage"},"thumbnailUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2021\/06\/cors-settings-1024x1014.png","articleSection":["Headless"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/","url":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/","name":"Headless WordPress Authentication with Native Cookies - Builders","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/#website"},"primaryImageOfPage":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#primaryimage"},"image":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#primaryimage"},"thumbnailUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2021\/06\/cors-settings-1024x1014.png","datePublished":"2021-06-28T11:32:39+00:00","dateModified":"2026-02-06T03:00:26+00:00","description":"Headless WordPress Authentication with Native Cookies","breadcrumb":{"@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#primaryimage","url":"","contentUrl":""},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/builders\/headless-wordpress-authentication-native-cookies\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/builders\/"},{"@type":"ListItem","position":2,"name":"Headless WordPress Authentication with Native Cookies"}]},{"@type":"WebSite","@id":"https:\/\/wpengine.com\/builders\/#website","url":"https:\/\/wpengine.com\/builders\/","name":"Builders","description":"Reimagining the way we build with WordPress.","publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/wpengine.com\/builders\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/wpengine.com\/builders\/#organization","name":"WP Engine","url":"https:\/\/wpengine.com\/builders\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/","url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","contentUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","width":348,"height":68,"caption":"WP Engine"},"image":{"@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/wpebuilders","https:\/\/www.youtube.com\/channel\/UCh1WuL54XFb9ZI6m6goFv1g"]},{"@type":"Person","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/e6e62698d757a8421cc9723ffa8b1be3","name":"Kellen Mace","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g","caption":"Kellen Mace"},"description":"Kellen Mace is the Manager of the Developer Relations team at WP Engine. He likes building modern web apps with SvelteKit, TypeScript, Tailwind, and AI tools.","url":"https:\/\/wpengine.com\/builders\/author\/kellen-macewpengine-com\/"}]}},"_links":{"self":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/222","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/comments?post=222"}],"version-history":[{"count":0,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/222\/revisions"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/media?parent=222"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/categories?post=222"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/tags?post=222"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}