vuethenticate
Version:
A Vue 3 authentication state management library using oidc-client-ts
686 lines (530 loc) • 20.3 kB
Markdown
# 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