@lifewind-ltda/vue-sso-client
v1.0.0
Published
Vue.js SSO client for LifeWind authentication
Downloads
10
Maintainers
Readme
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:
- Login to the LifeWind Core admin panel at
https://lifewind-core.test/admin(or your production URL) - Navigate to OAuth Clients → Create Client
- 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
- After creation, copy the Client ID — you'll need it for your frontend config
Tip: For local development, use
http://localhost:5173/sso/callbackas 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:
composer require lifewind/laravel-sso-clientThe 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 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
npm install lifewind-vue-sso-client2. Configure Plugin
// 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
// 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 router4. Create Pages
Login Page:
<!-- 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:
<!-- 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.
<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 stylingerrorClass(string): CSS classes for error message stylingredirectToAuth(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.
<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:
<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
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
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:
- User clicks login →
<LifeWindSSOButton>redirects to LifeWind OAuth provider - User authenticates → LifeWind redirects back with authorization code
- Callback handling →
<LifeWindSSOCallback>captures code and state - Token exchange → Frontend sends code to your backend API
- JWT creation → Backend validates with LifeWind and returns JWT + user data
- State management → Frontend stores token and user data automatically
- 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
<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
/* 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
// 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
// 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
<!-- 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
// 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
<!-- 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
// 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:
// 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
backendApiUrlis correct - Verify your backend allows requests from your frontend domain
Authentication loops:
- Check that your callback route is properly configured
- Ensure
redirectUriin config matches your actual callback URL - Verify state validation is working correctly
Debug Mode
// 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:
composer require lifewind/laravel-sso-clientRequired backend endpoints:
POST /sso/validate- Exchange OAuth code for JWTGET /sso/user- Get current authenticated userPOST /sso/refresh- Refresh JWT token
📄 License
MIT License. See LICENSE for details.
🤝 Support & Community
- Documentation: https://docs.lifewind.com/vue-sso
- Issues: GitHub Issues
- Discord: LifeWind Community
- Email: [email protected]
🚀 Migration Guide
From v1.x
- <SSOButton />
+ <LifeWindSSOButton />
- <SSOCallback />
+ <LifeWindSSOCallback />
// Legacy components still work but are deprecatedBeautiful, secure, and developer-friendly Vue.js authentication ✨
