Nuxt Keycloak

Comprehensive Keycloak authentication module for Nuxt 4 with SSR support, auto token refresh, role-based guards, and TypeScript.
Features
- 🔐 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
Quick Setup
pnpm add nuxt-keycloak
npm install nuxt-keycloak
yarn add nuxt-keycloak
- Add
nuxt-keycloak to the modules section of nuxt.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-id
That's it! You can now use Keycloak authentication in your Nuxt app ✨
Usage
Basic Authentication
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>
Protecting Pages
Protect entire pages by adding middleware:
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
middleware: 'keycloak-auth'
})
</script>
Role-Based Access Control
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>
Protecting API Routes
Server utilities are automatically available in Nuxt server routes (no import needed):
export default defineEventHandler(async (event) => {
const user = requireKeycloakAuth(event)
return {
message: 'Protected data',
user
}
})
export default defineEventHandler(async (event) => {
const user = requireRealmRole(event, 'admin')
return {
message: 'Admin data',
user
}
})
You can also explicitly import from nuxt-keycloak/server:
import { extractToken, verifyKeycloakToken, requireRealmRole } from 'nuxt-keycloak/server'
export default defineEventHandler(async (event) => {
const token = extractToken(event)
const user = await verifyKeycloakToken(token)
requireRealmRole(event, 'admin')
return { user }
})
Using with Traefik or Reverse Proxy
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',
middleware: true,
},
}
})
Or use environment variable:
NUXT_KEYCLOAK_SERVER_VERIFY_TOKEN=decode
Important: 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.
Manual Token Verification
For more control, manually verify tokens:
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'
})
}
if (!hasRealmRole(event, 'special-access')) {
throw createError({
statusCode: 403,
message: 'Forbidden'
})
}
return { user }
})
Configuration
Module Options
All configuration options:
export default defineNuxtConfig({
keycloak: {
url: 'http://localhost:8080',
realm: 'my-realm',
clientId: 'my-client',
initOptions: {
onLoad: 'check-sso',
pkceMethod: 'S256',
flow: 'standard',
checkLoginIframe: false,
silentCheckSsoRedirectUri: 'http://localhost:3000/silent-check-sso.html'
},
server: {
verifyToken: true,
middleware: false,
jwksCacheDuration: 600000,
rejectUnauthorized: true
},
client: {
autoRefreshToken: true,
minTokenValidity: 30,
persistRefreshToken: true
}
}
})
Environment Variables
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=false
API Reference
Composables
useKeycloak()
Returns:
keycloak - Keycloak instance (client-side only)
isAuthenticated - Authentication status
isInitialized - Initialization status
user - User profile data
token - Access token
tokenParsed - Parsed token claims
login(redirectUri?) - Redirect to login
logout(redirectUri?) - Logout user
register(redirectUri?) - Redirect to registration
updateToken(minValidity?) - Manually refresh token
loadUserProfile() - Load user profile
hasRealmRole(role) - Check realm role
hasResourceRole(role, resource?) - Check client role
isTokenExpired(minValidity?) - Check token expiration
Server Utilities
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 JWKS
extractToken(event) - Extract Bearer token from Authorization header
getKeycloakUser(event) - Get authenticated user from context
getKeycloakToken(event) - Get access token from context
isAuthenticated(event) - Check if user is authenticated
hasRealmRole(event, role) - Check if user has realm role
hasResourceRole(event, role, resource?) - Check if user has client role
requireKeycloakAuth(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)
Middleware
keycloak-auth - Protect routes (requires authentication)
keycloak-role - Enforce role-based access (use with route meta)
Keycloak Setup
1. Create a Realm
- Login to Keycloak Admin Console
- Create a new realm or use existing
- Note the realm name
2. Create a Client
- 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
3. Configure Client Settings
- Standard Flow: Enabled
- Direct Access Grants: Enabled (for password grant)
- Implicit Flow: Disabled (use PKCE instead)
4. Create Roles (Optional)
- Go to Roles → Add Role
- Create roles like
admin, user, manager
- Assign roles to users
5. Create Users
- Go to Users → Add User
- Set username, email, etc.
- Go to Credentials tab → Set password
- Go to Role Mappings → Assign roles
Examples
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
Troubleshooting
CORS Errors
Make sure your Keycloak client has the correct Web Origins configured. Add your app's origin (e.g., http://localhost:3000).
Token Not Refreshing
Check that:
client.autoRefreshToken is true in config
- Refresh token is valid and not expired
- Client has Standard Flow enabled in Keycloak
Silent Check-SSO Not Working
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.silentCheckSsoRedirectUri in 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.
Role Middleware Not Working
Ensure:
- User has the required role assigned in Keycloak
- Route meta includes
keycloakRoles configuration
- Both
keycloak-auth and keycloak-role middlewares are applied
Contribution
Local development
pnpm install
pnpm run dev:prepare
pnpm run dev
pnpm run dev:build
pnpm run lint
pnpm run test
pnpm run test:watch
pnpm run release
Credits
This 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.
License
MIT License
Copyright (c) 2025