npm package: @lifewind-ltda/vue-sso-client
Description: Vue.js SSO client for LifeWind authentication
Author: LifeWind
Latest version: 1.0.0

# LifeWind Vue SSO Client

**Complete frontend SSO authentication for Vue.js applications.**

Beautifully designed components and composables for seamless OAuth 2.0 authentication with LifeWind.

## Prerequisites: Register Your App in LifeWind Core

Before installing this package, you need an OAuth client registered in LifeWind Core:

1. **Login** to the LifeWind Core admin panel at `https://lifewind-core.test/admin` (or your production URL)
2. Navigate to **OAuth Clients** → **Create Client**
3. Fill in:
   - **Name**: Your app name (e.g. "Atlas Frontend")
   - **Redirect URI**: Your frontend callback URL (e.g. `https://your-app.com/sso/callback`)
   - **Grant Type**: Authorization Code
4. After creation, copy the **Client ID** — you'll need it for your frontend config

> **Tip**: For local development, use `http://localhost:5173/sso/callback` as the redirect URI (adjust port to match your Vite dev server).

## Backend Requirement

This frontend package works together with the **Laravel SSO Client** package on your backend:

```bash
composer require lifewind/laravel-sso-client
```

The backend provides the JWT token exchange endpoints (`/sso/validate`, `/sso/user`, `/sso/refresh`) that this frontend package communicates with. See the [lifewind-laravel-sso-client README](https://github.com/lifewind/laravel-sso-client) for backend setup.

## 🌟 Features

- ✅ **Complete OAuth Flow**: Handles redirect, callback, and token exchange
- ✅ **Vue 3 Components**: Ready-to-use `<LifeWindSSOButton>` and `<LifeWindSSOCallback>`
- ✅ **Composable API**: Reactive `useSSO()` for authentication state management
- ✅ **TypeScript Support**: Full type safety and IntelliSense
- ✅ **Customizable**: Slots, events, styling, and behavior options
- ✅ **Secure**: State validation, error handling, and secure token storage

## 🚀 Quick Start

### 1. Installation

```bash
npm install lifewind-vue-sso-client
```

### 2. Configure Plugin

```typescript
// main.ts
import { createApp } from 'vue'
import LifeWindSSO from 'lifewind-vue-sso-client'
import App from './App.vue'

const app = createApp(App)

app.use(LifeWindSSO, {
  baseUrl: 'https://sso.lifewind.com',
  clientId: 'your_oauth_client_id',
  redirectUri: 'https://your-app.com/sso/callback',
  backendApiUrl: 'https://your-backend-api.com'
})

app.mount('#app')
```

### 3. Add Router Routes

```typescript
// router.ts or router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/sso/callback',
    name: 'SSOCallback',
    component: () => import('./pages/SSOCallback.vue')
  },
  // ... your other routes
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
```

### 4. Create Pages

**Login Page:**
```vue
<!-- pages/Login.vue -->
<template>
  <div class="login-container">
    <h1>Welcome to Your App</h1>
    
    <LifeWindSSOButton 
      @success="handleSuccess"
      @error="handleError"
      button-text="Sign in with LifeWind"
      loading-text="Signing you in..."
      button-class="btn btn-primary btn-large"
    />
    
    <p v-if="errorMessage" class="error">{{ errorMessage }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import type { SSOResult } from 'lifewind-vue-sso-client'

const router = useRouter()
const errorMessage = ref('')

const handleSuccess = (result: SSOResult) => {
  console.log('Authentication successful:', result.user)
  // Token and user data automatically stored in localStorage
  router.push('/dashboard')
}

const handleError = (error: string) => {
  console.error('Authentication failed:', error)
  errorMessage.value = error
}
</script>
```

**Callback Page:**
```vue
<!-- pages/SSOCallback.vue -->
<template>
  <LifeWindSSOCallback 
    @success="handleSuccess"
    @error="handleError"
    success-redirect="/dashboard"
    error-redirect="/login"
  />
</template>

<script setup lang="ts">
import type { SSOResult } from 'lifewind-vue-sso-client'

const handleSuccess = (result: SSOResult) => {
  console.log('Callback successful:', result.user)
  // User will be automatically redirected
}

const handleError = (error: string) => {
  console.error('Callback failed:', error)
  // User will be automatically redirected to error page
}
</script>
```

## 🧩 Components

### LifeWindSSOButton

Beautiful, customizable login button with built-in OAuth flow handling.

```vue
<template>
  <LifeWindSSOButton 
    button-text="Sign In"
    loading-text="Signing in..."
    button-class="custom-btn-class"
    error-class="custom-error-class"
    :redirect-to-auth="true"
    @success="onSuccess"
    @error="onError"
    @loading="onLoadingChange"
  >
    <!-- Custom icon slot -->
    <template #icon>
      <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
        <path d="M10 2L3 7v11a1 1 0 001 1h12a1 1 0 001-1V7l-7-5z"/>
      </svg>
    </template>
    
    <!-- Custom loading icon -->
    <template #loading-icon>
      <div class="spinner"></div>
    </template>
    
    <!-- Custom button content with reactive data -->
    <template #default="{ isLoading }">
      {{ isLoading ? 'Please wait...' : 'Login with LifeWind' }}
    </template>
  </LifeWindSSOButton>
</template>
```

**Props:**
- `buttonText` (string): Button text - default: "Login with SSO"
- `loadingText` (string): Loading state text - default: "Authenticating..."
- `buttonClass` (string): CSS classes for button styling
- `errorClass` (string): CSS classes for error message styling
- `redirectToAuth` (boolean): Auto-redirect to OAuth provider - default: true

**Events:**
- `@success(result: SSOResult)`: Authentication successful
- `@error(error: string)`: Authentication failed
- `@loading(isLoading: boolean)`: Loading state changed

### LifeWindSSOCallback

Handles OAuth callback processing with beautiful loading and error states.

```vue
<template>
  <LifeWindSSOCallback
    success-redirect="/dashboard"
    error-redirect="/login"
    :auto-redirect="true"
    @success="onSuccess"
    @error="onError"
  />
</template>
```

**Props:**
- `successRedirect` (string): Where to redirect on success - default: "/"
- `errorRedirect` (string): Where to redirect on error - default: "/login"
- `autoRedirect` (boolean): Auto-redirect after processing - default: true

**Events:**
- `@success(result: SSOResult)`: Callback processed successfully
- `@error(error: string)`: Callback processing failed

## 🪝 Composable API

### useSSO()

Reactive authentication state and methods for full control:

```vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { useSSO } from 'lifewind-vue-sso-client'

const {
  // Reactive State
  isAuthenticated,  // ComputedRef<boolean>
  isLoading,       // ComputedRef<boolean>
  user,            // ComputedRef<SSOUser | null>
  token,           // ComputedRef<string | null>
  error,           // ComputedRef<string | null>
  
  // Actions
  login,           // () => void - Redirect to OAuth
  logout,          // () => void - Clear auth state
  handleCallback,  // (code: string, state: string) => Promise<SSOResult>
  refreshToken,    // () => Promise<void>
  getCurrentUser,  // () => Promise<SSOUser>
  clearError       // () => void
} = useSSO()

// Check authentication on mount
onMounted(async () => {
  if (token.value && !user.value) {
    try {
      await getCurrentUser()
    } catch (err) {
      console.log('Token expired, need to login again')
    }
  }
})
</script>

<template>
  <div>
    <!-- Authenticated State -->
    <div v-if="isAuthenticated" class="user-profile">
      <img :src="user.avatar" :alt="user.name" class="avatar">
      <div>
        <h3>Welcome, {{ user.name }}!</h3>
        <p class="email">{{ user.email }}</p>
        <button @click="logout" class="btn btn-outline">
          Sign Out
        </button>
      </div>
    </div>
    
    <!-- Unauthenticated State -->
    <div v-else class="login-prompt">
      <h2>Please sign in to continue</h2>
      <button 
        @click="login" 
        :disabled="isLoading"
        class="btn btn-primary"
      >
        {{ isLoading ? 'Signing in...' : 'Sign In' }}
      </button>
    </div>
    
    <!-- Error State -->
    <div v-if="error" class="error-message">
      <p>{{ error }}</p>
      <button @click="clearError" class="btn btn-small">
        Dismiss
      </button>
    </div>
    
    <!-- Loading State -->
    <div v-if="isLoading" class="loading-overlay">
      <div class="spinner"></div>
      <p>Authenticating...</p>
    </div>
  </div>
</template>
```

## 🔧 Configuration

### Plugin Configuration

```typescript
interface LifeWindSSOOptions {
  baseUrl: string        // LifeWind OAuth provider URL
  clientId: string       // OAuth client ID  
  redirectUri: string    // OAuth callback URL (your frontend)
  backendApiUrl: string  // Your Laravel backend API URL
  scopes?: string[]      // OAuth scopes (default: ['openid', 'profile', 'email'])
}

// Example configuration
app.use(LifeWindSSO, {
  baseUrl: 'https://sso.lifewind.com',
  clientId: 'your_client_id_here',
  redirectUri: 'https://app.yoursite.com/sso/callback', 
  backendApiUrl: 'https://api.yoursite.com',
  scopes: ['openid', 'profile', 'email', 'roles'] // optional
})
```

### TypeScript Types

```typescript
interface SSOUser {
  id: string
  email: string
  name: string
  lifewind_uuid?: string
}

interface SSOResult {
  user: SSOUser
  token: string
  expires_in: number
}

interface SSOConfig {
  baseUrl: string
  clientId: string
  redirectUri: string
  backendApiUrl: string
  scopes?: string[]
}
```

## 🔄 Authentication Flow

The package handles the complete OAuth 2.0 flow:

1. **User clicks login** → `<LifeWindSSOButton>` redirects to LifeWind OAuth provider
2. **User authenticates** → LifeWind redirects back with authorization code
3. **Callback handling** → `<LifeWindSSOCallback>` captures code and state
4. **Token exchange** → Frontend sends code to your backend API
5. **JWT creation** → Backend validates with LifeWind and returns JWT + user data
6. **State management** → Frontend stores token and user data automatically
7. **Ready to use** → User is authenticated and can access protected features

## 🛡️ Security Features

- ✅ **State Validation**: Prevents CSRF attacks during OAuth flow
- ✅ **Secure Storage**: Tokens stored in localStorage (not cookies)
- ✅ **Error Handling**: Comprehensive error states and user feedback
- ✅ **Token Refresh**: Automatic token renewal before expiration
- ✅ **URL Cleanup**: No sensitive tokens left in URL parameters
- ✅ **XSS Protection**: Secure token handling and sanitized user data

## 🎨 Styling & Customization

### Custom Button Styles

```vue
<template>
  <LifeWindSSOButton 
    button-class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-6 rounded-lg shadow-lg transition duration-200"
    error-class="text-red-600 text-sm mt-2"
  >
    <template #icon>
      <svg class="w-5 h-5 mr-2" fill="currentColor">
        <!-- Your custom icon -->
      </svg>
    </template>
  </LifeWindSSOButton>
</template>
```

### CSS Variables

```css
/* Customize default styles */
:root {
  --lifewind-sso-primary-color: #3b82f6;
  --lifewind-sso-primary-hover: #2563eb;
  --lifewind-sso-error-color: #dc2626;
  --lifewind-sso-border-radius: 0.5rem;
  --lifewind-sso-font-family: 'Inter', sans-serif;
}
```

## 🌍 Advanced Usage

### Route Protection

```typescript
// router/index.ts
import { useSSO } from 'lifewind-vue-sso-client'

router.beforeEach(async (to) => {
  if (to.meta.requiresAuth) {
    const { isAuthenticated, token } = useSSO()
    
    if (!isAuthenticated.value) {
      return {
        name: 'Login',
        query: { redirect: to.fullPath }
      }
    }
  }
})
```

### API Integration

```typescript
// composables/useApi.ts
import { useSSO } from 'lifewind-vue-sso-client'

export function useApi() {
  const { token } = useSSO()
  
  const apiCall = async (url: string, options: RequestInit = {}) => {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Authorization': `Bearer ${token.value}`,
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        ...options.headers
      }
    })
    
    if (!response.ok) {
      throw new Error(`API Error: ${response.statusText}`)
    }
    
    return response.json()
  }
  
  return { apiCall }
}
```

### Global State Management

```vue
<!-- App.vue -->
<script setup lang="ts">
import { provide, onMounted } from 'vue'
import { useSSO } from 'lifewind-vue-sso-client'

const sso = useSSO()

// Provide SSO state globally
provide('sso', sso)

// Check authentication on app load
onMounted(async () => {
  if (sso.token.value && !sso.user.value) {
    try {
      await sso.getCurrentUser()
    } catch (error) {
      console.log('Stored token is invalid')
      sso.logout()
    }
  }
})
</script>

<template>
  <div id="app">
    <!-- Global loading overlay -->
    <div v-if="sso.isLoading.value" class="global-loading">
      <div class="spinner"></div>
    </div>
    
    <!-- App content -->
    <router-view />
  </div>
</template>
```

## 🚨 Production Setup

### Environment Configuration

```typescript
// main.ts - Production setup
const config = {
  development: {
    baseUrl: 'https://sso-dev.lifewind.com',
    backendApiUrl: 'https://api-dev.yoursite.com'
  },
  production: {
    baseUrl: 'https://sso.lifewind.com', 
    backendApiUrl: 'https://api.yoursite.com'
  }
}

const env = import.meta.env.MODE
const ssoConfig = config[env] || config.production

app.use(LifeWindSSO, {
  ...ssoConfig,
  clientId: import.meta.env.VITE_LIFEWIND_CLIENT_ID,
  redirectUri: `${window.location.origin}/sso/callback`
})
```

### Error Boundary

```vue
<!-- components/ErrorBoundary.vue -->
<template>
  <div v-if="error" class="error-boundary">
    <h2>Authentication Error</h2>
    <p>{{ error }}</p>
    <button @click="retry" class="btn btn-primary">
      Try Again
    </button>
  </div>
  <slot v-else />
</template>

<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'
import { useSSO } from 'lifewind-vue-sso-client'

const error = ref('')
const { clearError, logout } = useSSO()

const retry = () => {
  error.value = ''
  clearError()
}

onErrorCaptured((err) => {
  if (err.message.includes('authentication')) {
    error.value = err.message
    logout() // Clear invalid state
    return false // Stop propagation
  }
})
</script>
```

## 🧪 Testing

### Unit Testing

```typescript
// tests/components/LoginButton.test.ts
import { mount } from '@vue/test-utils'
import { LifeWindSSOButton } from 'lifewind-vue-sso-client'

describe('LifeWindSSOButton', () => {
  it('renders correctly', () => {
    const wrapper = mount(LifeWindSSOButton, {
      props: {
        buttonText: 'Test Login'
      },
      global: {
        provide: {
          ssoClient: mockSSOClient
        }
      }
    })
    
    expect(wrapper.text()).toContain('Test Login')
  })
})
```

## 📋 Troubleshooting

### Common Issues

**Components not found:**
```typescript
// Make sure you're using the plugin correctly
app.use(LifeWindSSO, { /* config */ })

// Or import components manually
import { LifeWindSSOButton } from 'lifewind-vue-sso-client'
```

**CORS errors:**
- Ensure your backend has proper CORS configuration
- Check that your `backendApiUrl` is correct
- Verify your backend allows requests from your frontend domain

**Authentication loops:**
- Check that your callback route is properly configured
- Ensure `redirectUri` in config matches your actual callback URL
- Verify state validation is working correctly

### Debug Mode

```typescript
// Enable debug logging
if (import.meta.env.DEV) {
  window.addEventListener('lifewind-sso-debug', (event) => {
    console.log('SSO Debug:', event.detail)
  })
}
```

## 📦 Bundle Size

- **Gzipped**: ~4KB
- **Components**: Tree-shakable
- **Dependencies**: Vue 3, Vue Router (peer dependencies)

## 🤝 Backend Integration

This package works seamlessly with:

**Laravel SSO Client:**
```bash
composer require lifewind/laravel-sso-client
```

**Required backend endpoints:**
- `POST /sso/validate` - Exchange OAuth code for JWT
- `GET /sso/user` - Get current authenticated user
- `POST /sso/refresh` - Refresh JWT token

## 📄 License

MIT License. See [LICENSE](LICENSE) for details.

## 🤝 Support & Community

- **Documentation**: [https://docs.lifewind.com/vue-sso](https://docs.lifewind.com/vue-sso)
- **Issues**: [GitHub Issues](https://github.com/lifewind/vue-sso-client/issues)
- **Discord**: [LifeWind Community](https://discord.gg/lifewind)
- **Email**: [support@lifewind.com](mailto:support@lifewind.com)

## 🚀 Migration Guide

### From v1.x

```diff
- <SSOButton />
+ <LifeWindSSOButton />

- <SSOCallback />
+ <LifeWindSSOCallback />

// Legacy components still work but are deprecated
```

---

**Beautiful, secure, and developer-friendly Vue.js authentication** ✨