Every operation is cryptographically proven to be fair and correct. Experience the next generation of trading with Lighter. Verifiable correctness for each action you take. Visit Lighter
Join the Asterdex community now and sign up online. Aster DEX: on-chain perpetual derivatives. Decentralized and secure crypto trading. Open Asterdex

Nuxt 3 & Vue 3 Authentication with Logto: The Guide You Actually Need






Nuxt 3 & Vue 3 Authentication with Logto: Full Guide








Nuxt 3 & Vue 3 Authentication with Logto: The Guide You Actually Need

Updated 2025 · 14 min read · Tags: nuxt 3 authentication, vue 3 authentication, logto, openid connect, oauth

Authentication is one of those topics that looks simple on the surface — “just add a login page” — and then quietly consumes three days of your life.
Between token storage debates, redirect loops, PKCE handshakes, and the eternal question of where to put your useAuth() composable,
web app authentication can spiral fast.
This guide walks you through adding production-grade Nuxt 3 authentication and Vue 3 authentication
using Logto — an open-source, OpenID Connect-compliant identity provider
that doesn’t make you read four RFCs before getting a login button on screen.

We’ll cover both frameworks because, while they share a lineage, their authentication models differ in ways that matter.
Nuxt 3 has server-side capabilities — middleware, API routes, session handling — that make it possible to keep tokens off the client entirely.
Vue 3 SPAs, meanwhile, live and die by the browser, which demands a different approach to token handling and route protection.
Understanding both gives you the flexibility to architect auth correctly for whatever stack you’re building on.

By the end of this article you’ll have a working login/logout implementation, protected routes,
a clear mental model of the OAuth authentication flow and OpenID Connect authentication,
and — critically — no hardcoded secrets in your frontend bundle. Let’s get into it.

Why Logto? A Quick Case for Not Reinventing the Wheel

Before touching a single line of code, it’s worth answering the obvious question: why Logto specifically, when there’s Auth0, Clerk, Supabase Auth, and a dozen others?
The honest answer is that Logto hits a sweet spot between developer experience and standards compliance.
It’s fully OpenID Connect and OAuth 2.0 compliant, ships first-party SDKs for both
Nuxt and
Vue,
and can be self-hosted if you want full data sovereignty. For teams who’ve been burned by vendor lock-in before, that last point isn’t trivial.

Logto’s free cloud tier covers most indie and small-team use cases — social logins, passwordless, MFA, and a generous MAU limit.
More importantly for this guide, the Logto Vue SDK and its Nuxt counterpart abstract the hairy parts of
the auth redirect flow: the authorization code exchange, PKCE verification, token refresh, and session persistence.
You still need to understand what’s happening under the hood (and we’ll explain it), but you’re not implementing PKCE from scratch at 11pm on a Friday.

The SDK also handles the callback URL routing and token storage strategy appropriately per platform —
HttpOnly cookies in Nuxt (server-side), memory + silent refresh in Vue SPAs — which reflects a genuine understanding
of the security tradeoffs involved rather than just shipping a one-size-fits-all solution.

The Authentication Flow Under the Hood

Whether you’re building a Nuxt 3 login system or a Vue 3 login system,
the underlying OAuth authentication flow is the same: Authorization Code Flow with PKCE.
Here’s what actually happens when a user clicks “Sign in”:

  • Your app generates a random code verifier and its SHA-256 hash (code challenge), then redirects the user to Logto’s authorization endpoint with that challenge attached.
  • The user authenticates at Logto (credentials, social login, passkey — whatever you’ve configured).
  • Logto redirects back to your callback URL with a short-lived authorization code.
  • Your app exchanges that code — plus the original code verifier — for an access token, ID token, and optionally a refresh token.
  • The ID token (a signed JWT) contains user claims. The access token is used to call protected APIs.

PKCE (Proof Key for Code Exchange) is what makes this flow safe for public clients like SPAs and mobile apps.
Without it, an attacker who intercepts the authorization code could exchange it for tokens.
With PKCE, the code is useless without the verifier that only your app generated.
This is the foundation of secure JavaScript authentication and TypeScript authentication in modern web apps.

In Nuxt 3’s case, the token exchange and storage happen on the server, which means your access token
never touches localStorage or client-side JavaScript at all.
The browser holds a session cookie (HttpOnly, Secure, SameSite=Lax), and the Nuxt server acts as a confidential client.
This is the gold standard for Nuxt 3 security — and a significant advantage over purely client-side approaches.

Setting Up Logto: App Registration and Credentials

Head to Logto Cloud (or your self-hosted instance) and create a new application.
For Nuxt 3, select “Traditional Web” as the application type — this signals to Logto that your app is a confidential client
capable of securely storing a client secret. For a standalone Vue 3 SPA, select “Single Page Application”,
which enables the PKCE-only flow without a client secret.

After creation, grab your App ID, App Secret (Nuxt only), and your Issuer URL
(something like https://your-tenant.logto.app/oidc).
Then configure your redirect URIs. For local development, you’ll want:

  • Redirect URI: http://localhost:3000/callback
  • Post-logout redirect URI: http://localhost:3000

These need to match exactly what your SDK sends — Logto validates them strictly as a security measure.
Mismatches here are one of the top causes of redirect_uri_mismatch errors,
which is a rite of passage in web authentication tutorials everywhere.
Add your production URLs here too before going live, and resist the temptation to use wildcards.

Nuxt 3 Authentication: Step-by-Step Implementation

Installation and Configuration

Install the official Logto Nuxt SDK:

npm install @logto/nuxt
# or
yarn add @logto/nuxt

Add the module and configure it in nuxt.config.ts.
Store sensitive values in .env — never hardcode them.
The cookieEncryptionKey is used to encrypt the session cookie on the server side;
generate a strong random string and treat it like a database password.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@logto/nuxt'],

  runtimeConfig: {
    logto: {
      appId: process.env.LOGTO_APP_ID,
      appSecret: process.env.LOGTO_APP_SECRET,
      endpoint: process.env.LOGTO_ENDPOINT,        // e.g. https://abc.logto.app
      cookieEncryptionKey: process.env.LOGTO_COOKIE_KEY,
      redirectUri: 'http://localhost:3000/callback',
      postLogoutRedirectUri: 'http://localhost:3000',
    }
  }
})

The module automatically registers the API routes /api/logto/sign-in,
/api/logto/sign-out, and /api/logto/callback on the Nuxt server.
This is the elegance of the server-side approach: your authentication middleware
and token handling live in Nitro (Nuxt’s server engine), completely isolated from client-side JavaScript.
No token in localStorage, no XSS exposure — proper Nuxt 3 security by default.

Accessing Auth State in Components

The SDK exposes a useLogtoUser() composable that gives you the authenticated user object
(derived from the ID token claims) on both server and client sides via Nuxt’s universal rendering:

<script setup lang="ts">
const user = useLogtoUser()
// user is null when unauthenticated, UserInfoResponse when authenticated
</script>

<template>
  <div>
    <p v-if="user">Welcome, {{ user.name ?? user.username }}</p>
    <a v-if="!user" href="/api/logto/sign-in">Sign in</a>
    <a v-else href="/api/logto/sign-out">Sign out</a>
  </div>
</template>

Notice we’re linking to server API routes rather than calling a JavaScript function.
This is intentional — the sign-in initiation and the sign-out happen as full navigation requests,
which is correct behavior for the Authorization Code Flow.
The server sets the appropriate state cookie and redirects to Logto’s authorization endpoint.
Clean, standard, and compatible with both JavaScript-enabled and (theoretically) progressively enhanced clients.

Protecting Routes with Nuxt Middleware

To guard routes and implement an auth redirect flow,
create a route middleware in middleware/auth.ts:

// middleware/auth.ts
export default defineNuxtRouteMiddleware(() => {
  const user = useLogtoUser()

  if (!user.value) {
    return navigateTo('/api/logto/sign-in', { external: true })
  }
})

Apply it to any page that requires authentication using the definePageMeta macro:

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

const user = useLogtoUser()
</script>

<template>
  <h1>Dashboard — {{ user?.email }}</h1>
</template>

This is your nuxt session authentication pattern in its simplest form.
The middleware runs on both server (during SSR) and client (during navigation),
giving you consistent protection. For global protection with exceptions (e.g., a public landing page),
use a global middleware that whitelists specific routes rather than opting individual pages in.

Vue 3 Authentication: SPA Implementation with Logto

Installation and Provider Setup

For a standalone Vue 3 application — whether built with Vite or any other bundler —
install the Logto Vue SDK:

npm install @logto/vue

The SDK uses Vue’s provide/inject pattern. Register the Logto plugin in your main.ts,
passing your app’s configuration. For a Vue SPA, you’re registering as a public client —
no appSecret here, just the App ID and OIDC endpoint:

// main.ts
import { createApp } from 'vue'
import { createLogto, UserScope } from '@logto/vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)
app.use(createLogto, {
  endpoint: 'https://your-tenant.logto.app',
  appId: 'your-app-id',
  scopes: [UserScope.Email, UserScope.Profile],
  redirectUri: 'http://localhost:5173/callback',
  postLogoutRedirectUri: 'http://localhost:5173',
})

app.mount('#app')

The scopes array controls what user information your app requests.
UserScope.Email and UserScope.Profile are standard OIDC scopes.
Only request what you actually need — scope creep in OIDC is both a UX problem
(users see a broader consent prompt) and a security smell.

Using the Auth Composables

The Logto Vue SDK exposes a useLogto() composable that gives you everything you need
for Vue SPA authentication:

<script setup lang="ts">
import { useLogto } from '@logto/vue'

const { signIn, signOut, isAuthenticated, isLoading, fetchUserInfo } = useLogto()

const handleSignIn = () => {
  signIn('http://localhost:5173/callback')
}

const handleSignOut = () => {
  signOut('http://localhost:5173')
}
</script>

<template>
  <div v-if="isLoading">Loading...</div>
  <div v-else-if="isAuthenticated">
    <p>Authenticated ✓</p>
    <button @click="handleSignOut">Sign out</button>
  </div>
  <div v-else>
    <button @click="handleSignIn">Sign in</button>
  </div>
</template>

isLoading is important — it’s true while the SDK is silently checking for an existing session
on page load. Rendering your UI without accounting for this produces an ugly “flash of unauthenticated content”
that makes users think they’ve been logged out. Always gate your authenticated UI on both
!isLoading and isAuthenticated.

Handling the Callback and Route Guards

Create a dedicated /callback route. The SDK needs to process the authorization code
returned by Logto before your app can consider the user authenticated:

<!-- views/CallbackView.vue -->
<script setup lang="ts">
import { useHandleSignInCallback } from '@logto/vue'
import { useRouter } from 'vue-router'

const router = useRouter()

const { isLoading } = useHandleSignInCallback(() => {
  // Redirect after successful authentication
  router.replace('/dashboard')
})
</script>

<template>
  <p v-if="isLoading">Processing authentication...</p>
</template>

For route protection in Vue Router, add a global navigation guard in your router configuration.
This implements the auth redirect flow consistently across all protected routes:

// router/index.ts
import { useLogto } from '@logto/vue'

router.beforeEach(async (to) => {
  if (to.meta.requiresAuth) {
    const { isAuthenticated, signIn } = useLogto()
    if (!isAuthenticated.value) {
      await signIn('http://localhost:5173/callback')
      return false
    }
  }
})

Mark protected routes with meta: { requiresAuth: true } in your route definitions.
This is the standard pattern for Vue 3 security at the routing layer —
clean, explicit, and easy to reason about. For more complex scenarios (role-based access,
multi-tenant apps), this guard is the right place to add claims-based authorization checks
using the ID token payload.

Environment Variables and Security Checklist

A nuxt 3 authentication example or vue authentication example
that leaves secrets in source code is worse than no example at all —
it teaches bad habits that end up in production. Here’s what your .env file should hold
and what should never leave the server:

For Nuxt 3, your LOGTO_APP_SECRET and LOGTO_COOKIE_KEY must be server-only.
In Nuxt’s runtimeConfig, keys nested under the logto object (without a
public prefix) are automatically server-only. Double-check this by looking at your
browser’s network requests — if you see your app secret in a JavaScript bundle, something is misconfigured.

For Vue SPAs, only public values go in your config — App ID and endpoint.
There is no client secret in a SPA by design; PKCE is what makes the flow secure without one.
If you find yourself adding a clientSecret to a Vue SPA config, stop:
you’re either using the wrong application type in Logto, or following a tutorial written before 2019.

💡 Security note: Always add .env to your .gitignore.
Use environment variable injection in your CI/CD pipeline.
Rotate your LOGTO_COOKIE_KEY periodically — existing sessions will be invalidated,
but that’s a small price for cryptographic hygiene.

Nuxt vs Vue: Choosing the Right Auth Architecture

The Nuxt 3 approach is strictly more secure for apps that handle sensitive data,
require server-side rendering for SEO, or need to make authenticated API calls during SSR.
The server acts as a confidential client, tokens are never exposed to client-side JavaScript,
and session management is handled with encrypted HttpOnly cookies.
This is the architecture you want for e-commerce platforms, dashboards, SaaS apps —
anything where a compromised token has real consequences.

The Vue 3 SPA approach makes sense for internal tools, developer dashboards,
or apps where SEO is irrelevant and the primary concern is build simplicity.
The tradeoff is that access tokens live in memory (the SDK’s default, and the safest option for SPAs)
and you need a robust silent refresh strategy to keep sessions alive.
The Logto Vue SDK handles this, but it’s worth understanding the mechanism:
a hidden iframe or prompt=none authorization request is used to get a fresh token
without user interaction.

If you’re building a Nuxt app that also exposes client-side API calls from Vue components,
you can use Nuxt’s built-in $fetch with server proxy routes —
your frontend calls /api/user, Nuxt’s server attaches the access token,
and calls your backend. The client never sees the token.
This pattern is a genuine differentiator for Nuxt session authentication
over raw Vue SPA implementations and something worth building toward for any production application.

Frequently Asked Questions

How do I add authentication to a Nuxt 3 app?

Install @logto/nuxt, add it to your nuxt.config.ts modules array,
and configure your Logto App ID, App Secret, endpoint, and cookie encryption key via environment variables.
The module auto-registers sign-in, sign-out, and callback API routes on the Nuxt server.
Use the useLogtoUser() composable to access auth state in your components,
and add a route middleware to protect pages that require authentication.

What is the difference between Nuxt 3 and Vue 3 authentication?

Nuxt 3 supports server-side token handling — credentials and access tokens stay on the server,
and the browser only holds an encrypted session cookie. This makes it significantly more secure
and better suited for SSR applications. Vue 3 SPAs handle auth entirely on the client,
using PKCE flow (no client secret) and in-memory token storage. Both approaches use
the same OIDC/OAuth 2.0 authorization code flow; the difference is where the token exchange happens.

Is Logto free for small projects?

Yes. Logto Cloud offers a free tier that includes social login providers,
passwordless authentication, OIDC/OAuth 2.0 compliance, and a monthly active user quota
suitable for most hobby and early-stage projects — no credit card required.
For teams needing higher MAU limits, advanced MFA, or enterprise features,
paid plans are available. Logto is also fully open-source and self-hostable.


Total
0
Shares