Comprehensive Keycloak authentication module for Nuxt 4 with SSR support, auto token refresh, role-based guards, and TypeScript.
- 🔐 Hybrid Authentication - Support for both client-side (SPA) and server-side rendering (SSR) modes
- 🔄 Auto Token Refresh - Automatically refresh access tokens before expiration
- 🛡️ Role-Based Guards - Protect routes with realm and client/resource roles
- 📘 TypeScript Support - Full type safety with comprehensive TypeScript definitions
- 🔍 Silent Check-SSO - Non-intrusive authentication check for optional auth (auto-generated HTML file)
- 🔑 JWT Verification - Secure server-side token validation using JWKS with
jose - ⚙️ Configurable - Every feature can be toggled via configuration
- 🎯 Production Ready - Error handling, logging, and security best practices
- 🚀 Zero Config - Silent Check-SSO HTML automatically generated in public directory
- Install the module:
pnpm add nuxt-keycloak
# or
npm install nuxt-keycloak
# or
yarn add nuxt-keycloak- Add
nuxt-keycloakto themodulessection ofnuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-keycloak'],
keycloak: {
url: 'http://localhost:8080',
realm: 'your-realm',
clientId: 'your-client-id',
}
})- Configure environment variables (optional):
NUXT_PUBLIC_KEYCLOAK_URL=http://localhost:8080
NUXT_PUBLIC_KEYCLOAK_REALM=your-realm
NUXT_PUBLIC_KEYCLOAK_CLIENT_ID=your-client-idThat's it! You can now use Keycloak authentication in your Nuxt app ✨
Use the useKeycloak() composable in your components:
<script setup>
const { isAuthenticated, user, login, logout } = useKeycloak()
</script>
<template>
<div>
<div v-if="!isAuthenticated">
<button @click="login()">Login</button>
</div>
<div v-else>
<p>Welcome, {{ user?.username }}!</p>
<button @click="logout()">Logout</button>
</div>
</div>
</template>Protect entire pages by adding middleware:
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
middleware: 'keycloak-auth'
})
</script>Protect pages based on Keycloak roles:
<!-- pages/admin.vue -->
<script setup>
definePageMeta({
middleware: ['keycloak-auth', 'keycloak-role'],
keycloakRoles: {
realm: ['admin'], // Requires 'admin' realm role
resource: { // OR requires client role
clientId: 'my-app',
roles: ['manager']
}
}
})
</script>Server utilities are automatically available in Nuxt server routes (no import needed):
// server/api/protected.ts
export default defineEventHandler(async (event) => {
// Require authentication - auto-imported
const user = requireKeycloakAuth(event)
return {
message: 'Protected data',
user
}
})// server/api/admin.ts
export default defineEventHandler(async (event) => {
// Require specific role - auto-imported
const user = requireRealmRole(event, 'admin')
return {
message: 'Admin data',
user
}
})You can also explicitly import from nuxt-keycloak/server:
// server/api/custom.ts
import { extractToken, verifyKeycloakToken, requireRealmRole } from 'nuxt-keycloak/server'
export default defineEventHandler(async (event) => {
const token = extractToken(event)
const user = await verifyKeycloakToken(token)
// Use imported functions
requireRealmRole(event, 'admin')
return { user }
})When deploying behind Traefik or another reverse proxy that already verifies JWT tokens, you can configure the module to skip JWKS verification and simply decode tokens:
export default defineNuxtConfig({
keycloak: {
url: process.env.NUXT_PUBLIC_KEYCLOAK_URL,
realm: process.env.NUXT_PUBLIC_KEYCLOAK_REALM,
clientId: process.env.NUXT_PUBLIC_KEYCLOAK_CLIENT_ID,
server: {
verifyToken: 'decode', // Decode only, trust proxy verification
middleware: true,
},
}
})Or use environment variable:
NUXT_KEYCLOAK_SERVER_VERIFY_TOKEN=decodeImportant: Only use verifyToken: 'decode' when tokens are pre-verified by a trusted reverse proxy (Traefik ForwardAuth, etc.). This mode skips all signature and expiration validation.
For more control, manually verify tokens:
// server/api/custom.ts
export default defineEventHandler(async (event) => {
const token = extractToken(event)
if (!token) {
throw createError({
statusCode: 401,
message: 'No token provided'
})
}
const user = await verifyKeycloakToken(token)
if (!user) {
throw createError({
statusCode: 401,
message: 'Invalid token'
})
}
// Check custom logic
if (!hasRealmRole(event, 'special-access')) {
throw createError({
statusCode: 403,
message: 'Forbidden'
})
}
return { user }
})All configuration options:
export default defineNuxtConfig({
keycloak: {
// Keycloak server URL
url: 'http://localhost:8080',
// Realm name
realm: 'my-realm',
// Client ID (must be a public client)
clientId: 'my-client',
// Keycloak initialization options
initOptions: {
onLoad: 'check-sso', // or 'login-required'
pkceMethod: 'S256',
flow: 'standard',
checkLoginIframe: false,
silentCheckSsoRedirectUri: 'http://localhost:3000/silent-check-sso.html'
},
// Server-side configuration
server: {
verifyToken: true, // true = full JWKS verification, false = disabled, 'decode' = decode only (for reverse proxy scenarios)
middleware: false, // Enable global middleware
jwksCacheDuration: 600000, // JWKS cache duration (10 minutes)
rejectUnauthorized: true // Verify SSL certificates for JWKS endpoint
},
// Client-side configuration
client: {
autoRefreshToken: true, // Auto-refresh tokens
minTokenValidity: 30, // Refresh when < 30s validity
persistRefreshToken: true // Store refresh token in localStorage
}
}
})Use environment variables for different environments:
# Public (exposed to client)
NUXT_PUBLIC_KEYCLOAK_URL=https://keycloak.example.com
NUXT_PUBLIC_KEYCLOAK_REALM=production
NUXT_PUBLIC_KEYCLOAK_CLIENT_ID=nuxt-app
NUXT_PUBLIC_APP_URL=https://app.example.com
# Private (server-only)
NUXT_KEYCLOAK_SERVER_VERIFY_TOKEN=true # true, false, or 'decode'
NUXT_KEYCLOAK_SERVER_MIDDLEWARE=falseReturns:
keycloak- Keycloak instance (client-side only)isAuthenticated- Authentication statusisInitialized- Initialization statususer- User profile datatoken- Access tokentokenParsed- Parsed token claimslogin(redirectUri?)- Redirect to loginlogout(redirectUri?)- Logout userregister(redirectUri?)- Redirect to registrationupdateToken(minValidity?)- Manually refresh tokenloadUserProfile()- Load user profilehasRealmRole(role)- Check realm rolehasResourceRole(role, resource?)- Check client roleisTokenExpired(minValidity?)- Check token expiration
All utilities are auto-imported in Nuxt server routes. You can also explicitly import them from nuxt-keycloak/server:
import {
verifyKeycloakToken,
extractToken,
requireKeycloakAuth
} from 'nuxt-keycloak/server'Available Functions:
verifyKeycloakToken(token)- Verify JWT token using JWKSextractToken(event)- Extract Bearer token from Authorization headergetKeycloakUser(event)- Get authenticated user from contextgetKeycloakToken(event)- Get access token from contextisAuthenticated(event)- Check if user is authenticatedhasRealmRole(event, role)- Check if user has realm rolehasResourceRole(event, role, resource?)- Check if user has client rolerequireKeycloakAuth(event)- Require authentication (throws 401 if not authenticated)requireRealmRole(event, role)- Require realm role (throws 403 if missing)requireResourceRole(event, role, resource?)- Require client role (throws 403 if missing)
keycloak-auth- Protect routes (requires authentication)keycloak-role- Enforce role-based access (use with route meta)
- Login to Keycloak Admin Console
- Create a new realm or use existing
- Note the realm name
- Go to Clients → Create
- Client ID: Your app name (e.g.,
nuxt-app) - Client Protocol:
openid-connect - Access Type:
public(for SPA/Nuxt) - Valid Redirect URIs:
http://localhost:3000/*(or your app URL) - Web Origins:
http://localhost:3000(or your app URL) - Save
- Standard Flow: Enabled
- Direct Access Grants: Enabled (for password grant)
- Implicit Flow: Disabled (use PKCE instead)
- Go to Roles → Add Role
- Create roles like
admin,user,manager - Assign roles to users
- Go to Users → Add User
- Set username, email, etc.
- Go to Credentials tab → Set password
- Go to Role Mappings → Assign roles
Check out the playground directory for comprehensive examples:
- Public Page -
/- No authentication required - Dashboard -
/dashboard- Requires authentication - Admin Panel -
/admin- Requires 'admin' realm role - Profile -
/profile- Shows user info and token details
Make sure your Keycloak client has the correct Web Origins configured. Add your app's origin (e.g., http://localhost:3000).
Check that:
client.autoRefreshTokenistruein config- Refresh token is valid and not expired
- Client has Standard Flow enabled in Keycloak
The module automatically creates /public/silent-check-sso.html for you. If you need to customize it:
- The file is automatically generated in your
public/directory - Configure
initOptions.silentCheckSsoRedirectUriin config (e.g.,http://localhost:3000/silent-check-sso.html) - Add the URL to Valid Redirect URIs in Keycloak client
Note: The file is auto-generated by the module on nuxt prepare/nuxt dev, so you don't need to manually create it.
Ensure:
- User has the required role assigned in Keycloak
- Route meta includes
keycloakRolesconfiguration - Both
keycloak-authandkeycloak-rolemiddlewares are applied
Local development
# Install dependencies
pnpm install
# Generate type stubs
pnpm run dev:prepare
# Develop with the playground
pnpm run dev
# Build the playground
pnpm run dev:build
# Run ESLint
pnpm run lint
# Run Vitest
pnpm run test
pnpm run test:watch
# Release new version
pnpm run releaseThis project was inspired by and builds upon ideas from:
- vue-keycloak by José Miguel Gonçalves and Gery Hirschfeld (Apache 2.0)
Special thanks to the open-source community for their contributions to Keycloak integration solutions.
Copyright (c) 2025