@contable/nuxt-oauth-contable
v1.0.4
Published
Nuxt module for OAuth2/OIDC authentication with Contable (Ory Hydra)
Readme
@contable/nuxt-oauth-contable
Módulo Nuxt 3 para autenticación OAuth2/OIDC con Contable (Ory Hydra).
Características
- 🔐 Autenticación OAuth2/OIDC completa con Ory Hydra
- 🏢 Gestión de múltiples empresas (multi-tenant)
- 🔄 Refresh token automático
- 📦 Stores Pinia incluidos (
useOAuthAuthStore,useOAuthBusinessStore) - 🛡️ Middleware de autenticación listo para usar
- 🎯 Auto-imports de composables y funciones
Instalación
npm install @contable/nuxt-oauth-contableConfiguración
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'pinia-plugin-persistedstate/nuxt',
'@contable/nuxt-oauth-contable',
],
// Configuración del módulo (opcional - valores por defecto mostrados)
oauthContable: {
issuerUrl: 'https://contable.app',
clientId: 'pymo',
redirectUri: '', // Auto-detecta: ${protocol}//${host}/oauth2-callback
scopes: ['openid', 'offline', 'first_party', 'email', 'profile', 'devise', 'id'],
audience: ['https://contable.app'],
contableBackUrl: 'https://back.contable.app',
debug: false,
addPages: true, // Agrega páginas /login y /oauth2-callback automáticamente
loginRoute: 'login',
callbackRoute: 'oauth2-callback',
redirectAfterLogin: '/',
},
// IMPORTANTE: Puerto 3000 requerido para OAuth (redirect URI registrado)
devServer: {
port: 3000,
},
})Uso
Proteger páginas con middleware
<script setup>
definePageMeta({
middleware: 'auth', // Requiere autenticación
})
</script>Acceder a datos de autenticación
<script setup>
const authStore = useOAuthAuthStore()
const businessStore = useOAuthBusinessStore()
// Verificar si está autenticado
const isLoggedIn = computed(() => authStore.isAuthenticated)
// Obtener email del usuario
const userEmail = computed(() => authStore.userEmail)
// Obtener empresa seleccionada
const currentBusiness = computed(() => businessStore.selectedBusiness)
</script>Selector de empresas
<script setup>
const businessStore = useOAuthBusinessStore()
const selectBusiness = (business) => {
businessStore.setSelectedBusiness(business)
}
</script>
<template>
<select @change="selectBusiness($event.target.value)">
<option v-for="b in businessStore.businesses" :key="b.id" :value="b">
{{ b.fantasy_name || b.name }}
</option>
</select>
</template>Reaccionar a cambios de empresa
<script setup>
const myStore = useMyStore()
// Se ejecuta cuando el usuario cambia de empresa
useBusinessWatcher(async (newBusiness, oldBusiness) => {
if (newBusiness) {
myStore.clearData()
await myStore.fetchDataForBusiness(newBusiness.id)
}
})
</script>Headers de autenticación para APIs
// En un composable o store
import { getAuthHeaders } from '#imports'
const fetchData = async () => {
const headers = await getAuthHeaders()
// headers incluye:
// - Authorization: Bearer <token>
// - business-id: <selected_business_id>
const response = await fetch('/api/data', {
headers: {
'Content-Type': 'application/json',
...headers,
},
})
}Logout
<script setup>
import { oauthLogout } from '#imports'
const handleLogout = async () => {
await oauthLogout() // Limpia auth y redirige a /login
}
</script>Stores disponibles
useOAuthAuthStore
| Getter | Descripción |
|--------|-------------|
| isAuthenticated | boolean - Si hay token válido |
| accessToken | string \| null - OAuth access token |
| refreshToken | string \| null - OAuth refresh token |
| idToken | string \| null - OpenID Connect ID token |
| userEmail | string \| null - Email del usuario |
| Action | Descripción |
|--------|-------------|
| setAuthInfo(info) | Guardar tokens |
| clearAuth() | Limpiar autenticación |
useOAuthBusinessStore
| Getter | Descripción |
|--------|-------------|
| businesses | Business[] - Lista de empresas |
| selectedBusiness | Business \| null - Empresa seleccionada |
| selectedBusinessName | string \| null - Nombre de empresa |
| selectedBusinessId | number \| null - ID de empresa |
| hasBusinesses | boolean - Si tiene empresas |
| isLoading | boolean - Cargando empresas |
| Action | Descripción |
|--------|-------------|
| fetchBusinesses() | Cargar empresas desde Contable |
| setSelectedBusiness(b) | Seleccionar empresa |
| selectBusinessById(id) | Seleccionar por ID |
| clear() | Limpiar datos |
Funciones disponibles
| Función | Descripción |
|---------|-------------|
| oauthRedirect() | Redirige al login de Contable |
| oauthValidate() | Procesa callback OAuth |
| oauthRefreshToken() | Refresca token expirado |
| oauthLogout(redirect?) | Cierra sesión |
| getAuthHeaders() | Obtiene headers con token y business-id |
| useBusinessWatcher(cb) | Watch para cambios de empresa |
Páginas personalizadas
Si quieres personalizar las páginas de login/callback:
// nuxt.config.ts
export default defineNuxtConfig({
oauthContable: {
addPages: false, // Desactiva páginas automáticas
},
})Luego crea tus propias páginas usando las funciones del módulo:
<!-- pages/login.vue -->
<script setup>
import { oauthRedirect } from '#imports'
definePageMeta({
layout: false,
middleware: 'guest',
})
const handleLogin = () => oauthRedirect()
</script>Migración desde implementación manual
Si ya tienes OAuth implementado manualmente:
- Instala el módulo
- Renombra tus stores:
useAuthStore→ usaruseOAuthAuthStoreo crear aliasuseBusinessStore→ usaruseOAuthBusinessStoreo crear alias
- Actualiza imports de funciones OAuth
- Elimina archivos duplicados (utils/oauth.ts, stores/auth.ts, etc.)
// Crear alias en composables/use-auth-store.ts
export { useOAuthAuthStore as useAuthStore } from '#imports'