@habityzer/nuxt-kinde-auth
@habityzer/nuxt-kinde-auth
Kinde authentication module for Nuxt 4
A plug-and-play authentication module for Nuxt 4 that integrates Kinde OAuth authentication with minimal setup.
Features
✅ Easy Setup - Add authentication to your Nuxt 4 app in minutes
✅ OAuth 2.0 - Secure authentication flow via Kinde
✅ Session Management - Cookie-based sessions with configurable options
✅ Route Protection - Optional global middleware to protect routes
✅ TypeScript - Full TypeScript support with type definitions
✅ Server-Side Ready - SSR-compatible authentication
✅ Debug Tools - Built-in debug endpoints for development
Installation
# Using pnpm (recommended)
pnpm add @habityzer/nuxt-kinde-auth
# Using npm
npm install @habityzer/nuxt-kinde-auth
# Using yarn
yarn add @habityzer/nuxt-kinde-auth
Quick Start
1. Add Module to Nuxt Config
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@habityzer/nuxt-kinde-auth'],
kindeAuth: {
authDomain: process.env.NUXT_KINDE_AUTH_DOMAIN,
clientId: process.env.NUXT_KINDE_CLIENT_ID,
clientSecret: process.env.NUXT_KINDE_CLIENT_SECRET,
redirectURL: process.env.NUXT_KINDE_REDIRECT_URL,
logoutRedirectURL: '/',
postLoginRedirectURL: '/dashboard'
}
})
2. Set Environment Variables
# .env
NUXT_KINDE_AUTH_DOMAIN=https://yourapp.kinde.com
NUXT_KINDE_CLIENT_ID=your_client_id
NUXT_KINDE_CLIENT_SECRET=your_client_secret
NUXT_KINDE_REDIRECT_URL=http://localhost:3000/api/kinde/callback
NUXT_KINDE_LOGOUT_REDIRECT_URL=http://localhost:3000
3. Use the Composable
<script setup lang="ts">
const { isAuthenticated, user, login, logout } = useKindeAuth()
</script>
<template>
<div>
<button v-if="!isAuthenticated" @click="login">
Log in
</button>
<div v-else>
<p>Welcome, {{ user?.given_name }}!</p>
<button @click="logout">Log out</button>
</div>
</div>
</template>
Configuration
Full Configuration Options
export default defineNuxtConfig({
kindeAuth: {
// Required: Kinde domain
authDomain: 'https://yourapp.kinde.com',
// Required: Client credentials
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
// OAuth URLs
redirectURL: '/api/kinde/callback',
logoutRedirectURL: '/',
postLoginRedirectURL: '/dashboard',
// Cookie configuration
cookie: {
prefix: 'myapp_', // Optional: Prefix for cookie names
httpOnly: false, // Allow client-side access for logout
secure: true, // HTTPS only in production
sameSite: 'lax', // CSRF protection
path: '/', // Cookie path
maxAge: 604800 // 7 days in seconds
},
// Middleware configuration
middleware: {
enabled: true, // Enable route protection
global: true, // Protect all routes by default
publicRoutes: ['/', '/about'] // Routes that don't require auth
},
// Debug configuration
debug: {
enabled: false // Enable debug endpoints in development only
}
}
})
Cookie Options
| Option | Type | Default | Description |
|---|---|---|---|
prefix |
string |
'' |
Cookie name prefix to prevent conflicts between projects |
httpOnly |
boolean |
false |
HttpOnly flag for cookies |
secure |
boolean |
true (prod) |
Secure flag for cookies |
sameSite |
'lax' | 'strict' | 'none' |
'lax' |
SameSite attribute |
path |
string |
'/' |
Cookie path |
maxAge |
number |
604800 |
Max age in seconds (7 days) |
Note: The prefix option is useful when running multiple projects on localhost:3000. For example, setting prefix: 'project1_' will create cookies like project1_access_token instead of just access_token, preventing conflicts between different projects.
Middleware Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Enable global middleware |
global |
boolean |
true |
Make middleware global |
publicRoutes |
string[] |
['/'] |
Public routes (no auth required) |
Composable API
useKindeAuth()
The main composable for authentication functionality.
Returns
{
// State
isAuthenticated: Ref<boolean>
user: Ref<KindeUser | null>
isLoading: Ref<boolean>
// Computed
userDisplayName: Ref<string>
userEmail: Ref<string | null>
userPicture: Ref<string | null>
// Methods
login: () => Promise<void>
logout: () => void
checkAuth: () => boolean
fetchUser: () => Promise<KindeUser | null>
getToken: () => Promise<string | null>
refreshToken: () => Promise<boolean>
}
User Type
interface KindeUser {
id: string
email?: string
given_name?: string
family_name?: string
picture?: string
[key: string]: https://github.com/Habityzer/nuxt-kinde-auth/blob/HEAD/unknown
}
API Endpoints
The module automatically registers these endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/kinde/login |
GET | Initiate OAuth login |
/api/kinde/logout |
GET | Logout and clear session |
/api/kinde/callback |
GET | OAuth callback handler |
/api/kinde/user |
GET | Get current user info |
/api/kinde/token |
GET | Get access token (auto-refreshes if expired) |
/api/kinde/refresh |
GET | Manually refresh access token |
/api/kinde/debug/token |
GET | Debug endpoint (dev only) |
Server-Side Usage
Access Kinde context in server routes:
// server/api/example.ts
export default defineEventHandler(async (event) => {
const kindeClient = event.context.kinde
// Check authentication
const isAuthenticated = await kindeClient.isAuthenticated()
if (!isAuthenticated) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized'
})
}
// Get user
const user = await kindeClient.getUser()
// Get token
const token = await kindeClient.getToken()
// Get permissions
const permissions = await kindeClient.getPermissions()
// Get feature flags
const hasFeature = await kindeClient.getBooleanFlag('new_feature', false)
return { user, permissions, hasFeature }
})
Route Protection
Using Built-in Middleware
The module provides optional global middleware for route protection:
// nuxt.config.ts
export default defineNuxtConfig({
kindeAuth: {
middleware: {
enabled: true,
global: true,
publicRoutes: [
'/',
'/about',
'/blog'
]
}
}
})
Custom Middleware
You can also create custom middleware:
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
const { isAuthenticated, checkAuth } = useKindeAuth()
checkAuth()
if (!isAuthenticated.value && to.path.startsWith('/dashboard')) {
return navigateTo('/api/kinde/login', { external: true })
}
})
Advanced Usage
Cookie Prefix for Multiple Projects
When developing multiple Nuxt projects locally, they typically run on the same domain (localhost:3000), which can cause cookie conflicts. Use the cookie.prefix option to isolate sessions:
// Project 1: Habityzer
export default defineNuxtConfig({
kindeAuth: {
cookie: {
prefix: 'habityzer_'
}
}
})
// Project 2: Dashboard
export default defineNuxtConfig({
kindeAuth: {
cookie: {
prefix: 'dashboard_'
}
}
})
This creates separate cookies for each project:
- Project 1:
habityzer_access_token,habityzer_refresh_token, etc. - Project 2:
dashboard_access_token,dashboard_refresh_token, etc.
Extending the Module
You can wrap useKindeAuth with your own composable to add custom functionality:
// composables/useAuth.ts
export const useAuth = () => {
const kindeAuth = useKindeAuth()
// Fetch additional user data from your API
const fetchUserProfile = async () => {
const response = await $fetch('/api/user/profile')
return response
}
return {
...kindeAuth,
fetchUserProfile
}
}
Token Management & Auto-Refresh
The module automatically manages token refresh using the stored refresh token. When you call getToken(), it checks if the access token is expired or about to expire (within 5 minutes) and automatically refreshes it:
const { getToken, refreshToken } = useKindeAuth()
// Automatic refresh - the module handles expired tokens
const token = await getToken() // Refreshes automatically if needed
// Use token in API calls
const data = await $fetch('/api/protected-resource', {
headers: {
Authorization: `Bearer ${token}`
}
})
// Manual refresh (optional)
const success = await refreshToken()
if (success) {
console.log('Token refreshed successfully')
}
How it works:
- Access tokens are checked for expiry before being returned
- If expired or expiring soon (< 5 minutes), the refresh token is used automatically
- New tokens are saved to cookies seamlessly
- If refresh fails (expired refresh token), the user is logged out
Proxy Pattern for Backend API
Create a proxy to forward requests with Kinde tokens:
// server/api/backend/[...path].ts
export default defineEventHandler(async (event) => {
const kindeClient = event.context.kinde
const token = await kindeClient.getToken()
const path = event.context.params?.path || ''
const config = useRuntimeConfig()
return $fetch(`${config.backendUrl}/${path}`, {
headers: {
Authorization: `Bearer ${token}`
}
})
})
Debug Mode
Enable debug endpoints and debug page in development:
// nuxt.config.ts
export default defineNuxtConfig({
kindeAuth: {
debug: {
enabled: process.env.NODE_ENV !== 'production'
}
}
})
🛠️ Nuxt DevTools Integration
The module automatically adds a "Kinde Auth" tab to your Nuxt DevTools in development mode!
To use it:
- Open your Nuxt app in development mode
- Click the Nuxt icon (usually bottom-right corner, or press
Shift + Alt/Option + D) - Click the "Kinde Auth" tab in the DevTools sidebar
The DevTools tab displays:
- ✅ Authentication Status - Live status indicator
- ✅ Current User Info - See authenticated user details
- ✅ Token Status - Real-time token expiry countdown with auto-refresh
- ✅ Configuration - All your Kinde settings (cookie prefix, redirects, etc.)
- ✅ Interactive Testing - Test token refresh, API calls, and more
No configuration needed! The tab appears automatically when debug.enabled is true.
🔍 Debug Page
When debug mode is enabled, the debug URL will be logged to your browser console on app startup.
Access the interactive debug page at:
http://localhost:3000/__kinde-debug
Quick Access: Bookmark this URL or check your browser console for the debug link!
The debug page provides:
- ✅ Configuration Display - View all your Kinde settings (cookie prefix, redirects, etc.)
- ✅ Real-time Token Status - Expiry countdown and status indicators
- ✅ Interactive Token Refresh - Test manual and automatic refresh
- ✅ API Call Testing - Verify tokens work with your endpoints
- ✅ Token Inspection - Decode and view token contents
- ✅ Auto-refresh - Updates every 10 seconds
- ✅ User Information - Display current user details
Perfect for testing:
- Token expiry and refresh functionality
- API calls with authentication
- Token lifecycle management
- Troubleshooting auth issues
- Verifying configuration settings
Debug API Endpoints
Token Information
GET /api/kinde/debug/token-info
Returns comprehensive token status:
{
"authenticated": true,
"tokens": {
"access": {
"isExpired": false,
"isExpiringSoon": true,
"expiresAt": "2024-01-01T12:00:00Z",
"timeUntilExpiry": 245,
"timeUntilExpiryFormatted": "4m 5s"
},
"refresh": {
"exists": true
}
},
"refreshStatus": {
"canRefresh": true,
"shouldRefresh": true,
"reason": "Token expires in less than 5 minutes"
}
}
Raw Tokens
GET /api/kinde/debug/token
Returns decoded tokens:
{
"accessToken": {
"raw": "eyJ...",
"decoded": { ... }
},
"idToken": {
"raw": "eyJ...",
"decoded": { ... }
}
}
TypeScript Support
The module includes full TypeScript support:
import type { KindeUser } from '@habityzer/nuxt-kinde-auth'
const user: KindeUser = {
id: '123',
email: 'user@example.com',
given_name: 'John',
family_name: 'Doe'
}
Migration Guide
From Custom Kinde Integration
If you have an existing Kinde integration:
-
Install the module
pnpm add @habityzer/nuxt-kinde-auth -
Update nuxt.config.ts
- Add module to
modulesarray - Move Kinde config to
kindeAuthkey - Remove manual runtime config
- Add module to
-
Remove old files
- Delete
server/middleware/kinde.ts - Delete
server/api/login.get.ts - Delete
server/api/logout.get.ts - Delete
server/api/callback.get.ts
- Delete
-
Update imports
- Replace custom
useAuthwithuseKindeAuth - Update API endpoint URLs (
/api/login→/api/kinde/login)
- Replace custom
-
Test thoroughly
- Verify login/logout works
- Check route protection
- Test server-side auth
Troubleshooting
"Kinde authentication not initialized"
Cause: Module not loaded or configuration missing.
Solution:
- Ensure
@habityzer/nuxt-kinde-authis in yourmodulesarray - Verify
authDomain,clientId, andclientSecretare set - Restart dev server
Session Not Persisting
Cause: Cookie configuration issues.
Solution:
- Check
cookie.securesetting (should befalsefor localhost) - Verify
cookie.sameSiteis set to'lax' - Clear browser cookies and try again
Redirect Loop on Login
Cause: Middleware configuration conflict.
Solution:
- Ensure
/api/kinde/callbackis inpublicRoutes - Check that middleware is not blocking auth endpoints
- Disable module middleware if using custom middleware
Token Refresh Issues
Cause: Refresh token expired or invalid.
Solution:
- Refresh tokens have a longer lifespan but do expire eventually
- If refresh fails, users must re-authenticate
- Check Kinde dashboard for token expiry settings
- The module automatically handles this by clearing session and requiring login
Requirements
- Nuxt 4.x
- Node.js >= 18
- Kinde account and application
License
MIT
Releases
Versioning and npm publishes run in GitHub Actions when changes are pushed to main or master (see .github/workflows/publish.yml). For commit conventions and npm token setup, see SETUP_NPM_PUBLISH.md.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues and questions:
Made with ❤️ for the Nuxt community
Related Packages
Shared Nuxt layer for Symfony + Kinde authentication integration
Generate type-safe Nuxt 3 & 4 composables from OpenAPI schemas
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.4.5 | ^3.0 | MIT | |
| 1.4.4 | ^3.0 | MIT | |
| 1.4.3 | ^3.0 | MIT | |
| 1.4.2 | ^3.0 | MIT | |
| 1.4.1 | ^3.0 | MIT | |
| 1.4.0 | ^3.0 | MIT | |
| 1.3.5 | ^3.0 | MIT | |
| 1.3.4 | ^3.0 | MIT | |
| 1.2.2 | ^3.0 | MIT | |
| 1.2.1 | ^3.0 | MIT | |
| 1.2.0 | ^3.0 | MIT | |
| 1.1.0 | ^3.0 | MIT | |
| 1.0.9 | ^3.0 | MIT |