User Auth and Session Management in Nuxt with Sidebase and Strapi v4
Welcome
Project setup
- Nuxt - 3.4.2
- @sidebase/nuxt-auth - 0.5.0
Important Before you start, read through the main Github page of
@sidebase@nuxt-auth
. The documentation is great and the team is very helpful.
In this guide, we'll be adding auth and session management features into our Nuxt application and authenticate with Strapi through the @sidebase/nuxt-auth
module made by sidebase
.
Introduction
Nuxt is a meta framework built on top of Vue.
Strapi is a powerful CMS back-end you can fully customize to your needs. It has a lot of built in creature comforts to start off with.
@sidebase/nuxt-auth
is an authentication module built for use with Nuxt. It's easy, fast, and secure.
We will use @sidebase/nuxt-auth
for managing the sessions and focus on its own great features like protecting pages globally. You can easily create any amount of users on your front-end through Strapi and really focus only on building out your prototype, without needing to spend hours in setting up every single piece. I work with a full stack mindset and I in my opinion the combination of Strapi, Nuxt and @sidebase/nuxt-auth
is just amazing. Spend more time on developing and less on setting up every nitty gritty thing. I like to work smarter, not harder.
The source code for this article is available on GitHub.
The orginal nuxt-auth-example
template can also be found on Github
Creating a New Strapi project
- Run the below command in terminal to start the Strapi installation script.
yarn create strapi-app my-strapi-project --quickstart
// or
npx create-strapi-app@latest my-strapi-project --quickstart
Once installation is complete, your browser automatically opens a new tab. Strapi runs out of the box on http://localhost:1337/
.
After installation we need to create a admin user account.
- Complete the form on the new tab in order to create your own administrator account.
Once completed, you can use that account to sign into your Strapi application. You will be redirected to the admin panel
.
In the admin panel
, navigate to Content Manager
on the top-left main navigation menu. Once on the Content Manager
page click on the User
collection-type. The User
collection type can hold many application-side user's, we will create a new User
record.
- In the right top corner, click on
Create New Entry
. - Fill in all the
User
fields on this new page and remember the password.
username: 'The visible name of the user e.g: Juliette.'
email: 'Email of the user'
password: 'Generate/create a password'
blocked: 'Set this to false'
confirmed: 'Set this to true'
- Save the new
User
.
We will need to save the User account credentials, because we will need to use either the username or the email of the user together with the password we created here to authenticate in Nuxt.
That's all we have to do within our Strapi project. We will now move on to the Nuxt Application side of things.
Creating a New Nuxt Application
Let's start by creating a new Nuxt application. We can create a Nuxt application using the following command in terminal:
npx nuxi init nuxt-auth-strapi
The above command will ask for the name of the project. We'll call it nuxt-auth-strapi
.
Once the setup of the project and installing all dependencies are complete, we can go inside the application-side directory and start the application using the following command:
cd nuxt-auth-strapi && yarn dev
The above command will start the application on http://localhost:3000/
.
This article won't be covering the installation of TailwindCSS. Here is a link to the official TailwindCSS docs for Nuxt to help you install and configure that part yourself.
Installing and Integrating @sidebase/nuxt-auth
with Nuxt and Strapi
In this section, we'll be installing and integrating nuxt-auth
.
- Install the package
yarn add --dev @sidebase/nuxt-auth
// or
npm i -D @sidebase/nuxt-auth
- Add
@sidebase/nuxt-auth
to yourmodules
innuxt.config.ts
file.
export default defineNuxtConfig({
modules: ['@sidebase/nuxt-auth']
})
- Create a
.env
file in the root of your Nuxt project.
ORIGIN=http://localhost:3000
NUXT_SECRET=<-a-better-secret->,
STRAPI_BASE_URL=http://localhost:1337/api
- Add your runtime environment variables, these are private keys that are only available server-side in nitro.
runtimeConfig: {
private: {
NUXT_SECRET: process.env.NUXT_SECRET,
STRAPI_BASE_URL: process.env.STRAPI_BASE_URL,
},
public: {}
}
- Add and configure the
auth
object with the following options.
auth: {
origin: process.env.ORIGIN,
}
},
Your nuxt.config.ts
file should contain the following code:
export default defineNuxtConfig({
runtimeConfig: {
private: {
// The private keys which are only available server-side
NUXT_SECRET: process.env.NUXT_SECRET,
STRAPI_BASE_URL: process.env.STRAPI_BASE_URL,
},
public: {}
},
modules: [
"@sidebase/nuxt-auth",
],
auth: {
origin: process.env.ORIGIN,
},
});
Make sure you have updated your .env file accordingly.
Creating and Integrating the Strapi Credential Flow into our Nuxt Application with Sidebase
In this section, we'll create a custom Credentials Flow for Strapi and integrate it into Nuxt.js and our Strapi application.
First off the Nuxt-Auth module expects all auth requests to be sent to /api/auth/
, all requests will be handled by the NuxtAuthHandler
.
- You'll need to create a folder in the root of your Nuxt project called
server
in thisserver
folder you need to create two more folders calledapi
and inside thatauth
.Folders ./server/api/auth/
- We need to create a catch-all server-route that holds our
NuxtAuthHandler
, create a file called[...].ts
in the auth folder. - Copy the following logic into the newly created
[...].ts
file.
// ~/server/api/auth/[...].ts
import CredentialsProvider from "next-auth/providers/credentials";
import { NuxtAuthHandler } from "#auth";
export default NuxtAuthHandler({
// secret needed to run nuxt-auth in production mode (used to encrypt data)
secret: useRuntimeConfig().private.NUXT_SECRET,
providers: [
// @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point
CredentialsProvider.default({
name: "Credentials",
credentials: {
// We need the credentials object to be present.
// You can leave it empty though.
},
async authorize(credentials: any) {
const response = await $fetch(
`${useRuntimeConfig().private.STRAPI_BASE_URL}/api/auth/local/`,
{
method: "POST",
body: JSON.stringify({
identifier: credentials.username,
password: credentials.password,
}),
}
);
if (response.user) {
const u = {
id: response.id,
name: response.user.username,
// Passing the original JWT through the email field.
// IMPORTANT: Do not pass decoded JWTs,
// always make sure they are encoded!
email: response.jwt
};
return u;
} else {
throw new Error('User not found');
}
},
}),
],
pages: {
signIn: '/auth/signin'
},
});
Note: We feed the original JWT token returned by Strapi in the email field of the Nuxt-Auth user object. This is a separate and internally managed session through a different JWT. Don't use that JWT because that will not work.
The above code is a custom credentials provider that talks with Strapi's local auth flow. You can completely replace the provider and add a lot more options like callbacks to improve this example case.
Sign-in Page & Auth Logic Setup
Custom sign-in page
In the pages
folder create a new folder called auth
and add a new vue page called signin.vue
.
- Copy the following template code
<script setup lang="ts">
const { signIn, signOut, data, status, getSession, getCsrfToken, getProviders } = await useAuth({ required: false })
const providers = await getProviders()
const crsf = await getCsrfToken()
let credentials = reactive({
username: "",
password: "",
});
const logIn = async (e) => {
e.preventDefault()
await signIn('credentials', { callbackUrl: '/protected', redirect: true, username: loginForm.username, password: loginForm.password } )
};
</script>
<template>
<section class="grid grid-cols-2 wrapper my-6 mx-auto items-center justify-center">
<!-- login container -->
<div class="bg-gray-100 flex mx-auto rounded-2xl ease-in-out duration-300 shadow-xl max-w-[60rem] p-5 items-center">
<!-- form -->
<div class=" px-8 md:px-16">
<h2 class="font-bold text-2xl text-[#002D74]">Login</h2>
<p class="text-xs mt-4 text-[#002D74]">Strapi users can login with their username (or e-mail) and password.</p>
<form class="flex flex-col gap-4">
<input name="csrfToken" type="hidden" v-model="credentials.csrfToken" />
<input v-model="credentials.username" class="p-2 mt-8 rounded-xl border" type="text" name="email" placeholder="Email">
<div class="relative">
<input v-model="credentials.password" class="p-2 rounded-xl border w-full" type="password" name="password" placeholder="Password">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="gray" class="bi bi-eye absolute top-1/2 right-3 -translate-y-1/2" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z" />
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z" />
</svg>
</div>
<button class="bg-[#002D74] rounded-xl text-white py-2 hover:scale-105 duration-300" @click="logIn">
Sign in (Credential Flow)
</button>
</form>
</div>
</div>
<div class="max-w-5xl mx-auto mt-5 px-5">
<h3 class="text-xl font-bold ">Authentication Overview</h3>
<pre v-if="status"><span>Status:</span> {{ status }}</pre>
<pre v-if="data"><span>Data:</span> {{ data }}</pre>
<pre v-if="providers"><span>Providers:</span> {{ providers }}</pre>
</div>
</section>
</template>
@sidebase/nuxt-auth
normally generates it's own login page with matching provider styles. For this example I am sending the input field values through the signIn()
helper called from within our custom ./pages/auth/signin
page.
You can directly attach the signIn()
function to the submit button.
@click="signIn('credentials', { callbackUrl: '/protected/globally', username: credentials.username, password: credentials.password})"
Alternatively, we can write a simple function that allows us to also log the response. In the example I've changed the previous example to call the logIn()
function instead, to help you 'wrap' your head around it.
const logIn = async (e) => {
e.preventDefault()
const res = await signIn('credentials', { callbackUrl: '/protected/globally', redirect: true, username: loginForm.username, password: loginForm.password } )
// Will either print status 200 on success or the error you set up in the [...].ts nitro endpoint.
console.log(res)
};
Setting up middleware
Application Side Middleware
- Create a folder called
middleware
in the root of your app. - Create a file in the middleware folder called
auth.global.ts
and add the following code
import { defineNuxtRouteMiddleware } from '#app'
export default defineNuxtRouteMiddleware(async (to) => {
if (to.path.startsWith('/protected/')) {
await useAuth({ callbackUrl: to.path })
}
})
Setting up Nitro for Authenticated Strapi requests.
After logging in with Strapi User
we can use the returned JWT from Strapi to access the authenticated endpoints. Lets a create a new endpoint first and then add the JWT when we call it on our signin page.
Nitro & Strapi Auth Endpoint Access
Create a new nitro server endpoint
- In the
server/api
folder create a new folder calledadmin
./server/api/admin - Create a new server route called
data.get.ts
in theadmin
folder. Copy the following code
export default defineEventHandler(async (event) => {
const settings = {
method: "GET",
headers: {
"Content-Type": "application/json"
},
};
const response = await $fetch(
`${useRuntimeConfig().private.STRAPI_BASE_URL}/api/<your-strapi-content-endpoint>`,
settings
);
return response;
});
First we need to provide Nitro with the application-side fetch function together with session JWT header
We will add some logic to the sign-in page that passes the session JWT headers to Nitro.
- Copy the following code and add it in the script setup part of your
signin
page.
// file: ./pages/auth/signin.vue
const headers = useRequestHeaders(['cookie'])
await $fetch(`/api/admin/data`, {
method: "GET",
headers: { cookie: headers.cookie }
});
Grab the session JWT with getToken()
In the data.get.ts
server route.
- Import the 'getToken' helper.
import { getToken } from "#auth";
- Inside the defineEventHandler add:
const token = await getToken({ event });
Add the original Strapi JWT to your Authorisation header
- Modify the Authorisation header to include the JWT token from the token.
Authorization: "Bearer " + token.email
The entire Auth server endpoint.
The final code in /server/api/admin/data.get.ts
.
import { getToken, getServerSession } from "#auth";
export default defineEventHandler(async (event) => {
const session = await getServerSession(event);
if (!session) return { status: "unauthenticated!" };
const token = await getToken({ event });
const settings = {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token.email
},
};
const response = await $fetch(
`${useRuntimeConfig().private.STRAPI_BASE_URL}/api/<your-strapi-auth-protected-endpoint>`,
settings
);
return response;
});
Change logThank you for the help BracketJohn, helping me work out the kinks of the article!
Update 17-11-22 The Nuxt-Auth module is released out of beta. I added syntax highlighting, @tailwind/typography doesn't support this out of the box so had to work my away around...
Update 22-11-22
- Updated to Nuxt stable production release.
- Optimised and improved the codebase.
- Updated the article to reflect the changes in my example repo
Update 24-11-22
- Updated to the latest version of
nuxt-auth
.- Optimised and improved the codebase by adding the Strapi setup.
- Updated the article to reflect the changes in my example repo
- Trying to fix the vue syntax highlighting ASAP...
Update 25-04-23
- Updated to the latest version of
nuxt-auth
.- Updated to use useAuth() instead of useSession().
- Updated all imports
- Fixed the vue syntax highlighting ˆˆ.
Nuxt Experimental Features: Connect your Turso DB natively with db0 and Drizzle ORM
Explore the enhanced developer experience in Nuxt's nightly build, showcasing improved database setup in Nuxt projects using db0 and Drizzle ORM.
Connect a Turso DB to Your Nuxt Project with Drizzle
Elevate your Nuxt application by connecting it to a Turso database with Drizzle ORM for efficient data management and querying.