@lifewind-ltda/react-sso-client
v1.0.0
Published
React SSO client for LifeWind authentication
Downloads
9
Maintainers
Readme
LifeWind React SSO Client
Complete frontend SSO authentication for React applications.
Provider, hooks, and components 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
- React Context Provider:
<SSOProvider>manages all auth state - useSSO Hook: Access authentication state and actions from any component
- Ready-to-use Components:
<SSOButton>and<SSOCallback>for quick integration - TypeScript: Full type safety and IntelliSense
- React Router: Built-in integration with React Router v6/v7
- Secure: CSRF state validation, error handling, and localStorage token storage
Quick Start
1. Installation
npm install lifewind-react-sso-clientPeer dependencies: react, react-dom, react-router-dom
2. Wrap Your App with SSOProvider
// main.tsx
import { BrowserRouter } from "react-router-dom"
import { SSOProvider } from "lifewind-react-sso-client"
import App from "./App"
const ssoConfig = {
baseUrl: "https://lifewind-core.your-domain.com",
clientId: "your_oauth_client_id",
redirectUri: "https://your-app.com/sso/callback",
backendApiUrl: "https://your-backend-api.com",
}
createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<SSOProvider config={ssoConfig}>
<App />
</SSOProvider>
</BrowserRouter>
)3. Add Routes
// App.tsx
import { Routes, Route } from "react-router-dom"
import { SSOCallback } from "lifewind-react-sso-client"
import LoginPage from "./pages/Login"
import DashboardPage from "./pages/Dashboard"
export default function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/sso/callback"
element={
<SSOCallback
successRedirect="/dashboard"
errorRedirect="/login"
/>
}
/>
<Route path="/dashboard" element={<DashboardPage />} />
</Routes>
)
}4. Add Login Button
// pages/Login.tsx
import { SSOButton } from "lifewind-react-sso-client"
export default function LoginPage() {
return (
<div>
<h1>Welcome</h1>
<SSOButton
buttonText="Sign in with LifeWind"
loadingText="Redirecting..."
onError={(err) => console.error(err)}
/>
</div>
)
}Components
SSOButton
Drop-in login button that triggers the OAuth redirect.
<SSOButton
buttonText="Sign In"
loadingText="Signing in..."
className="custom-btn-class"
onSuccess={() => console.log("Redirecting to SSO...")}
onError={(error) => console.error(error)}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| buttonText | string | "Login with SSO" | Button label |
| loadingText | string | "Authenticating..." | Label during redirect |
| className | string | Built-in Tailwind classes | CSS classes for the button |
| onSuccess | () => void | — | Called after redirect starts |
| onError | (error: string) => void | — | Called on error |
| children | ReactNode \| (isLoading: boolean) => ReactNode | — | Custom content or render prop |
Render prop example:
<SSOButton>
{(isLoading) => (
<span>{isLoading ? "Please wait..." : "Login with LifeWind"}</span>
)}
</SSOButton>SSOCallback
Handles the OAuth callback page — extracts code and state from the URL, exchanges them for a JWT, and redirects.
<SSOCallback
successRedirect="/dashboard"
errorRedirect="/login"
autoRedirect={true}
successDelay={1500}
errorDelay={3000}
onSuccess={(result) => console.log("Authenticated:", result.user)}
onError={(error) => console.error(error)}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| successRedirect | string | "/" | Redirect path on success |
| errorRedirect | string | "/login" | Redirect path on error |
| autoRedirect | boolean | true | Auto-redirect after processing |
| successDelay | number | 1500 | Delay (ms) before success redirect |
| errorDelay | number | 3000 | Delay (ms) before error redirect |
| onSuccess | (result: SSOResult) => void | — | Called on success |
| onError | (error: string) => void | — | Called on error |
useSSO Hook
Access all authentication state and actions from any component:
import { useSSO } from "lifewind-react-sso-client"
function ProfilePage() {
const {
// State
isAuthenticated, // boolean
isLoading, // boolean
user, // SSOUser | null
token, // string | null
error, // string | null
// Actions
login, // () => void — redirect to OAuth
logout, // () => void — clear auth state
handleCallback, // (code, state) => Promise<SSOResult>
refreshToken, // () => Promise<void>
getCurrentUser, // () => Promise<SSOUser>
clearError, // () => void
} = useSSO()
if (!isAuthenticated) {
return <button onClick={login}>Sign In</button>
}
return (
<div>
<h1>Welcome, {user?.name}!</h1>
<p>{user?.email}</p>
<button onClick={logout}>Sign Out</button>
</div>
)
}Configuration
SSOConfig
interface SSOConfig {
baseUrl: string // LifeWind Core URL (e.g. "https://lifewind-core.test")
clientId: string // OAuth client ID from admin panel
redirectUri: string // Frontend callback URL (e.g. "https://your-app.com/sso/callback")
backendApiUrl: string // Your Laravel backend URL (e.g. "https://api.your-app.com")
scopes?: string[] // OAuth scopes (default: ["openid", "profile", "email"])
}Environment Variables
Use environment variables for different environments:
const ssoConfig = {
baseUrl: import.meta.env.VITE_LIFEWIND_SSO_URL,
clientId: import.meta.env.VITE_LIFEWIND_CLIENT_ID,
redirectUri: `${window.location.origin}/sso/callback`,
backendApiUrl: import.meta.env.VITE_API_URL,
}# .env.development
VITE_LIFEWIND_SSO_URL=https://lifewind-core.test
VITE_LIFEWIND_CLIENT_ID=your_dev_client_id
VITE_API_URL=https://your-backend.test
# .env.production
VITE_LIFEWIND_SSO_URL=https://sso.lifewind.com
VITE_LIFEWIND_CLIENT_ID=your_prod_client_id
VITE_API_URL=https://api.your-domain.comTypeScript Types
interface SSOUser {
id: string
email: string
name: string
lifewind_uuid?: string
}
interface SSOResult {
user: SSOUser
token: string
expires_in: number
}Authentication Flow
- User clicks login —
<SSOButton>orlogin()redirects to LifeWind OAuth - User authenticates — LifeWind redirects back to your
/sso/callbackwith a code - Callback processing —
<SSOCallback>capturescodeandstatefrom URL - Token exchange — Frontend sends code to your backend's
/sso/validateendpoint - JWT returned — Backend validates with LifeWind and returns JWT + user data
- State updated — Token and user stored in
localStorageand React context - Ready — User is authenticated,
useSSO()returnsisAuthenticated: true
Advanced Usage
Route Protection
import { Navigate, Outlet } from "react-router-dom"
import { useSSO } from "lifewind-react-sso-client"
function AuthGuard() {
const { isAuthenticated, isLoading } = useSSO()
if (isLoading) return <div>Loading...</div>
if (!isAuthenticated) return <Navigate to="/login" replace />
return <Outlet />
}
// In your routes:
<Route element={<AuthGuard />}>
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Route>API Requests with Token
import { useSSO } from "lifewind-react-sso-client"
function useApi() {
const { token } = useSSO()
const fetchWithAuth = async (url: string, options: RequestInit = {}) => {
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
"Content-Type": "application/json",
...options.headers,
},
})
if (!response.ok) throw new Error(`API Error: ${response.statusText}`)
return response.json()
}
return { fetchWithAuth }
}Token Refresh on App Load
import { useEffect } from "react"
import { useSSO } from "lifewind-react-sso-client"
function App() {
const { token, user, getCurrentUser } = useSSO()
useEffect(() => {
if (token && !user) {
getCurrentUser().catch(() => {
console.log("Stored token expired")
})
}
}, [])
return <Routes>{/* ... */}</Routes>
}Troubleshooting
CORS errors:
- Ensure your Laravel backend has
sso/*paths inconfig/cors.php - Check that
backendApiUrlpoints to the correct backend URL
"Invalid state parameter" error:
- The state is stored in
sessionStorage— make sure the callback URL matches the same origin - Clear browser storage and try again
Authentication loops:
- Verify
redirectUriin config matches the OAuth client's registered redirect URI exactly - Check that
<SSOCallback>route path matchesredirectUri
Token not persisting:
- Token is stored in
localStorageunderauth_token - User data is stored under
user_data - Check browser DevTools > Application > Local Storage
Bundle Size
- ESM: ~15 KB (gzip: ~3.7 KB)
- UMD: ~17 KB (gzip: ~4.0 KB)
- Tree-shakable exports
License
MIT License. See LICENSE for details.
