org.inovus.unified-auth
v0.8.0
Published
A unified authentication library that automatically adapts to your environment, providing simple localStorage-based authentication for development and multi-tenant cookie-based SSO for production.
Readme
org.inovus.unified-auth
A unified authentication library that automatically adapts to your environment, providing simple localStorage-based authentication for development and multi-tenant cookie-based SSO for production.
Overview
unified-auth automatically detects your environment and uses the appropriate authentication strategy:
- localStorage Mode (Development/WebContainer): Simple authentication with login UI, perfect for local development and web containers
- cookieSession Mode (Production): Multi-tenant SSO using cookies and sessionStorage, designed for production environments
The library seamlessly switches between modes based on where your application is running - no configuration needed. In development, you can optionally override the mode using the mode prop on AuthProvider.
Features
localStorage Mode
- ✅ Login UI component included
- ✅ Simple localStorage-based token storage
- ✅ Works on localhost and web containers
- ✅
/apirelative URLs for proxy support - ✅ Direct
login()method for user authentication
cookieSession Mode
- ✅ Multi-tenant SSO support
- ✅ Cookie-based authentication (reads existing auth)
- ✅ Cross-subdomain authentication
- ✅ Automatic tenant discovery and switching
- ✅ SessionStorage + Cookies storage strategy
- ✅ Automatic token refresh with fallback tenants
Shared Features
- ✅ React Context API integration (
AuthProvider,useAuth) - ✅ Protected route component
- ✅ Type-safe with TypeScript
- ✅ Automatic token refresh
- ✅ Session management
Installation
npm install org.inovus.unified-authQuick Start
1. Wrap Your App with AuthProvider
import { AuthProvider } from 'org.inovus.unified-auth';
function App() {
return (
<AuthProvider>
<YourApp />
</AuthProvider>
);
}With mode override (development only):
// Force localStorage mode in development
<AuthProvider mode="local">
<YourApp />
</AuthProvider>
// Force cookieSession mode in development (for testing)
<AuthProvider mode="cookie">
<YourApp />
</AuthProvider>
// Auto-detect (default)
<AuthProvider>
<YourApp />
</AuthProvider>The library automatically:
- Detects your environment (localhost/webcontainer vs production)
- Uses localStorage mode for development (unless
modeprop overrides it) - Uses cookieSession mode for production (mode prop is ignored in production)
- Initialises authentication state
2. Use Authentication in Components
import { useAuth } from 'org.inovus.unified-auth';
function MyComponent() {
const { user, isAuthenticated, isLoading, logout } = useAuth();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) return <div>Please log in</div>;
return (
<div>
<h1>Welcome, {user?.fullName}</h1>
<button onClick={logout}>Logout</button>
</div>
);
}3. Protect Routes
import { ProtectedRoute } from 'org.inovus.unified-auth';
function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
);
}Environment Detection
The library automatically detects your environment using window.location.hostname:
localStorage Mode (Development)
Returns true for:
localhost127.0.0.1*.webcontainer.io*.webcontainer-api.io*.fly.dev
cookieSession Mode (Production)
Returns false for all other domains (e.g., *.totum.surgery)
Mode Override (Development Only)
In development (localhost/webcontainer), you can override the automatic detection using the mode prop:
// Force localStorage mode (default in dev)
<AuthProvider mode="local">
<App />
</AuthProvider>
// Force cookieSession mode (for testing production mode locally)
<AuthProvider mode="cookie">
<App />
</AuthProvider>Note: In production (non-localhost), the mode prop is ignored and cookieSession mode is always used.
Usage Modes
localStorage Mode - Development
When running on localhost or web containers, the library uses localStorage mode:
Features
- Has
login()method for direct authentication - Stores tokens in localStorage
- Uses
/apirelative URLs (requires proxy setup in dev server) - Includes LoginPage component
Proxy Setup Required
Important: localStorage mode requires a proxy configuration in your development server to route /api requests to your backend. See Configuration section for details.
Example: Login Flow
import { useAuth, LoginPage } from 'org.inovus.unified-auth';
// Use the built-in LoginPage component
function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
{/* other routes */}
</Routes>
);
}
// Or implement custom login
function CustomLogin() {
const { login, isLoading } = useAuth();
const handleLogin = async (email: string, password: string) => {
try {
await login({ login: email, password });
// User is now authenticated
} catch (error) {
console.error('Login failed:', error);
}
};
// ... your login form
}AuthContext API (localStorage Mode)
interface AuthContextType {
user: ServiceUser | null;
isAuthenticated: boolean;
isLoading: boolean;
isLocal: boolean; // Always true in localStorage mode
authMode: "local" | "cookie"; // Always "local" in localStorage mode
login: (request: LoginRequest) => Promise<LoginResponse>;
logout: () => void;
}cookieSession Mode - Production
When running in production, the library uses cookieSession mode. You can also force this mode in development using mode="cookie":
Features
- Reads authentication from cookies (set by provider apps)
- Multi-tenant support
- Automatic tenant discovery
- Tenant switching capability
- No
login()method (auth handled by provider apps)
How It Works
- Consumer Pattern: This app reads auth from cookies set by provider apps
- Automatic Discovery: Discovers all available tenants from cookies
- Tenant Selection: Selects best tenant (query param
?as=tenant, single tenant, or redirects) - Session Management: Creates sessionStorage per tab with tokens
- Token Refresh: Automatic refresh with fallback to alternative tenants
Example: Multi-Tenant Usage
import { useAuth } from 'org.inovus.unified-auth';
function UserProfile() {
const {
user,
currentTenant,
availableTenants,
logout,
logoutAllTenants
} = useAuth();
return (
<div>
<h2>Profile</h2>
<p>User: {user?.fullName}</p>
<p>Current Tenant: {currentTenant}</p>
<p>Available Tenants: {availableTenants.join(", ")}</p>
<button onClick={logout}>Logout Current Tenant</button>
<button onClick={logoutAllTenants}>Logout All Tenants</button>
</div>
);
}AuthContext API (cookieSession Mode)
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
isLocal: boolean; // Always false in cookieSession mode
authMode: "local" | "cookie"; // Always "cookie" in cookieSession mode
currentTenant: string;
availableTenants: string[];
logout: () => void;
logoutAllTenants: () => void;
}Note: switchTenant() is available on authService but not exposed via the React context. Use authService.switchTenant() directly if needed.
API Reference
AuthService
localStorage Mode Methods
// Login with email and password
await authService.login(email: string, password: string): Promise<LoginResponse>
// Check authentication status
authService.isAuthenticated(): boolean
// Get current user (without tokens)
authService.getUser(): User | null
// Get ServiceUser (with tokens)
authService.getServiceUser(): ServiceUser | null
// Get access token
authService.getAccessToken(): string | null
// Logout
authService.logout(): void
// Make authenticated fetch request
authService.authFetch(url: string, options?: RequestInit): Promise<Response>
// Refresh token
authService.refreshToken(): Promise<boolean>cookieSession Mode Methods
// Initialise app (refresh tokens if needed)
await authService.initializeApp(): Promise<boolean>
// Get current user
authService.getUser(): User | null
// Get current tenant
authService.getCurrentTenant(): string
// Get all available tenants
authService.getAvailableTenants(): string[]
// Switch to different tenant (private - not exposed via React context)
authService.switchTenant(tenantName: string): boolean
// Check authentication
authService.isAuthenticated(): boolean
// Make authenticated request (axios)
authService.request(url: string, options?: AxiosRequestConfig): Promise<AxiosResponse>
// Fetch auth session (with token refresh)
authService.fetchAuthSession(options?: { forceRefresh?: boolean }): Promise<SessionTokens | null>
// Redirect to main app
authService.redirectToMainApp(): void
// Logout current tenant
authService.logout(): void
// Logout all tenants
authService.logoutAllTenants(): voidReact Hooks
useAuth()
Returns the authentication context for the current mode.
localStorage Mode:
const {
user,
isAuthenticated,
isLoading,
isLocal, // Always true
authMode, // Always "local"
login,
logout
} = useAuth();cookieSession Mode:
const {
user,
isAuthenticated,
isLoading,
isLocal, // Always false
authMode, // Always "cookie"
currentTenant,
availableTenants,
logout,
logoutAllTenants
} = useAuth();Components
AuthProvider
Main authentication provider component.
Props:
children: ReactNode- Your app component treemode?: "auto" | "local" | "cookie"- Authentication mode (development only)"auto"(default) - Automatically detect based on environment"local"- Force localStorage mode (dev/webcontainer style)"cookie"- Force cookieSession mode (production/multi-tenant)
Note: In production (non-localhost), mode prop is ignored and cookieSession is always used.
import { AuthProvider } from 'org.inovus.unified-auth';
// Default (auto-detect)
<AuthProvider>
<App />
</AuthProvider>
// Override in development
<AuthProvider mode="local">
<App />
</AuthProvider>LoginPage
Built-in login form component (localStorage mode only).
import { LoginPage } from 'org.inovus.unified-auth';
<Route path="/login" element={<LoginPage />} />ProtectedRoute
Wraps routes that require authentication.
Behavior:
- localStorage mode: Renders
LoginPagedirectly when not authenticated (no redirect) - cookieSession mode: Redirects to
/loginwhen not authenticated
import { ProtectedRoute } from 'org.inovus.unified-auth';
<ProtectedRoute>
<YourProtectedComponent />
</ProtectedRoute>Types
ServiceUser
Complete user data including sensitive tokens:
interface ServiceUser {
userId: string;
email?: string;
fullName?: string;
role?: string;
roleGroups?: string[];
systemId?: string | null;
accessToken: string | null;
idToken: string | null;
refreshToken: string;
}User
Public user data (tokens removed):
type User = Omit<ServiceUser, 'accessToken' | 'refreshToken' | 'idToken'>;LoginRequest
interface LoginRequest {
login: string;
password: string;
}LoginResponse
interface LoginResponse {
value: ServiceUser;
errors: any[];
warnings: any[];
}Architecture
Runtime Detection Flow
Import Package
↓
detectIsLocal() checks window.location.hostname
↓
├─→ localhost/webcontainer.io/fly.dev (Development)
│ ↓
│ Check mode prop (if provided)
│ ├─→ mode="local" → localStorage Mode
│ ├─→ mode="cookie" → cookieSession Mode
│ └─→ mode="auto" (default) → localStorage Mode
│
│ localStorage Mode:
│ - Uses authService.localStorage
│ - Uses AuthProvider.localStorage
│ - Has login() method
│
│ cookieSession Mode:
│ - Uses authService.cookieSession
│ - Uses AuthProvider.cookieSession
│ - Multi-tenant SSO
│
└─→ Production domains
↓
cookieSession Mode (mode prop ignored)
- Uses authService.cookieSession
- Uses AuthProvider.cookieSession
- Multi-tenant SSOStorage Strategies
localStorage Mode:
localStorage: All tokens (accessToken, refreshToken, idToken)localStorage: User data (without tokens)
cookieSession Mode:
Cookies: Refresh token + baseDomain (cross-subdomain SSO)sessionStorage: Access token, ID token, user data (per-tab)
Migration Guide
From local-auth-public
The library maintains API compatibility. You can continue using:
import { AuthProvider, useAuth, LoginPage } from 'org.inovus.unified-auth';
// Same usage
const { login, user, logout } = useAuth();
await login({ login: email, password });From tenant-auth
The library maintains API compatibility. You can continue using:
import { AuthProvider, useAuth } from 'org.inovus.unified-auth';
// Same usage
const { currentTenant, availableTenants } = useAuth();Configuration
Proxy Setup for localStorage Mode (Development)
When using localStorage mode (localhost/webcontainer), you need to configure a proxy for API calls. The library uses relative /api URLs, so configure your dev server to proxy these requests.
Vite Configuration
// vite.config.js
export default {
server: {
proxy: {
"/api": {
target: "https://example.website.surgery", // Your API server
changeOrigin: true,
secure: true,
},
},
},
};How It Works
- Library makes request to
/api/Account/Login - Vite dev server intercepts
/api/*requests - Proxies to
https://example.website.surgery/api/Account/Login - Response is returned to the library
This works automatically - no code changes needed in the library!
Environment Variables
For Local Development (cookieSession mode)
If using cookieSession mode locally, you can set:
LOCAL_API_PORT=9000
LOCAL_TENANT_APP_PORT=3000These are used when detectIsLocal() returns true but you're using cookieSession mode.
Development
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Format code
npm run format
# Lint
npm run lintBuilding the Package
npm run buildOutputs to dist/:
index.cjs- CommonJS bundleindex.mjs- ES Module bundleindex.d.ts- TypeScript definitionsunified-auth.css- Tailwind CSS styles
Testing
The library includes tests from tenant-auth. Run with:
npm testLicense
MIT
Support
For issues and questions, please open an issue in the repository.
