Get one dev’s tips for the minimalist approach to authentication in Nuxt 3.
After spending a significant amount of my time working with Nuxt 3 this summer, I had to focus on designing a few authentication systems. I’ve explored various approaches, discovering that some are far simpler and more efficient than others.
In this article, I’ll share with you my most streamlined approach to managing authentication in Nuxt 3. 👩💻
With the transition from Nuxt 2 to Nuxt 3, many developers might notice that familiar packages like @nuxtjs/auth
are no longer viable. Fortunately for us, the Nuxt team is actively developing a new official module for Nuxt 3. 🏋️♀️
In the meantime, there are three main solutions currently available:
Built-in Nuxt 3 utilities: Nuxt 3’s native utilities offer a straightforward, integrated approach to session management and authentication. This is the method I’ll focus on in this article.
nuxt-auth-utils
: A minimalistic module maintained by Sebastien Chopin, nuxt-auth-utils
provides essential tools for session management and OAuth integration. It’s lightweight yet powerful for many projects.
@sidebase/nuxt-auth
: If you’re looking for a more feature-rich solution, this package is a strong contender. It supports OAuth providers, email-based authentication with Magic URLs and more. Leveraging the well-established Auth.js/NextAuth.js ecosystem, it’s ideal for more complex projects.
While both nuxt-auth-utils
and @sidebase/nuxt-auth
are excellent options, they cater to different needs. nuxt-auth-utils
is a more streamlined solution, focusing on core utilities for session and OAuth management. On the other hand, @sidebase/nuxt-auth
offers a broader range of features, including support for multiple authentication providers and enhanced session management capabilities, making it a robust choice for complex applications. 🦹♀️
I haven’t had the chance to use any of these packages in production yet so I won’t cover them in this article. I thought they were worth mentioning, though, as they might be more suitable for your project’s requirements if the solution I’m about to present doesn’t meet your needs. 🤗
This approach is one I frequently use in various applications due to its simplicity and direct integration with Nuxt 3. This solution assumes that your backend handles authentication using tokens, such as JWT, and that these tokens are accessible via an API.
To implement this, you’ll need three components: a simple composable function (or a Pinia store), middleware to handle SSR and an Axios plugin to attach the token to the request headers. While you can use fetch
or any other HTTP client, I prefer Axios due to familiarity. 😅
Let’s start by creating a composable function to manage authentication:
// composable/useAuth.ts
export function useAuth() {
// Custom composable to interact with your API
const api = useApi()
// Define reactive states for user and token
const user = ref<null | User>(null)
const opaqueToken = useState<null | string>('opaqueToken')
// Function to handle login via email
const loginWithEmail = async (params: {
email: string
password: string
}) => {
try {
const { data: { opaqueToken, user } } = await api.auth.loginWithEmail({
email: params.email,
password: params.password,
})
opaqueToken.value = opaqueToken
user.value = user
return { user }
} catch (error) {
return Promise.reject(error)
}
}
// Function to handle login via Google by exchanging the code for an opaque token
const loginWithGoogle = async (params: {
code: string
}) => {
try {
const { data: { opaqueToken, user } } = await api.auth.loginWithGoogle({
code: params.code,
})
opaqueToken.value = opaqueToken
user.value = user
return { user }
}
catch (error) {
return Promise.reject(error)
}
}
// Function to handle logout
const logout = async () => {
try {
await api.auth.logout()
opaqueToken.value = null
user.value = null
} catch (error) {
return Promise.reject(error)
}
}
// Watch for changes in the opaqueToken and sync with a cookie
watch(opaqueToken, (newVal) => {
const cookie = useCookie('opaqueToken')
if (cookie.value !== newVal)
cookie.value = newVal
})
return {
opaqueToken,
user,
loginWithEmail,
loginWithGoogle,
logout,
}
}
By invoking the loginWithEmail
function anywhere in your application, you’ll have access to both the user data and the opaque token.
Now let’s create a global middleware to share the opaque token between the server and the client. As you can see, we are using the useCookie
composable as a proxy to share the opaque token between the server and the client. useState
should be initialized on the server during SSR so it’s available immediately when the client hydrates.
// middleware/opaqueToken.global.ts
// Define a global middleware that will run for every route in the application
export default defineNuxtRouteMiddleware(() => {
// Create or access a reactive state named 'opaqueToken'
// This state will be available throughout the application
const state = useState('opaqueToken')
// Retrieve the value of the 'opaqueToken' cookie and assign it to the 'opaqueToken' state
// This ensures that the state is always in sync with the cookie value
// This synchronization is crucial for maintaining a consistent user session across server and client.
state.value = useCookie('opaqueToken').value
})
This way, the token is always available and synchronized between SSR and CSR, maintaining a consistent user session.
Finally, let’s set up an Axios plugin to automatically include the opaque token in the authorization headers for each request:
// plugins/axios.ts
export default defineNuxtPlugin(() => {
// Retrieve the 'opaqueToken' state
const opaqueToken = useCookie('opaqueToken').value
// Set the 'Authorization' header with the 'opaqueToken' value
axios.interceptors.request.use((config) => {
if (opaqueToken.value) {
config.headers.Authorization = `Bearer ${opaqueToken.value}`
}
return config
})
})
With this plugin, the opaque token is included in the authorization headers for each request, allowing the backend to authenticate the user.
And that’s all you need to implement a basic, yet effective, authentication system using built-in Nuxt 3 utilities! 🧚♀️
Sometimes, the simplest solutions are the most powerful. After countless hours of trial and error, this approach has proven to be the most reliable and easy to implement. Whether you’re building a small project or a more complex application, this method provides a solid foundation for secure and efficient authentication. 🤓
If you found this article helpful, feel free to reach out to me on Twitter @RifkiNada. For more articles and insights, check out my work at www.nadarifki.com. 😉
Thank you for reading! 🙏
Nada is a JavaScript developer who likes to play with UI components to create interfaces with great UX. She specializes in Vue/Nuxt, and loves sharing anything and everything that could help her fellow frontend web developers. Nada also dabbles in digital marketing, dance and Chinese. You can reach her on Twitter @RifkiNada or visit her website, nadarifki.com.