Secure Your Nuxt 3 App

Secure Your Nuxt 3 App

Nuxt auth logo

Authentication is a crucial aspect of any modern web application. Whether you’re building an e-commerce site, a social platform, or any service requiring user authentication, handling authentication securely and efficiently is key to ensuring a smooth user experience. In this post, we’ll explore how to easily integrate authentication into your Nuxt.js application using the @workmate/nuxt-auth package. We would discuss the local auth provider here, where you can connect to your backend directly. The package also has support for oAuth2 like github and google. To view that, check out the github repository https://github.com/work-mate/nuxt-auth-module

What is @workmate/nuxt-auth? @workmate/nuxt-auth is a package designed to make authentication in Nuxt.js apps seamless. This package provides a straightforward way to add authentication flows, such as login, registration, and user session management, to your Nuxt app.

Installing @workmate/nuxt-auth The first step is to install the package. Open your terminal and navigate to the root of your Nuxt project. Then, run the following command to install the @workmate/nuxt-auth package:

npm install --save @workmate/nuxt-auth

or if you are using yarn:

yarn add @workmate/nuxt-auth

Setting Up the Package in Your Nuxt Project Once the package is installed, you need to configure it in your Nuxt application. Open your nuxt.config.js file and add @workmate/nuxt-auth to the modules array:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    "@workmate/nuxt-auth"
  ],
  ...
});

Set up your auth providers

// nuxt.config.ts
const BACKEND_URL = process.env.BACKEND_BASE_URL || "http://localhost:9000";

export default defineNuxtConfig({
  modules: [ "@workmate/nuxt-auth",],

  auth: {
    global: true,
    redirects: {
      redirectIfLoggedIn: "/dashboard",
      redirectIfNotLoggedIn: "/register", // default is /login
    },
    apiClient: {
      baseURL: BACKEND_URL,
    },
    //token: {
    // type: "Bearer",
    // maxAge: 1000 * 60 * 60 * 24 * 30,
    // cookiesNames: {
    //  accessToken: "auth:token",
    //  refreshToken: "auth:refreshToken",
    //  authProvider: "auth:provider",
    //  tokenType: "auth:tokenType",
    //}
  };
    providers: {
      local: {
        endpoints: {
          signIn: {
            path: `${BACKEND_URL}/api/auth/login`,
            method: "POST",
            tokenKey: "token",
            body: {
              principal: "email",
              password: "password",
            },
          },
          user: {
            path: `${BACKEND_URL}/api/auth/user`,
            userKey: "data",
          },
          signOut: {
            path: `${BACKEND_URL}/api/auth/user`,
            method: "POST",
          },
        },
      },
    },
  },
});

Brief explanation of the config Let's go through the above real quick, the global:true line ensures that the auth middleware is set on every page of your website. And since you won't be authenticated by default, you want to turn it off on the home route like so

// pages/index.vue
<template>
   <main>Landing page</main>
</template>

<script lang="ts" setup>
definePageMeta({
  auth: false,
});
<script>

You can also set the auth to false on pages you want your users to access regardless of whether they are logged in or not like the help page or the about us page.

In the case where you don't want to use the global middleware, add the auth middleware to the pages you want to protect, eg the dashboard

// pages/dashboard.vue
<template>
   <main>Dashboard page</main>
</template>

<script lang="ts" setup>
definePageMeta({
  middleware: "auth",
});
<script>

The signIn endpoint In the above config, the application would make a post request to the endpoint ${BACKEND_URL}/api/auth/login with the body

{
    "email": "email@example.com",
    "password": "password"
}

and would expect data with the format

{
    "token": "auth token",
}

for nested data, use 'period' separated keys eg nested.token.data.

The same foes for the user endpoint and the sign out endpoint.

The api client By default, nuxt would want you to send requests to your frontend domain, meanwhile you might want to send it to another (eg. the backend). This package automatically handles this and also adds the authorization headers to the api client so you don't have to manually do that.

To use the api client, add the apiClient's baseurl to the config as shown in the code above. Then instead of calling useFetch or $fetch in nuxt use

useAuthFetch(url, options)
const { $authFetch } = useNuxtApp();

$authFetch(url, options)

And they have the exact same interface with the useFetch and $fetch apis.

Logging In

// pages/index.vue
<template>
   <form @submit.prevent="submit" class="login-form">
      <div class="form-group">
        <label for="email">Email</label>
        <input
          type="email"
          id="email"
          v-model="email"
          placeholder="Enter your email"
          required
        />
      </div>

      <div class="form-group">
        <label for="password">Password</label>
        <input
          type="password"
          id="password"
          v-model="password"
          placeholder="Enter your password"
          required
        />
      </div>

      <button type="submit">Login</button>
    </form>
</template>

<script lang="ts" setup>
definePageMeta({
  middleware: 'auth-guest',
});

const email = ref();
const password = ref();

const { login } = useAuth();

function submit() {
  login("local", {
    principal: email.value,
    password: password.value,
  }).catch(err => {
     //handle error
  })
}
<script>

The auth guest middleware allows only unauthenticated users to visit a page, which is what we need in the case of the login.

Auth Data In order to get the auth data, you can use the useAuth composable

const {
  loggedIn,
  user,
  token,
  refreshToken,
  login,
  logout,
  refreshUser,
  refreshTokens,
} = useAuth();

// or
const { $auth } = useNuxtApp();
const {
  loggedIn,
  user,
  token,
  refreshToken,
  login,
  logout,
  refreshUser,
  refreshTokens,
} = $auth;

In order to logout, you can use the logout from the above code snippet

logout().then(() => {
  // show logout notification
});