@workmate/nuxt-auth

Authentication module for Nuxt 3 and Nuxt 4 with local credentials, GitHub, and Google OAuth — Zod schema validation, refresh tokens, cookie management, route middleware, and typed composables.
2,980 8
Install
npm install @workmate/nuxt-auth
Latest Version:3.0.1
Vue:3
License:MIT
Last Updated:Oct 12, 2026
Links: GitHub  ·  npm
Maintainer: dev-davexoyinbo

@workmate/nuxt-auth

NPM npm GitHub last commit

Auth module for Nuxt 3 & 4 apps. Supports local credentials, GitHub OAuth, and Google OAuth with Zod schema validation.

Providers

Provider Key Status
Local local ✅
GitHub github ✅
Google google ✅
Facebook facebook 🚧
LinkedIn linkedin 🚧

Installation

npm install @workmate/nuxt-auth zod
# or
yarn add @workmate/nuxt-auth zod

zod is a peer dependency. The module needs zod@^4.2.0.

Add to nuxt.config.ts:

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

Configuration

// nuxt.config.ts
import { z } from "zod";

const userSchema = z.object({
  id: z.string(),
  email: z.email(),
  name: z.string().optional(),
});

export default defineNuxtConfig({
  modules: ["@workmate/nuxt-auth"],
  auth: {
    global: false,
    redirects: {
      redirectIfNotLoggedIn: "/login",
      redirectIfLoggedIn: "/",
    },
    apiClient: {
      baseURL: "http://localhost:8080",
    },
    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: "/api/auth/login/password",
            method: "POST",
            tokenKey: "token",
            refreshTokenKey: "refresh_token",
          },
          signOut: { path: "/api/auth/logout", method: "POST" },
          user: { path: "/api/auth/user", userKey: "user" },
          refreshToken: {
            path: "/api/auth/refresh",
            method: "POST",
            tokenKey: "token",
            refreshTokenKey: "refresh_token",
            body: { token: "token", refreshToken: "refresh_token" },
          },
        },
        schemas: {
          login: () => z.object({
            email_address: z.email(),
            password: z.string().min(8),
          }),
          user: () => userSchema,
        },
      },

      github: {
        CLIENT_ID: process.env.GITHUB_CLIENT_ID || "",
        CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET || "",
        HASHING_SECRET: process.env.HASHING_SECRET || "",
        SCOPES: "user repo",
        schemas: { user: () => userSchema },
      },

      google: {
        CLIENT_ID: process.env.GOOGLE_CLIENT_ID || "",
        CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET || "",
        HASHING_SECRET: process.env.HASHING_SECRET || "",
        SCOPES:
          "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
        schemas: { user: () => userSchema },
      },
    },
  },
});

Module options reference

interface ModuleOptions {
  global: boolean;
  redirects: {
    redirectIfNotLoggedIn?: string;
    redirectIfLoggedIn?: string;
  };
  apiClient: {
    baseURL: string;
    credentials?: RequestCredentials;
  };
  token: {
    type: string;
    maxAge: number;
    cookiesNames: {
      accessToken: string;
      refreshToken: string;
      authProvider: string;
      tokenType: string;
    };
  };
  providers: {
    local?: LocalAuthInitializerOptions;
    github?: GithubAuthInitializerOptions;
    google?: GoogleAuthInitializerOptions;
  };
}

type HttpMethod =
  | "GET"
  | "HEAD"
  | "PATCH"
  | "POST"
  | "PUT"
  | "DELETE"
  | "CONNECT"
  | "OPTIONS"
  | "TRACE";

type LocalAuthInitializerOptions = {
  endpoints?: {
    signIn?: {
      path?: string;
      method?: HttpMethod;
      tokenKey?: string;
      refreshTokenKey?: string;
    };
    signOut?: { path: string; method: HttpMethod } | false;
    signUp?: { path?: string; method?: HttpMethod } | false;
    user?: { path: string; userKey: string } | false;
    refreshToken?:
      | {
          path: string;
          method: HttpMethod;
          tokenKey: string;
          refreshTokenKey: string;
          body: { token: string; refreshToken: string };
        }
      | false;
  };
  schemas?: {
    login?: () => ZodType;
    user?: () => ZodType;
  };
};

type GithubAuthInitializerOptions = {
  CLIENT_ID: string;
  CLIENT_SECRET: string;
  HASHING_SECRET: string;
  SCOPES?: string;
  schemas?: { user?: () => ZodType };
};

type GoogleAuthInitializerOptions = {
  CLIENT_ID: string;
  CLIENT_SECRET: string;
  HASHING_SECRET: string;
  SCOPES?: string;
  schemas?: { user?: () => ZodType };
};

Server routes

The module registers these routes on your Nuxt server:

Method Path Description
POST /api/auth/login Login with any provider
POST /api/auth/logout Clear session and cookies
GET /api/auth/user Get the current authenticated user
POST /api/auth/refresh Refresh access token
GET /api/auth/callback/github GitHub OAuth callback (if configured)
GET /api/auth/callback/google Google OAuth callback (if configured)

POST /api/auth/login

// Request body
{
  provider: string;          // "local" | "github" | "google"
  [key: string]: https://github.com/work-mate/nuxt-auth-module/blob/HEAD/any;        // provider-specific fields (e.g. email_address, password)
}

// Response — local provider
{
  tokens: {
    accessToken: string;
    refreshToken?: string;
    tokenType: string;
    provider: string;
  };
}

// Response — OAuth providers
{
  url: string;               // redirect to this URL to begin OAuth flow
}

POST /api/auth/logout

No request body. Clears all auth cookies and calls the provider's logout endpoint if configured.

GET /api/auth/user

No request body. Reads token from cookies.

// Response
{
  user: AuthUser | null;
}

POST /api/auth/refresh

No request body. Reads tokens from cookies, calls the provider's refresh endpoint.

// Response
{
  tokens: {
    accessToken: string;
    refreshToken?: string;
    tokenType: string;
    provider: string;
  };
}

OAuth callbacks

Add <base_url>/api/auth/callback/github and <base_url>/api/auth/callback/google as the callback URLs in your OAuth app settings.


Composables

All composables are auto-imported.

useAuth()

Low-level access to the full auth plugin. Prefer the focused composables below for most use cases.

const {
  isLoggedIn,
  user,
  token,
  refreshToken,
  tokenType,
  provider,
  tokenNames,
  login,
  logout,
  refreshUser,
  refreshTokens,
} = useAuth();

// login(provider, data?, redirectTo?)
await login("local", { email_address: "me@example.com", password: "secret" });
await login("github");

// logout(redirectTo?)
await logout("/login");

useAuthLogin()

Typed login functions. The local provider accepts any object — whatever you pass is sent directly to your signIn endpoint. If schemas.login is configured, the object is validated against it first; if not, it is forwarded as-is. You control the field names.

const { localLogin, googleLogin, githubLogin } = useAuthLogin();

// localLogin(opts, redirectTo?)
// opts shape comes entirely from your schemas.login Zod schema (or Record<string, any> if no schema)
await localLogin({ email_address: "me@example.com", password: "secret123" });
await localLogin({ username: "alice", pin: "1234" }); // different backend, different fields
await localLogin(
  { email_address: "me@example.com", password: "secret123" },
  "/dashboard",
);

// googleLogin(opts?, redirectTo?)
await googleLogin();
await googleLogin({ redirectUrl: "/dashboard" });

// githubLogin(opts?, redirectTo?)
await githubLogin();

useAuthUser()

const { user, isLoggedIn, refreshUser } = useAuthUser();

// user: ComputedRef<AuthUser | null | undefined>
// isLoggedIn: ComputedRef<boolean>

if (isLoggedIn.value) {
  console.log(user.value);
}

await refreshUser(); // re-fetches user from the server

useAuthToken()

const { token, refreshToken, tokenType, provider, tokenNames, refreshTokens } =
  useAuthToken();

// All values are computed refs
console.log(token.value); // current access token
console.log(provider.value); // "local" | "github" | "google"

await refreshTokens(); // exchange refresh token for new tokens

useAuthFetch()

Wrapper around Nuxt's useFetch that automatically injects the Authorization header. Retries once on 401 after refreshing tokens. It has the same arguments and return type as useFetch.

const { data, error } = await useAuthFetch("/api/profile");

// Supports all useFetch options
const { data } = await useAuthFetch<User>("/api/profile", {
  method: "POST",
  body: { name: "Alice" },
});

Plugins

$auth

The full auth plugin, same as useAuth().

const { $auth } = useNuxtApp();

if ($auth.isLoggedIn.value) {
  await $auth.logout();
}

$authFetch

An ofetch instance pre-configured with your apiClient.baseURL, the current Authorization header, and automatic token refresh on 401. Use this instead of $fetch for authenticated requests.

const { $authFetch } = useNuxtApp();

const data = await $authFetch("/api/protected");

To set the base URL:

auth: {
  apiClient: {
    baseURL: "http://localhost:8080/v1",
  },
}

Set apiClient.credentials to "include" when the API is on another origin and requests must carry cookies (for example load-balancer sticky-session cookies). The API must then respond with Access-Control-Allow-Credentials: true and an exact Access-Control-Allow-Origin — a * wildcard is rejected by browsers for credentialed requests.

auth: {
  apiClient: {
    baseURL: "https://api.example.com",
    credentials: "include",
  },
}

Middlewares

Route middleware

Protect a page (requires login):

// pages/dashboard.vue
definePageMeta({
  middleware: "auth",
});

Guest-only page (redirect away if logged in):

// pages/login.vue
definePageMeta({
  middleware: "auth-guest",
});

Global middleware

Apply auth to all routes:

// nuxt.config.ts
auth: {
  global: true,
}

Opt a page out when global is enabled:

definePageMeta({
  auth: false,
});

Redirects are configured under auth.redirects:

  • redirectIfNotLoggedIn — where auth middleware sends unauthenticated users (default: "/login")
  • redirectIfLoggedIn — where auth-guest middleware sends authenticated users (default: "/")

Schema validation

Schemas are defined in nuxt.config.ts per provider using Zod 4.2 or later. They are converted to JSON Schema at build time and reconstructed at runtime — no serialization issues.

Each schema is a function that returns the Zod schema. Nuxt 4.5 and later deep-clone nuxt.config.ts, and a Zod schema that is put directly in the config stops the build with Cannot read properties of undefined (reading 'checks').

// nuxt.config.ts
import { z } from "zod";

local: {
  schemas: {
    login: () => z.object({ email_address: z.email(), password: z.string().min(8) }),
    user: () => z.object({ id: z.string(), email: z.email(), name: z.string().optional() }),
  },
}

schemas.login — the local provider sends whatever object you pass directly to your signIn endpoint. There are no fixed field names — use whatever your backend expects (email_address, username, phone, etc.). If schemas.login is configured, the body is validated server-side before being forwarded. Invalid bodies return HTTP 400 with field errors:

{
  "message": "Invalid request body",
  "data": { "password": ["Too small: expected string to have >=8 characters"] }
}

If schemas.login is not configured, the body is forwarded as-is with no validation.

schemas.user — validated server-side when your backend returns user data. Mismatches throw a server error with field-level detail. Optional — omit to skip validation.

A schema must be representable as JSON Schema. A schema with .transform() or z.date() stops the build. A .refine() check is not kept, because JSON Schema cannot hold a function.

TypeScript infers the login payload type from your schema — localLogin(opts) is typed as the Zod output of schemas.login, or Record<string, any> if no schema is set.


Migration: v2 → v3 (breaking changes in v3.0.0)

Schemas are functions

Each schema in nuxt.config.ts is a function that returns the Zod schema. A schema that is not a function stops the build.

Before:

schemas: {
  login: z.object({ email_address: z.email(), password: z.string().min(8) }),
  user: userSchema,
}

After:

schemas: {
  login: () => z.object({ email_address: z.email(), password: z.string().min(8) }),
  user: () => userSchema,
}

Zod is a peer dependency

The module uses the zod package of your app. Install zod@^4.2.0 in your app:

npm install zod@^4.2.0

defaultProvider is removed

The defaultProvider option had no effect. Remove it from the auth config.

useAuthFetch has the type of useFetch

useAuthFetch accepts the same arguments as useFetch and returns the same type. When you give no type argument, the type of data comes from the server route, as it does for useFetch.


Migration: v1 → v2 (breaking changes in v2.0.0)

Configuration

In v1, signIn.body mapped fixed field names (principal, password) onto the request. In v2 the body is open — you pass any object and it is sent as-is to your endpoint. Remove signIn.body entirely and add schemas.login to describe the shape your backend expects.

Before:

local: {
  endpoints: {
    signIn: {
      path: "/api/auth/login",
      tokenKey: "token",
      body: {
        principal: "email_address",
        password: "password",
      },
    },
  },
}

After:

import { z } from "zod";

local: {
  endpoints: {
    signIn: {
      path: "/api/auth/login",
      tokenKey: "token",
    },
  },
  schemas: {
    login: () => z.object({
      email_address: z.email(),
      password: z.string().min(8),
    }),
  },
}

Login call

Before:

const { login } = useAuth();
await login("local", { principal: values.email, password: values.password });

After:

const { localLogin } = useAuthLogin();
await localLogin({ email_address: values.email, password: values.password });

Peer dependency

Zod 4.2 or later is required (zod@^4.2.0). If you were on Zod 3, upgrade:

npm install zod@^4.2.0

z.fromJSONSchema does not exist before Zod 4.2.

Related Packages

@devcoffee/nuxt-core

Nuxt 4 module providing OpenID Connect / OAuth 2.0 authorization code grant with...

3,965
@securegraph/sg-nuxt-jwt-sso-bridge

JWT-based SSO bridge client for Nuxt 3. Connects to external JWT authentication...

1,460
@authup/client-web-nuxt

Nuxt module that signs your app in with Authup and guards its routes by login st...

11,785 41
nuxt-openapi-meta

Auto-generate Nitro defineRouteMeta (OpenAPI) for Nuxt server routes from JSDoc...

688 0
nuxt-yup

yup implementation for nuxt

2,981 4

Version History

Version Released Vue License
3.0.1 ^3.0 MIT
3.0.0 ^3.0 MIT
2.2.0 ^3.0 MIT
2.1.0 ^3.0 MIT
2.0.1 ^3.0 MIT
Pre-releases (13)
Version Released Vue License
1.0.0-beta-2.7 ^3.0 MIT
1.0.0-beta-2.6 ^3.0 MIT
1.0.0-beta-2.5 ^3.0 MIT
1.0.0-beta-2.4 ^3.0 MIT
1.0.0-beta-2.3 ^3.0 MIT