@habityzer/nuxt-symfony-kinde-layer
v2.3.0
Published
Shared Nuxt layer for Symfony + Kinde authentication integration
Downloads
434
Maintainers
Readme
@habityzer/nuxt-symfony-kinde-layer
Shared Nuxt layer for Symfony + Kinde authentication integration. This layer provides common authentication logic, API proxying, and pre-configured modules for Nuxt projects using Symfony backends with Kinde authentication.
Features
- ✅ Symfony API Proxy - Automatically forwards requests with Kinde auth tokens
- ✅ Accept Header Fix - Properly forwards Accept header for content negotiation (JSON vs Hydra)
- ✅ Auth Composable - Unified authentication state management
- ✅ Global Auth Middleware - Configurable route protection
- ✅ E2E Testing Support - Built-in support for automated testing
- ✅ Pre-configured Modules - Includes @nuxt/ui, @nuxt/image, Pinia, ESLint, and more
- ✅ Type-safe - Full TypeScript support with OpenAPI integration
Installation
Using pnpm workspace (recommended for monorepos)
# In your project root
pnpm add @habityzer/nuxt-symfony-kinde-layerOr link locally
cd /path/to/@habityzer/nuxt-symfony-kinde-layer
pnpm install
pnpm link --global
cd /path/to/your-project
pnpm link --global @habityzer/nuxt-symfony-kinde-layerUsage
1. Extend the layer in your nuxt.config.ts
export default defineNuxtConfig({
extends: ['@habityzer/nuxt-symfony-kinde-layer'],
// Runtime config - expose auth settings for middleware
runtimeConfig: {
apiBaseUrl: process.env.API_BASE_URL,
public: {
apiBaseUrl: process.env.API_BASE_URL,
// Optional: Override layer defaults for auth config
kindeAuth: {
cookie: {
prefix: 'myapp_' // Override default cookie prefix
},
middleware: {
publicRoutes: ['/', '/blog', '/help'] // Override default public routes
}
}
}
},
// Configure Kinde authentication module
kindeAuth: {
authDomain: process.env.KINDE_AUTH_DOMAIN,
clientId: process.env.KINDE_CLIENT_ID,
clientSecret: process.env.KINDE_CLIENT_SECRET,
redirectURL: process.env.KINDE_REDIRECT_URL,
logoutRedirectURL: process.env.KINDE_LOGOUT_REDIRECT_URL,
postLoginRedirectURL: '/dashboard',
cookie: {
prefix: 'myapp_' // IMPORTANT: Must match runtimeConfig.public.kindeAuth.cookie.prefix
},
middleware: {
publicRoutes: ['/', '/blog', '/help'] // Must match runtimeConfig.public
}
}
})2. Environment Variables
Create a .env file in your project:
# Symfony Backend
API_BASE_URL=http://localhost:8000
# Kinde Authentication (required by @habityzer/nuxt-kinde-auth module)
KINDE_AUTH_DOMAIN=https://your-domain.kinde.com
KINDE_CLIENT_ID=your-client-id
KINDE_CLIENT_SECRET=your-client-secret
KINDE_REDIRECT_URL=http://localhost:3000/api/kinde/callback
KINDE_LOGOUT_REDIRECT_URL=http://localhost:3000
# Layer Configuration (optional - layer provides defaults)
NUXT_PUBLIC_AUTH_COOKIE_PREFIX=myapp_
NUXT_PUBLIC_AUTH_LOGIN_PATH=/api/kinde/login
NUXT_PUBLIC_AUTH_CLOCK_SKEW_SECONDS=300
NUXT_PUBLIC_AUTH_APP_TOKEN_PREFIX=Bearer
NUXT_PUBLIC_AUTH_E2E_TOKEN_COOKIE_NAME=kinde_token
NUXT_PUBLIC_AUTH_ID_TOKEN_NAME=id_token
NUXT_PUBLIC_AUTH_ACCESS_TOKEN_NAME=access_token
NUXT_PUBLIC_AUTH_REFRESH_TOKEN_NAME=refresh_tokenNote: The layer provides sensible defaults for all NUXT_PUBLIC_AUTH_* variables. You only need to override them if you want different values. The KINDE_* variables are required.
3. Use the Auth Composable
<script setup lang="ts">
const {
isAuthenticated,
currentUser,
userDisplayName,
userEmail,
isPremium,
login,
logout,
fetchUserProfile
} = useAuth()
// Fetch user profile on mount
onMounted(async () => {
if (isAuthenticated.value) {
await fetchUserProfile()
}
})
</script>
<template>
<div>
<template v-if="isAuthenticated">
<p>Welcome, {{ userDisplayName }}!</p>
<p v-if="isPremium">Premium user</p>
<button @click="logout">Logout</button>
</template>
<template v-else>
<button @click="login">Login</button>
</template>
</div>
</template>4. Call Symfony APIs
The layer automatically proxies requests to /api/symfony/*:
// This calls your Symfony backend at /api/users
const users = await $fetch('/api/symfony/api/users')
// With generated OpenAPI composables
const { getUsersApi } = useUsersApi()
const response = await getUsersApi()What's Included
Modules
@nuxt/ui- UI component library@nuxt/image- Image optimization@nuxt/eslint- Linting@pinia/nuxt- State management@habityzer/nuxt-kinde-auth- Kinde authentication@vueuse/core- Vue composition utilities
Files
server/api/symfony/[...].ts- Symfony API proxy with authserver/middleware/auth-guard.ts- Server-side authentication middlewareserver/utils/auth-constants.ts- Re-exports shared auth constants for serverapp/composables/useAuth.ts- Authentication composableapp/plugins/auth-guard.client.ts- Client-side authentication guardapp/constants/auth.ts- Re-exports shared auth constants for appshared/auth-constants.ts- Core authentication constants (source of truth)
Configuration Options
Cookie Prefix
CRITICAL: Always set a unique cookie prefix per project to avoid cookie conflicts when running multiple projects locally:
// MUST be set in BOTH places:
runtimeConfig: {
public: {
kindeAuth: {
cookie: {
prefix: 'myproject_' // For middleware to read
}
}
}
},
kindeAuth: {
cookie: {
prefix: 'myproject_' // For Kinde module (must match above)
}
}Why both?
kindeAuth.cookie.prefix- Used by the Kinde auth module to set/read cookiesruntimeConfig.public.kindeAuth.cookie.prefix- Used by the layer's middleware to check authentication
Without unique prefixes: If you run ew-nuxt and habityzer-nuxt locally at the same time, they'll share cookies and cause auth conflicts!
Public Routes
Configure which routes don't require authentication:
kindeAuth: {
middleware: {
publicRoutes: [
'/',
'/blog',
'/about',
'/legal'
]
}
}E2E Testing
The layer supports E2E testing with app tokens:
Generate an app token in Symfony:
php bin/console app:token:manage createSet the token in your E2E tests:
// Set cookie await page.context().addCookies([{ name: 'kinde_token', value: 'app_your_token_here', domain: 'localhost', path: '/' }])
API Schema Generation
The layer includes OpenAPI tools for generating typed API composables:
# Generate TypeScript types from OpenAPI schema
pnpm generate:types
# Generate API composables
pnpm generate:api
# Or do both
pnpm sync:apiAdd these scripts to your project's package.json:
{
"scripts": {
"generate:types": "openapi-typescript ./schema/api.json -o ./app/types/api.ts --default-non-nullable false && eslint ./app/types/api.ts --fix",
"generate:api": "pnpm generate:types && nuxt-openapi-composables generate -s ./schema/api.json -o ./app/composables/api --types-import '~/types/api'",
"sync:api": "pnpm update:schema && pnpm generate:api"
}
}Development
Local Development Setup
Install dependencies:
pnpm installAvailable scripts:
pnpm dev # Run dev server with example env pnpm build # Build the layer pnpm lint # Check for linting issues pnpm lint:fix # Auto-fix linting issues pnpm release # Create semantic release (CI only)Git hooks (via Husky):
- Pre-commit: Automatically runs
pnpm lintbefore each commit - Commit-msg: Validates commit message format (conventional commits)
- Pre-commit: Automatically runs
First time setup: The pre-commit hook will automatically run
nuxt prepareif needed (with placeholder environment variables).
Commit Message Format
This project uses Conventional Commits. Your commits must follow this format:
type(scope): subject
body (optional)Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringtest: Adding or updating testschore: Maintenance tasks
Examples:
git commit -m "feat: add authentication middleware"
git commit -m "fix: resolve cookie prefix conflict"
git commit -m "docs: update README with CI setup"CI/CD Setup
Required Environment Variables for GitHub Actions
When building or publishing this layer in CI/CD (e.g., GitHub Actions), you need to provide placeholder environment variables for the nuxt prepare step. The layer's nuxt.config.ts validates these at build time.
Add these to your workflow:
- name: Prepare Nuxt
env:
KINDE_AUTH_DOMAIN: https://placeholder.kinde.com
KINDE_CLIENT_ID: placeholder_client_id
KINDE_CLIENT_SECRET: placeholder_client_secret
KINDE_REDIRECT_URL: http://localhost:3000/api/auth/callback
KINDE_LOGOUT_REDIRECT_URL: http://localhost:3000
NUXT_PUBLIC_AUTH_COOKIE_PREFIX: auth_
NUXT_PUBLIC_AUTH_LOGIN_PATH: /login
NUXT_PUBLIC_AUTH_CLOCK_SKEW_SECONDS: 300
NUXT_PUBLIC_AUTH_APP_TOKEN_PREFIX: Bearer
NUXT_PUBLIC_AUTH_E2E_TOKEN_COOKIE_NAME: e2e_token
NUXT_PUBLIC_AUTH_ID_TOKEN_NAME: id_token
NUXT_PUBLIC_AUTH_ACCESS_TOKEN_NAME: access_token
NUXT_PUBLIC_AUTH_REFRESH_TOKEN_NAME: refresh_token
run: pnpm nuxt prepareNote: These are placeholder values only used for type generation and validation. Projects consuming this layer will provide their own real credentials at runtime.
Troubleshooting
Cookie Name Conflicts
If you see authentication issues, ensure each project has a unique cookie prefix:
// Project A
kindeAuth: { cookie: { prefix: 'projecta_' } }
// Project B
kindeAuth: { cookie: { prefix: 'projectb_' } }TypeScript Errors with API Responses
If you get type mismatches between expected Hydra collections and plain arrays, the proxy is correctly forwarding the Accept header. Make sure your OpenAPI schema matches what the API actually returns.
Architecture & Design Decisions
Shared Constants Architecture
The layer uses a centralized constants file at shared/auth-constants.ts as the single source of truth for authentication configuration values (cookie names, token prefixes, etc.).
Structure:
shared/auth-constants.ts- Core constants definitionsapp/constants/auth.ts- Re-exports for client-side code (supports~/constants/authimports)server/utils/auth-constants.ts- Re-exports for server-side code (supports#importsand relative imports)
Why this structure:
- Single source of truth prevents drift between client and server values
- Re-export pattern works around Nuxt/Nitro bundling constraints
- Maintains clean import paths for consuming projects
Runtime Configuration
The layer uses Nuxt's runtimeConfig to make authentication settings available to both server middleware and client code:
Implementation:
- Constants are imported in
nuxt.config.tsfromshared/auth-constants.ts - Environment variables override defaults (e.g.,
NUXT_PUBLIC_AUTH_COOKIE_PREFIX) - Values are merged into
runtimeConfig.public.kindeAuthfor runtime access - Both server middleware and client plugins read from runtime config
This approach allows:
- Projects to override defaults via environment variables
- Type-safe access to configuration throughout the app
- Consistent behavior between development and production
Cookie Prefix Configuration
The layer uses a project-specific cookie prefix (e.g., ew-, habityzer_) to prevent cookie conflicts when running multiple projects locally.
Implementation:
- Base cookie names are defined without prefixes (
id_token,access_token) - Projects configure their prefix in
nuxt.config.ts - The prefix is applied dynamically at runtime by middleware and composables
- Projects should NOT redefine the cookie constant names - they inherit from the layer
TypeScript Type Suppressions
You may see @ts-expect-error comments for the cookie property in configuration files. This is expected and safe.
Reason: The @habityzer/nuxt-kinde-auth module's TypeScript definitions don't include our custom cookie.prefix configuration property, but it works correctly at runtime.
Solution: We use @ts-expect-error comments to suppress TypeScript errors without compromising type safety elsewhere in the codebase.
Cache Clearing
If you encounter auto-import issues after updating the layer (especially for composables), clear your Nuxt cache:
rm -rf .nuxt node_modules/.cache
pnpm buildThis forces Nuxt to regenerate its auto-import registry and pick up changes from the layer.
License
MIT
Contributing
This is a private layer for Habityzer projects. For issues or improvements, contact the team.
