# 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
