Overview
This is a companion blog post to support the video on a way to integrate Payload CMS 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 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.
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
Installation & Configuration
Create a new Nuxt application
npx nuxi@latest init <project-name>
cd /<project-name>
npm install
Add Nuxt/UI Module Documentation
Install module
npm install @nuxt/ui
Modify Nuxt config
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, 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 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
andsignOut
functions will make these two functions unnecessary.
The Plugin
// 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
hook to access the $payloadAuth
plugin we discussed above.
// 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 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()
<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()
<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>
Top comments (0)