diff --git a/docs/next/guides/authentication.mdx b/docs/next/guides/authentication.mdx
new file mode 100644
index 000000000..6319d37ef
--- /dev/null
+++ b/docs/next/guides/authentication.mdx
@@ -0,0 +1,197 @@
+---
+slug: /next/guides/auth
+title: Authentication
+description: Use Faust.js' built-in authentication strategies to authenticate users from your WordPress backend
+---
+
+Authentication can be a cumbersome process when building headless WordPress sites. Thankfully, Faust.js takes care of all your authentication needs in your Next.js application.
+
+## How It Works
+
+**TL;DR**: Authentication in Faust.js can be boiled down to 5 main steps:
+
+1. User initiates request to authenticated data
+2. Faust.js facilitates the request for an authorization code from the WPE Headless plugin. This is a short lived token that is used to request a refresh and access token
+3. Faust.js facilitates the request for a refresh and access token from the WPE Headless plugin using the authorization code
+4. Faust.js stores the refresh token in a secure, http only cookie. The refresh token is used to request a new access token when the current one expires
+5. Faust.js stores the access token in memory to be used in subsequent authenticated requests
+
+## Strategies
+
+There are two authentication strategies available in Faust.js: `redirect` and `local`
+
+### Redirect Based Authentication
+
+
+
+
+
+Redirect based authentication is the default strategy in Faust.js. This strategy involves the user being redirected to WordPress to authenticate. Once the user has authenticated, the user is redirected back to the Next.js application with an authorization code that is then used to request a refresh and access token, thus completing the login process.
+
+This strategy is great for use cases where your authenticated users are admins/editors/etc. and do not necessarily need a "white label" login/register experience. Typically, you would use the redirect strategy if your primary reason for authentication is previews.
+
+Since Redirect based authentication is the default authentication method, there is no configuration needed on your end to use it. It comes out of the box, and you'll see it in action when using previews or the `useAuth` hook.
+
+### Local Based Authentication
+
+
+
+
+
+Local Based Authentication is the second strategy available in Faust.js. This strategy involves the user initiating a login request from the Next.js application via the `useLogin` hook. Upon successful login, `useLogin` returns an authorization code that is then used to request a refresh and access token, thus completing the login process.
+
+This strategy is great for use cases where you want to support a more "white label" login/register experience. This strategy routes un-authenticated requests to your specified Next.js login page. In addition, users who wish to login/register will not have to interact with WordPress or the WordPress backend at all, giving you the flexibility to implement and fine tune your user flow.
+
+To use this strategy, you'll need to configure your `faust.config.js` file to use the `local` authentication strategy, in addition to the route of your Next.js login page. Take the following `faust.config.js` file for example:
+
+```tsx title=faust.config.js {15,16}
+import { headlessConfig } from '@faustjs/core';
+
+if (!process.env.NEXT_PUBLIC_WORDPRESS_URL) {
+ console.error(
+ 'You must provide a NEXT_PUBLIC_WORDPRESS_URL environment variable, did you forget to load your .env file?',
+ );
+}
+
+/**
+ * @type {import("@faustjs/core").HeadlessConfig}
+ */
+export default headlessConfig({
+ wpUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL,
+ apiClientSecret: process.env.WP_HEADLESS_SECRET,
+ authType: 'local',
+ loginPagePath: '/login',
+});
+```
+
+We define the `authType` to `local` to indicate to the Faust.js config that we want to use the `local` strategy for authentication. We also define the `loginPagePath` to `/login` to indicate the route of the Next.js login page. This means un-authenticated requests will be redirected to the `/login` route in your Next.js application.
+
+In your `login.tsx` page, you could take advantage of the `useLogin` hook to initiate a login request to the WordPress backend:
+
+```tsx title=pages/login.tsx {9,18}
+import { client } from 'client';
+import { useState } from 'react';
+
+export default function Login() {
+ const { useLogin } = client.auth;
+ const [usernameEmail, setUserNameEmail] = useState('');
+ const [password, setPassword] = useState('');
+
+ const { login, isLoading, data, error } = useLogin();
+
+ const errorMessage = data?.error || error?.message;
+
+ return (
+
+ );
+}
+```
+
+The `useLogin` hook exports an object with the following properties:
+
+- `login`: a function that initiates a request to the WordPress backend for an authorization code that accepts two arguments: `usernameEmail`, which is either the user's username or email as a string, and `password` as a string.
+- `isLoading`: a boolean that indicates whether the login request is in progress.
+- `data`: the response data from the login request.
+- `error`: the error from the login request.
+
+For a more detailed explanation of the `useLogin` hook, see the [`useLogin` hook docs](/docs/next/reference/custom-hooks#uselogin) .
+
+Upon a successful login, a refresh token will be stored in a secure, http only cookie, as well as the access token in memory to use for subsequent authenticated requests. A login request can be confirmed it succeeded by checking for the `code` property in the `data` object.
+
+Additionally, if the login page URL contains a `redirect_uri` query parameter, the user will be redirected to the specified URL upon successful login.
+
+
+
+## Making Authenticated Requests
+
+In the Faust.js client, you can use `useQuery`, `usePost`, `usePage`, etc. to make a request to the WordPress backend.
+
+These are exported from the `client` like so:
+
+```tsx {1,4}
+import { client } from 'client';
+
+export default function Page() {
+ const { useQuery } = client;
+
+ return(
+ ...
+ )
+}
+```
+
+Requests using the above methodology will be un-authenticated.
+
+To make authenticated requests, use the `auth` property exported from the `client`. This is essentially a replica of the `client`, except every request gets called with an access token:
+
+```tsx {1,4,5}
+import { client } from 'client';
+
+export default function Page() {
+ const { useMutation, usePreview, useAuth } = client.auth;
+ const { isLoading, isAuthenticated } = useAuth();
+
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ if (!isAuthenticated) {
+ return You are not authenticated!
;
+ }
+
+ return Authenticated content
;
+}
+```
+
+**Note:** The `useAuth` hook fetches the applicable tokens and ensures that the user is authenticated. Therefore, you should check for `isAuthenticated` prior to making authenticated requests, as doing so too early will result in a request without a valid access token.
diff --git a/docs/next/reference/custom-hooks.mdx b/docs/next/reference/custom-hooks.mdx
index 3bb8d69c1..f1ca1c335 100644
--- a/docs/next/reference/custom-hooks.mdx
+++ b/docs/next/reference/custom-hooks.mdx
@@ -23,8 +23,8 @@ Using the above names, Faust.js is able to apply the following logic to determin
The following is an example of how to use the `usePost` hook with a `postSlug`:
```tsx title=/src/pages/posts/[postSlug].tsx {5,6}
-import { getNextStaticProps } from "@faustjs/next";
-import { client } from "client";
+import { getNextStaticProps } from '@faustjs/next';
+import { client } from 'client';
export default function Page() {
const { usePost } = client;
@@ -42,14 +42,14 @@ export default function Page() {
The above code will also work with `postId` and `postUri` depending upon what URL scheme you want to use. You may also want to fetch a specific post. Doing that might look similar to the following:
```tsx {6-10}
-import { getNextStaticProps } from "@faustjs/next";
-import { GetStaticPropsContext } from "next";
-import { client, PostIdType } from "client";
+import { getNextStaticProps } from '@faustjs/next';
+import { GetStaticPropsContext } from 'next';
+import { client, PostIdType } from 'client';
export default function Page() {
const { usePost } = client;
const post = usePost({
- id: "hello-world",
+ id: 'hello-world',
idType: PostIdType.SLUG,
});
@@ -73,8 +73,8 @@ The `usePosts` hook provides the standard interface for getting a list of posts
The following is an example of how to use the `usePosts` hook with no URL params:
```tsx title=src/pages/index.tsx {6-8,14-16}
-import { getNextStaticProps } from "@faustjs/next";
-import { client } from "client";
+import { getNextStaticProps } from '@faustjs/next';
+import { client } from 'client';
export default function Home() {
const { usePosts } = client;
@@ -98,8 +98,8 @@ export default function Home() {
The code above will get the first 6 posts from the Headless WordPress API. If you want to create a page that would pull posts associated with a specific category you can use the following code:
```tsx title=src/pages/category/[categorySlug].tsx {6-8,14-16}
-import { getNextStaticProps } from "@faustjs/next";
-import { client } from "client";
+import { getNextStaticProps } from '@faustjs/next';
+import { client } from 'client';
export default function Home() {
const { usePosts } = client;
@@ -139,8 +139,8 @@ Using the above names, Faust.js is able to apply the following logic to determin
The following is an example of how to use the `usePage` hook with a `pageUri`:
```tsx title=/src/pages/[...pageUri].tsx {5,6}
-import { getNextStaticProps } from "@faustjs/next";
-import { client } from "client";
+import { getNextStaticProps } from '@faustjs/next';
+import { client } from 'client';
export default function Page() {
const { usePage } = client;
@@ -158,14 +158,14 @@ export default function Page() {
The above code will also work with `pageId` and `pageUri` depending upon what URL scheme you want to use. You may also want to fetch a specific page. Doing that might look similar to the following:
```tsx {6-10}
-import { getNextStaticProps } from "@faustjs/next";
-import { GetStaticPropsContext } from "next";
-import { client, PageIdType } from "client";
+import { getNextStaticProps } from '@faustjs/next';
+import { GetStaticPropsContext } from 'next';
+import { client, PageIdType } from 'client';
export default function Page() {
const { usePost } = client;
const page = usePage({
- id: "hello-world",
+ id: 'hello-world',
idType: PageIdType.SLUG,
});
@@ -180,41 +180,142 @@ export default function Page() {
### `usePreview`
-The `usePreview` hook provides an abstraction around getting a preview `page` or `post` from your Headless WordPress API. WordPress generally uses query parameters for `post` and `page` preview URLs. It also uses the `ID` of the `post` or `page`, so you need to call the Headless WordPress API in a specific way to get the data you need. You also can't use the Next.js URL params scheme with dynamic file names to read query params. Instead you have to pull the query params off the Next.js router and pass them into `usePreview`. `usePreview` expects you to pass in either `pageId` or `postId` depending upon what type of content you are fetching. If you pass in `pageId` then `usePreview` will look for a `page` by `ID`. If you pass in `postId` then `usePreview` will look for a `post` by `ID`.
+The `usePreview` hook provides an abstraction around getting a preview `page` or `post` from the Headless WordPress API. When calling the `usePreview` hook on an appropriate preview page, the hook will determine if the preview is a `page` or `post` and will make a request to retrieve the proper preview content.
The following example shows how to use the `usePreview` hook to render either a `post` or `page` from WordPress:
```tsx title=src/pages/preview.tsx {14-17}
-import { useRouter } from "next/router";
-import { PageComponent } from "./[...pageUri]";
-import type { Page, Post } from "@faustjs/core";
-import { PostComponent } from "./posts/[postSlug]";
-import { client } from "client";
+import { PageComponent } from './[...pageUri]';
+import { PostComponent } from './posts/[postSlug]';
+import { client } from 'client';
export default function Preview() {
- const {
- query: { p, page_id },
- } = useRouter();
- const { usePreview } = client;
- const isPage = !!page_id;
-
- const postOrPage: unknown = usePreview({
- pageId: isPage ? (p as string) : undefined,
- postId: !isPage ? (p as string) : undefined,
- } as any);
-
- if (postOrPage === null) {
+ const { usePreview } = client.auth;
+ const result = usePreview();
+
+ if (client.useIsLoading() || !result) {
+ return loading...
;
+ }
+
+ if (result.type === 'page') {
+ if (!result.page) {
+ return <>Not Found>;
+ }
+
+ return ;
+ }
+
+ if (!result.post) {
return <>Not Found>;
}
- if (isPage) {
- return ;
+ return ;
+}
+```
+
+### `useAuth`
+
+The `useAuth` hook provides a way to guarantee a page's content is only rendered if the user is authenticated. If the user is not authenticated, the page will redirect to the WordPress backend if the `authType` is `redirect`, and to the `loginPagePath` if `authType` is `local`.
+
+The following example shows how to use the `useAuth` hook to render a page only if the user is authenticated:
+
+```tsx title=src/pages/gated-content.tsx {5}
+import { client } from 'client';
+
+export default function Gated() {
+ const { useAuth } = client.auth;
+ const { isLoading, isAuthenticated } = useAuth();
+
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ if (!isAuthenticated) {
+ return You are not authenticated!
;
}
- return ;
+ return Authenticated content
;
+}
+```
+
+`useAuth` exports an object with the following properties:
+
+- `isLoading`: A boolean that indicates whether the `useAuth` function is currently checking if a user is authenticated.
+- `isAuthenticated`: A boolean that indicates whether the user is authenticated.
+
+### `useLogin`
+
+The `useLogin` hook provides an abstraction around obtaining an authorization code and fetching refresh/access tokens from WordPress, thus logging in a user to your Headless frontend.
+
+The following example shows how to use the `useLogin` hook to login a user:
+
+```tsx title=pages/login.tsx {9,18}
+import { client } from 'client';
+import { useState } from 'react';
+
+export default function Login() {
+ const { useLogin } = client.auth;
+ const [usernameEmail, setUserNameEmail] = useState('');
+ const [password, setPassword] = useState('');
+
+ const { login, isLoading, data, error } = useLogin();
+
+ const errorMessage = data?.error || error?.message;
+
+ return (
+
+ );
}
```
+`useLogin` exports an object with the following properties:
+
+- `login`: A function that initiates a request to obtain an authorization code via a GraphQL mutation by the provided `usernameEmail` and `password` arguments. Note that the `usernameEmail` argument is a string that can be either a username or an email address.
+- `isLoading`: A boolean that indicates whether the `login` function is currently fetching an authorization code.
+- `data`: An object that contains the response data from the `login` function.
+- `error`: An object that contains the error data from the `login` function.
+
+When an authorization code is successfully fetched, `useLogin` will facilitate the request to the WPE Headless authorize endpoint to obtain the refresh/access tokens. From there, the tokens are stored properly, and the user is logged in.
+
+Additionally, if the login page URL contains a `redirect_uri` query parameter, the user will be redirected to the `redirect_uri` URL after the login is successful.
+
### Custom Queries and Mutations
GQty publishes the following hooks that can be used for custom queries, mutations, or subscriptions:
diff --git a/internal/website/sidebars.js b/internal/website/sidebars.js
index 7684939c4..2d2ac7bf3 100644
--- a/internal/website/sidebars.js
+++ b/internal/website/sidebars.js
@@ -80,6 +80,11 @@ module.exports = {
label: 'Permalinks',
id: 'next/guides/permalinks',
},
+ {
+ type: 'doc',
+ label: 'Authentication',
+ id: 'next/guides/authentication',
+ },
{
type: 'doc',
label: '404s',
diff --git a/internal/website/static/docs/img/auth-local-flow.png b/internal/website/static/docs/img/auth-local-flow.png
new file mode 100644
index 000000000..318ce4110
Binary files /dev/null and b/internal/website/static/docs/img/auth-local-flow.png differ
diff --git a/internal/website/static/docs/img/auth-redirect-flow.png b/internal/website/static/docs/img/auth-redirect-flow.png
new file mode 100644
index 000000000..5ce05dbdb
Binary files /dev/null and b/internal/website/static/docs/img/auth-redirect-flow.png differ
diff --git a/internal/website/static/docs/video/next/local-auth-flow-previews.mp4 b/internal/website/static/docs/video/next/local-auth-flow-previews.mp4
new file mode 100644
index 000000000..7ce0905cc
Binary files /dev/null and b/internal/website/static/docs/video/next/local-auth-flow-previews.mp4 differ