UNPKG

vuethenticate

Version:

A Vue 3 authentication state management library using oidc-client-ts

686 lines (530 loc) 20.3 kB
# Vuethenticate A Vue 3 authentication state management library using oidc-client-ts. ## Features - 🔐 OIDC/OAuth2 authentication with automatic token management - ⚡ Vue 3 Composition API with reactive state - 🔄 Automatic token renewal - 📦 TypeScript support - 🎯 Minimal configuration required - 🎨 Customizable callback component ## Installation ```bash npm install vuethenticate ``` ## Quick Start > **Important Setup Order**: Make sure to initialize `useAuth()` in your main App component or a parent component before any callback routes are accessible. The callback components depend on this initialization. ### 1. Configure Authentication ```vue <!-- App.vue - Main Application Component --> <script setup> import { useAuth } from 'vuethenticate' import { RouterView } from 'vue-router' // Initialize authentication in your main app component const { user, isAuthenticated, signIn, signOut } = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id' }) </script> <template> <div id="app"> <nav> <div v-if="isAuthenticated"> <p>Welcome, {{ user.profile.name }}!</p> <button @click="signOut">Sign Out</button> </div> <div v-else> <button @click="signIn">Sign In</button> </div> </nav> <!-- Router outlet - callback routes will work properly now --> <RouterView /> </div> </template> ``` ### 2. Setup Callback Route ```vue <!-- CallbackPage.vue --> <script setup> import { AuthCallback } from 'vuethenticate' function onSuccess(user, state) { console.log('Authentication successful:', user) console.log('State:', state) // Redirect to app or show success message window.location.href = '/dashboard' } function onError(error) { console.error('Authentication failed:', error) // Handle error - redirect to login or show error window.location.href = '/login' } </script> <template> <AuthCallback :onSuccess="onSuccess" :onError="onError" > <div>Signing you in...</div> <template #error="{ error }"> <div> <h2>Authentication Failed</h2> <p>{{ error.message }}</p> </div> </template> </AuthCallback> </template> ``` ### 3. Add Route Configuration ```javascript // router.js import { createRouter, createWebHistory } from 'vue-router' import CallbackPage from './CallbackPage.vue' import LogoutCallbackPage from './LogoutCallbackPage.vue' import SilentCallbackPage from './SilentCallbackPage.vue' const routes = [ { path: '/auth/callback', component: CallbackPage }, { path: '/auth/logout-callback', component: LogoutCallbackPage }, { path: '/auth/silent-callback', component: SilentCallbackPage }, // ... other routes ] export default createRouter({ history: createWebHistory(), routes }) ``` ### 4. Setup Silent Callback (for Token Renewal) ```vue <!-- SilentCallbackPage.vue --> <script setup> import { SilentCallback } from 'vuethenticate' function onError(error) { console.error('Silent renewal failed:', error) } </script> <template> <SilentCallback :onError="onError" /> </template> ``` ## URL State Support You can pass state through the authentication flow with full TypeScript support: ```typescript // Define your state type interface MyAppState { returnUrl: string; theme: 'light' | 'dark'; userId?: string; } // Use with generic type parameter const auth = useAuth<MyAppState>(config); // Pass state during sign in await auth.signIn({ returnUrl: '/dashboard', theme: 'dark' }); // Pass state during sign out await auth.signOut({ returnUrl: '/goodbye', theme: 'light' }); ``` ### Receiving State in Callback ```vue <script setup lang="ts"> import { AuthCallback } from 'vuethenticate'; interface MyAppState { returnUrl: string; theme: 'light' | 'dark'; } const handleSuccess = (user: User, state?: MyAppState) => { if (state?.returnUrl) { router.push(state.returnUrl); } if (state?.theme) { setTheme(state.theme); } }; </script> <template> <AuthCallback<MyAppState> @success="handleSuccess" /> </template> ``` > **Important**: Make sure to call `useAuth()` in your main App component or a parent component before rendering any callback components. The callback components rely on the authentication being initialized first. ## API Reference ### `useAuth(config)` The main composable for authentication state management. #### Configuration | Property | Type | Required | Default | Description | |----------|------|----------|---------|-------------| | `authority` | `string` | ✓ | - | OIDC provider URL | | `clientId` | `string` | ✓ | - | Application client ID | | `redirectUri` | `string` | | `${origin}/auth/callback` | Callback URL | | `scope` | `string` | | `'openid profile'` | OIDC scopes | | `responseType` | `string` | | `'code'` | OAuth response type | | `storage` | `'localStorage' \| 'sessionStorage' \| 'memory'` | | `'localStorage'` | Storage type | | `automaticSilentRenew` | `boolean` | | `true` | Enable automatic token refresh | | `silentRedirectUri` | `string` | | `${origin}/auth/silent-callback` | Silent refresh callback URL | | `postLogoutRedirectUri` | `string` | | `${origin}` | Post-logout redirect URL | | `logLevel` | `LogLevel` | | `LogLevel.NONE` | Logging level for debug information | | `logger` | `Logger` | | `silentLogger` | Custom logger implementation | #### Event Callbacks | Property | Type | Description | |----------|------|-------------| | `onError` | `(error: Error) => void` | Called when an error occurs | | `onUserLoaded` | `(user: User) => void` | Called when user is loaded | | `onUserUnloaded` | `() => void` | Called when user is unloaded | | `onAccessTokenExpired` | `() => void` | Called when access token expires | | `onAccessTokenExpiring` | `() => void` | Called before access token expires | | `onSilentRenewError` | `(error: Error) => void` | Called when silent token renewal fails (auth state is automatically reset) | #### Returns | Property | Type | Description | |----------|------|-------------| | `user` | `Ref<User \| null>` | Current user object | | `isAuthenticated` | `Ref<boolean>` | Authentication status | | `isLoading` | `Ref<boolean>` | Loading state | | `error` | `Ref<Error \| null>` | Current error | | `accessToken` | `Ref<string \| null>` | Current access token | | `isExpired` | `Ref<boolean>` | Token expiration status | | `signIn` | `(state?: TState) => Promise<void>` | Initiate sign in with optional state | | `signOut` | `(state?: TState) => Promise<void>` | Sign out user with optional state | | `silentRenew` | `() => Promise<void>` | Manually renew token | | `clearError` | `() => void` | Clear current error | | `resetAuthState` | `() => Promise<void>` | Manually reset authentication state by removing user and clearing reactive state | | `cleanup` | `() => void` | Manual cleanup of UserManager resources (rarely needed) | ### `<AuthCallback>` Component for handling OAuth callback. **Note**: This component requires `useAuth()` to be called in a parent component first. #### Props | Property | Type | Required | Description | |----------|------|----------|-------------| | `onSuccess` | `(user: User, state?: TState) => void` | | Success callback with typed state | | `onError` | `(error: Error) => void` | | Error callback | #### Slots | Slot | Props | Description | |------|-------|-------------| | `default` | - | Loading content | | `error` | `{ error: Error }` | Error content | #### Events | Event | Payload | Description | |-------|---------|-------------| | `success` | `User, state?: TState` | Emitted on successful authentication with typed state | | `error` | `Error` | Emitted on authentication error | ### `<LogoutCallback>` Component for handling OAuth logout callback. **Note**: This component requires `useAuth()` to be called in a parent component first. #### Props | Property | Type | Required | Description | |----------|------|----------|-------------| | `onSuccess` | `(state?: TState) => void` | | Success callback with typed state | | `onError` | `(error: Error) => void` | | Error callback | #### Slots | Slot | Props | Description | |------|-------|-------------| | `default` | - | Loading content | | `error` | `{ error: Error }` | Error content | | `success` | - | Success content | #### Events | Event | Payload | Description | |-------|---------|-------------| | `success` | `state?: TState` | Emitted on successful logout with typed state | | `error` | `Error` | Emitted on logout error | #### Usage ```vue <!-- LogoutCallbackPage.vue --> <script setup> import { LogoutCallback } from 'vuethenticate' function onSuccess(state) { console.log('Logout successful:', state) // Redirect to home or show success message window.location.href = '/' } function onError(error) { console.error('Logout failed:', error) // Handle error - redirect or show error window.location.href = '/error' } </script> <template> <LogoutCallback :onSuccess="onSuccess" :onError="onError" > <div>Processing logout...</div> <template #error="{ error }"> <div> <h2>Logout Failed</h2> <p>{{ error.message }}</p> </div> </template> <template #success> <div> <h2>Logout Successful</h2> <p>You have been logged out successfully.</p> </div> </template> </LogoutCallback> </template> ``` ### `<SilentCallback>` Component for handling silent token renewal callbacks. This component should be mounted on a separate route (typically `/auth/silent-callback`) and is used internally by the library for automatic token refresh. **Note**: This component requires `useAuth()` to be called in a parent component first. #### Props | Property | Type | Required | Description | |----------|------|----------|-------------| | `onError` | `(error: Error) => void` | | Error callback for silent renewal failures | #### Usage ```vue <template> <SilentCallback :onError="handleSilentError" /> </template> <script setup> import { SilentCallback } from 'vuethenticate' function handleSilentError(error) { console.error('Silent renewal failed:', error) } </script> ``` > **Note**: This component is primarily used in an iframe or popup for silent token renewal. It should be placed on a minimal page with no other content. ## Logging and Debugging Vuethenticate provides comprehensive logging capabilities to help debug authentication flows and monitor authentication events. The logging system supports both internal library logging and configurable `oidc-client-ts` debug output. ### Log Levels ```typescript import { LogLevel } from 'vuethenticate'; enum LogLevel { NONE = 0, // No logging ERROR = 1, // Only errors WARN = 2, // Warnings and errors INFO = 3, // Info, warnings, and errors DEBUG = 4, // All messages including debug info } ``` ### Basic Logging Setup ```typescript import { useAuth, LogLevel, consoleLogger } from 'vuethenticate'; // Enable debug logging with console output const auth = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id', logLevel: LogLevel.DEBUG, logger: consoleLogger }); ``` ### Custom Logger Implementation ```typescript import { useAuth, LogLevel, type Logger } from 'vuethenticate'; const customLogger: Logger = { error: (message: string, ...args: unknown[]) => { // Send to your error tracking service console.error('[AUTH ERROR]', message, ...args); // Sentry.captureException(new Error(message)); }, warn: (message: string, ...args: unknown[]) => { console.warn('[AUTH WARN]', message, ...args); }, info: (message: string, ...args: unknown[]) => { console.info('[AUTH INFO]', message, ...args); }, debug: (message: string, ...args: unknown[]) => { if (process.env.NODE_ENV === 'development') { console.debug('[AUTH DEBUG]', message, ...args); } }, }; const auth = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id', logLevel: LogLevel.INFO, logger: customLogger }); ``` ### Environment-Based Logging ```typescript import { useAuth, LogLevel, consoleLogger } from 'vuethenticate'; const getLogConfig = () => { switch (process.env.NODE_ENV) { case 'development': return { logLevel: LogLevel.DEBUG, logger: consoleLogger }; case 'test': return { logLevel: LogLevel.NONE }; case 'production': return { logLevel: LogLevel.ERROR, logger: productionLogger }; default: return { logLevel: LogLevel.WARN, logger: consoleLogger }; } }; const auth = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id', ...getLogConfig() }); ``` ### Available Loggers #### `consoleLogger` Standard console output logger suitable for development. #### `silentLogger` No-op logger that suppresses all output (default when no logger is specified). #### `createLevelLogger(baseLogger, level)` Creates a filtered logger that only outputs messages at or above the specified level. ```typescript import { createLevelLogger, consoleLogger, LogLevel } from 'vuethenticate'; const warnOnlyLogger = createLevelLogger(consoleLogger, LogLevel.WARN); ``` ### What Gets Logged When logging is enabled, you'll see output for: - **Authentication initialization** - Setup and configuration validation - **Token operations** - Token acquisition, renewal, and expiration - **User state changes** - Login, logout, and user profile updates - **Silent renewal** - Background token refresh attempts - **OIDC protocol flows** - Detailed protocol-level debugging (when DEBUG level) - **Error conditions** - All authentication-related errors with context ### OIDC Client Debug Logs The underlying `oidc-client-ts` library has extensive debug logging. When you configure logging in Vuethenticate, it automatically configures the OIDC client logging as well, so you'll see detailed protocol-level information including: - HTTP requests and responses - Token validation steps - Silent renewal iframe operations - OIDC discovery document processing ### Production Considerations For production environments, consider: - Setting `logLevel` to `LogLevel.ERROR` or `LogLevel.NONE` - Using a custom logger that sends errors to your monitoring service - Avoiding debug-level logging to prevent performance impact - Implementing log sampling for high-traffic applications ```typescript // Production-safe logging configuration const auth = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id', logLevel: LogLevel.ERROR, logger: { error: (message, ...args) => { // Send to monitoring service monitoringService.error(message, args); }, warn: () => {}, // Suppress in production info: () => {}, // Suppress in production debug: () => {}, // Suppress in production } }); ``` ## Logging Vuethenticate provides comprehensive logging support for debugging authentication flows and OIDC protocol interactions. **Logging is silent by default** - you must explicitly configure it to see output. ### Configuration Add logging configuration to your auth config: ```typescript import { useAuth, LogLevel, consoleLogger } from 'vuethenticate' const auth = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id', // Enable debug logging logLevel: LogLevel.DEBUG, // logger: consoleLogger, // Optional: defaults to console when logLevel is set }) ``` ### Log Levels | Level | Value | Description | |-------|-------|-------------| | `LogLevel.NONE` | 0 | No logging (default) | | `LogLevel.ERROR` | 1 | Error messages only | | `LogLevel.WARN` | 2 | Warnings and errors | | `LogLevel.INFO` | 3 | Info, warnings, and errors | | `LogLevel.DEBUG` | 4 | All messages including debug info | ### Built-in Loggers | Logger | Description | |--------|-------------| | `consoleLogger` | Logs to browser console (used automatically when logLevel is set) | | `silentLogger` | No output - useful for explicitly disabling logs | ### Behavior - **Silent by default**: No logging occurs unless explicitly configured - **Smart defaults**: When you set a `logLevel`, `consoleLogger` is used automatically - **Custom loggers**: Provide your own `logger` for custom logging behavior - **OIDC integration**: Both internal auth logs and underlying OIDC client logs are controlled by the same configuration ### Custom Logger Implement the `Logger` interface for custom logging: ```typescript import type { Logger } from 'vuethenticate' const customLogger: Logger = { error: (message, ...args) => { // Send to error reporting service errorService.log(message, args) }, warn: (message, ...args) => console.warn(message, ...args), info: (message, ...args) => console.info(message, ...args), debug: (message, ...args) => { if (process.env.NODE_ENV === 'development') { console.debug(message, ...args) } }, } const auth = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id', logLevel: LogLevel.INFO, logger: customLogger, }) ``` ### What Gets Logged With logging enabled, you'll see detailed information about: - **Authentication flows**: Sign-in, sign-out, and callback processing - **Token management**: Token refresh, expiration, and renewal - **OIDC protocol**: Requests, responses, and protocol-level details - **Error handling**: Detailed error information with stack traces - **State management**: User loading/unloading events ### Development vs Production ```typescript const auth = useAuth({ authority: 'https://your-oidc-provider.com', clientId: 'your-client-id', // Enable detailed logging only in development logLevel: process.env.NODE_ENV === 'development' ? LogLevel.DEBUG : LogLevel.ERROR, }) ``` ## Advanced Usage ### Manual Authentication State Reset The `resetAuthState()` method provides a way to manually clear authentication state and remove the user from storage. This is useful for custom logout flows or error recovery scenarios. ```typescript const auth = useAuth(config); // Manually reset authentication state await auth.resetAuthState(); // This will: // - Remove user from storage // - Clear reactive user state // - Log the operation // - Handle any errors that occur during removal ``` **Common use cases:** - Custom logout flows that don't use the standard OAuth logout endpoint - Error recovery when authentication state becomes corrupted - Implementing "sign out from all devices" functionality - Testing scenarios where you need to reset authentication state ### Error Handling and State Recovery All callback components (`AuthCallback`, `LogoutCallback`, `SilentCallback`) now automatically handle errors by resetting authentication state. When an error occurs during callback processing: 1. The error is logged with appropriate details 2. Authentication state is automatically reset via `resetAuthState()` 3. The error is re-thrown for your application to handle This ensures that failed authentication flows don't leave the application in an inconsistent state. ```typescript // Callback components automatically handle errors and reset state // No additional error handling required for state consistency ``` ### Manual Cleanup The `useAuth` composable includes a `cleanup()` function for manual resource cleanup. **This is rarely needed** as the library manages resources automatically. ```typescript const auth = useAuth(config); // Only use in special scenarios like testing or custom cleanup requirements auth.cleanup(); ``` **When you might need cleanup:** - Unit testing scenarios where you need to reset state between tests - Dynamic configuration changes (though creating a new instance is usually better) - Custom cleanup logic in very specific edge cases **Note:** The cleanup function removes event listeners and clears the UserManager instance from the internal registry. Normal application usage should never require calling this function. ## License MIT