Payload CMS -Authentication in Nuxt Using a Custom Plugin
Tags: payloadcms, nuxt, vue, javascript
Published: 2023-10-03 21:21:34

##Overview
This is a companion blog post to support the video on a way to integrate [Payload CMS](https://payloadcms.com/) SignIn, SignOut and Create Account in a Nuxt JS Application. 

**_This video assumes that you already have a Payload CMS server running. [See Part One of my Payload CMS video series](https://youtu.be/TDd9EedIrGY) to see the server that the Nuxt Application in this video is interacting with._**

>Payload CMS - The best way to build a modern backend + admin UI. No black magic, all TypeScript, and fully open-source, Payload is both an app framework and a headless CMS. 
- https://payloadcms.com/

It includes the code for the necessary pages and the custom plugin for connecting with Payload CMS to get current user and manage the user information; and the middleware for controlling access to application pages

##Video
{% youtube pTEmKsAkngg %}

##Installation & Configuration

###Create a new Nuxt application
```
npx nuxi@latest init <project-name>
cd /<project-name>
npm install
```

###Add Nuxt/UI Module [Documentation](https://ui.nuxt.com/getting-started)

Install module
```
npm install @nuxt/ui
```
Modify Nuxt config
```typescript
export default defineNuxtConfig({
  modules: ['@nuxt/ui']
})
```

##The Code
I have included the main files that need to be modified to get the application running after you delete the `App.vue` file

###The plugin
The main event here is the custom [Nuxt Plugin](https://nuxt.com/docs/guide/directory-structure/plugins), I have written a custom plugin in Nuxt and set it so that it will be the first one run by starting the name with `01`.

The plugin checks for a user using the Payload CMS endpoint `customers/me` to check for a user; the user will be returned if one exists, otherwise null is returned.

The plugin then stores the user information and the authentication information in state variables using the [Nuxt useState](https://nuxt.com/docs/api/composables/use-state#usestate) function.

There are also two additional functions added to the plugin
- `updateUser` sets the current user information after a successful login
- `clearUsers` clears the user information and the authentication information after the user logs out

> As I stated in the video, refactoring the plugin to include the `signIn` and `signOut` functions will make these two functions unnecessary.

The Plugin
```typescript
// nuxt-app/plugins/01.payload-auth.ts
import { Customer } from "~/payload-types";

export interface CurrentUserAuthInfo {
  token: string;
  exp: number;
}

export interface CurrentPayloadUserInfo extends CurrentUserAuthInfo {
  user: Customer;
}

export default defineNuxtPlugin(async () => {
  const currentUser = useState<Customer | null>("currentUser", () => null);
  const userAuthInfo = useState<null | CurrentUserAuthInfo>("authInfo", () => {
    return {
      token: "",
      exp: 0,
    };
  });

  async function getUser() {
    if (currentUser.value) {
      return currentUser.value;
    }
    try {
      const resp = await fetch("http://localhost:3100/api/customers/me", {
        method: "GET",
        credentials: "include",
        headers: {
          ...useRequestHeaders(),
        },
      });

      if (!resp.ok) {
        const errorMsg = (await resp.json())?.errors[0].message;
        throw new Error(errorMsg);
      }
      const userInfo = (await resp.json()) as CurrentPayloadUserInfo;
      console.log(userInfo);
      userAuthInfo.value = {
        token: userInfo.token,
        exp: userInfo.exp,
      };
      currentUser.value = userInfo?.user;
      return userInfo?.user;
    } catch (error: any) {
      console.log("getUser - error", error);
      currentUser.value = null;
      return currentUser.value;
    }
  }

  await getUser();
  console.log("In Payload plugin", currentUser);

  return {
    provide: {
      payloadAuth: {
        currentUser,
        userAuthInfo,
        /**
         * called to make sure we have the current user
         * information set in the composable.
         */
        updateUser: async () => {
          await getUser();
        },
        /**
         * clear user information from the composable
         */
        clearUser: () => {
          currentUser.value = null;
          userAuthInfo.value = null;
        },
      },
    },
  };
});

```

###The Middleware
The middleware `auth.ts` has a single purpose which is to redirect the user to the login page.

It works by accessing the plugin using the [`useNuxtApp`](https://nuxt.com/docs/api/composables/use-nuxt-app#usenuxtapp) hook to access the `$payloadAuth` plugin we discussed above.

```typescript
// nuxt-app/middleware/auth.ts

import { defineNuxtRouteMiddleware } from "#app";

export default defineNuxtRouteMiddleware(async (to, from) => {
  const { $payloadAuth } = useNuxtApp();
  const user = $payloadAuth.currentUser?.value;
  console.log('middleware user', user)
  if (!user) {
    // Redirect to login page
    return navigateTo("/login");
  }
});
```

###The Login Page
The login page uses [NuxtUI](https://ui.nuxt.com/getting-started) to make things look nice, but there is also some verification functionality provided by NuxtUI that we use to make sure we are provided an `email` and `password` value to use with the Payload CMS API call.

Important to notice how we access the plugin after a successful login to update the user information in the plugin with the information from the currently authenticated user by calling `$payloadAuth.updateUser()`

```jsx
<template>
  <UContainer class="mt-6">
    <UCard class="m-4">
      <template #header>
        <h3>Login</h3>
      </template>

      <UForm
        ref="loginInputForm"
        :validate="validate"
        :state="loginInput"
        @submit.prevent="submit"
      >
        <UFormGroup label="Email" name="email">
          <UInput v-model="loginInput.email" />
        </UFormGroup>
        <UFormGroup label="Password" name="password">
          <UInput v-model="loginInput.password" type="password" />
        </UFormGroup>
        <UButton type="submit" class="mt-8"> Submit </UButton>
      </UForm>

      <template #footer />
    </UCard>
  </UContainer>
</template>
<script setup lang="ts">
import { FormError } from "@nuxt/ui/dist/runtime/types/form";
import { ref } from "vue";

const {$payloadAuth} = useNuxtApp();

type LoginInput = {
  email: string;
  password: string;
};
const loginInputForm = ref();
const loginInput = ref<LoginInput>({
  email: "",
  password: "",
});

/**
 * validate form information
 *
 * @param state
 */
const validate = (state: LoginInput): FormError[] => {
  const errors = [];
  if (!state.email) errors.push({ path: "email", message: "Required" });
  if (!state.password) errors.push({ path: "password", message: "Required" });

  return errors;
};

/**
 *
 */
async function submit() {
  try {
    const resp = await fetch("http://localhost:3100/api/customers/login", {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        ...useRequestHeaders()
      },
      body: JSON.stringify({
        email: loginInput.value.email,
        password: loginInput.value.password,
      }),
    });

    if (!resp.ok) {
      const errorMsg = (await resp.json())?.errors[0].message;
      throw new Error(errorMsg);
    }
    const user = await resp.json();
    console.log(user);

    // set user globally
    await $payloadAuth.updateUser()

    // goto home
    await navigateTo("/");
  } catch (error: any) {
    alert("Sign In Error " + error.message);
  }
}
</script>

```

###The Index/Home Page
The home page is really here to show the information from the current user. We get that information from the `$payloadAuth` plugin we created.

We have the `logOut` function that calls the Payload CMS API and then after the logout is completed we use the plugin again to clear out any user information, `$payloadAuth.clearUser()`

```jsx
<template>
  <UContainer class="mt-6">
    HELLO
    <p>{{ $payloadAuth.currentUser }}</p>
    <UButton @click="handleLogout">SIGN OUT</UButton>
  </UContainer>
</template>
<script setup lang="ts">
definePageMeta({
  middleware: ["auth"],
  alias: ["/", "/index"],
});
const {$payloadAuth} = useNuxtApp();

/**
 *
 */
async function handleLogout() {

  try {
    const resp = await fetch("http://localhost:3100/api/customers/logout", {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!resp.ok) {
      const errorMsg = (await resp.json())?.errors[0].message;
      throw new Error(errorMsg);
    }

    // clear user
    $payloadAuth.clearUser()

    // redirect
    navigateTo("/login")

  } catch (error: any) {
    alert("Sign Out Error " + error.message);
  }
}
</script>

```