lbyte-authentication-service
v1.0.1
Published
Shared Authentication Service for React microfrontends (single-spa compatible)
Maintainers
Readme
LByte Authentication Service
A comprehensive authentication service library for React microfrontends using single-spa architecture. This library provides centralized authentication state management, HTTP client with automatic token handling, and reusable components.
🚀 Features
- Centralized Auth State: React Context-based authentication state management
- HTTP Client: Built-in API client with automatic token injection and refresh
- TypeScript Support: Full type definitions included
- Microfrontend Ready: Designed for single-spa and module federation
- Token Management: Automatic token refresh and error handling
- Testing Support: Comprehensive test coverage with Jest and RTL
📦 Installation
npm install lbyte-authentication-servicePeer Dependencies
npm install react react-dom🔧 Basic Setup
1. Wrap your root application with AuthServiceProvider
// shell/root application
import React from 'react';
import { AuthServiceProvider } from 'lbyte-authentication-service';
import { AppRouter } from './router';
function App() {
return (
<AuthServiceProvider>
<AppRouter />
</AuthServiceProvider>
);
}
export default App;2. Use authentication in your components
// Any microfrontend component
import React from 'react';
import { useAuthService } from 'lbyte-authentication-service';
function LoginComponent() {
const { isAuthenticated, user, setAuth, clearAuth } = useAuthService();
const handleLogin = async () => {
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const { token, user } = await response.json();
setAuth({
isAuthenticated: true,
user: { id: user.id, name: user.name },
token
});
} catch (error) {
console.error('Login failed:', error);
}
};
if (isAuthenticated) {
return (
<div>
<h2>Welcome, {user?.name}!</h2>
<button onClick={clearAuth}>Logout</button>
</div>
);
}
return (
<div>
<button onClick={handleLogin}>Login</button>
</div>
);
}3. Use the API client for authenticated requests
import React, { useEffect, useState } from 'react';
import { useApiClient } from 'lbyte-authentication-service';
function ProfileComponent() {
const [profile, setProfile] = useState(null);
const apiClient = useApiClient();
useEffect(() => {
const fetchProfile = async () => {
try {
const response = await apiClient.get('/user/profile');
const profileData = await response.json();
setProfile(profileData);
} catch (error) {
console.error('Failed to fetch profile:', error);
}
};
fetchProfile();
}, [apiClient]);
return (
<div>
{profile ? (
<div>
<h3>Profile</h3>
<p>Email: {profile.email}</p>
<p>Role: {profile.role}</p>
</div>
) : (
<p>Loading profile...</p>
)}
</div>
);
}🏗️ Advanced Usage
Custom API Client Configuration
import { apiClient } from 'lbyte-authentication-service';
// Configure base URL and default headers
apiClient.initialize(
() => localStorage.getItem('token'), // Token getter
() => {
// Token expired handler
localStorage.removeItem('token');
window.location.href = '/login';
},
async () => {
// Optional: Token refresh handler
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken })
});
if (response.ok) {
const { token } = await response.json();
localStorage.setItem('token', token);
return true;
}
return false;
} catch {
return false;
}
}
);Protected Routes
import React from 'react';
import { useAuthService } from 'lbyte-authentication-service';
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated } = useAuthService();
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
}
// Usage
function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
);
}🧪 Testing
Testing Components with Auth Service
import React from 'react';
import { render, screen } from '@testing-library/react';
import { AuthServiceProvider } from 'lbyte-authentication-service';
import { MyComponent } from './MyComponent';
function renderWithAuth(ui: React.ReactElement) {
return render(
<AuthServiceProvider>
{ui}
</AuthServiceProvider>
);
}
test('renders component with authentication', () => {
renderWithAuth(<MyComponent />);
expect(screen.getByText(/login/i)).toBeInTheDocument();
});Mocking API Client
import { apiClient } from 'lbyte-authentication-service';
// Mock API client in tests
jest.mock('lbyte-authentication-service', () => ({
...jest.requireActual('lbyte-authentication-service'),
useApiClient: () => ({
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
delete: jest.fn(),
}),
}));API Configuration
The library provides flexible base URL configuration for different environments and deployment scenarios.
Global Configuration (Recommended)
Configure the API base URL globally for all API calls:
import { setGlobalApiConfig } from '@your-org/lbyte-authentication-service';
// Set at application startup
setGlobalApiConfig({
baseURL: 'https://api.example.com'
});Per-instance Configuration
Create a custom ApiClient instance with specific configuration:
import { ApiClient } from '@your-org/lbyte-authentication-service';
const customApiClient = new ApiClient({
baseURL: 'https://api.different.com'
});Environment-based Configuration
Configure different base URLs for different environments:
import { setGlobalApiConfig } from '@your-org/lbyte-authentication-service';
// In your microfrontend's initialization
const getApiBaseURL = () => {
if (window.location.hostname === 'localhost') {
return 'http://localhost:3001';
} else if (window.location.hostname.includes('staging')) {
return 'https://staging-api.example.com';
} else {
return 'https://api.example.com';
}
};
setGlobalApiConfig({
baseURL: getApiBaseURL()
});Runtime Configuration Updates
Update configuration at runtime if needed:
import { setGlobalApiConfig, getGlobalApiConfig } from '@your-org/lbyte-authentication-service';
// Get current configuration
const currentConfig = getGlobalApiConfig();
console.log('Current base URL:', currentConfig.baseURL);
// Update configuration
setGlobalApiConfig({
...currentConfig,
baseURL: 'https://new-api.example.com'
});API Reference
AuthService
Types
interface AuthState {
isAuthenticated: boolean;
user: null | { id: string; name: string };
token: string | null;
}
interface AuthServiceContextProps extends AuthState {
setAuth: (auth: Partial<AuthState>) => void;
clearAuth: () => void;
}useAuthService Hook
const {
isAuthenticated, // boolean
user, // { id: string; name: string } | null
token, // string | null
setAuth, // (auth: Partial<AuthState>) => void
clearAuth // () => void
} = useAuthService();API Client
Methods
// Basic HTTP methods
await apiClient.get('/endpoint');
await apiClient.post('/endpoint', data);
await apiClient.put('/endpoint', data);
await apiClient.delete('/endpoint');
// Generic request method
await apiClient.request('/endpoint', {
method: 'PATCH',
body: JSON.stringify(data),
headers: { 'Custom-Header': 'value' }
});Configuration
interface ApiClientConfig {
baseURL?: string;
defaultHeaders?: Record<string, string>;
}🏢 Microfrontend Architecture
Single-SPA Integration
// root-config.js
import { registerApplication, start } from 'single-spa';
registerApplication({
name: 'shell',
app: () => import('./shell/shell.app.js'),
activeWhen: '/',
});
registerApplication({
name: 'mf-dashboard',
app: () => import('./dashboard/dashboard.app.js'),
activeWhen: '/dashboard',
});
start();// shell.app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { AuthServiceProvider } from 'lbyte-authentication-service';
function Shell() {
return (
<AuthServiceProvider>
<div id="single-spa-application:shell">
<nav>Navigation</nav>
<main id="microfrontend-content" />
</div>
</AuthServiceProvider>
);
}
export const mount = (props) => {
ReactDOM.render(<Shell />, props.domElement);
};Module Federation
// webpack.config.js
const ModuleFederationPlugin = require('@module-federation/webpack');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
dashboard: 'dashboard@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'lbyte-authentication-service': { singleton: true },
},
}),
],
};🔒 Security Best Practices
- Token Storage: Use secure storage (httpOnly cookies) for production
- Token Refresh: Implement automatic token refresh logic
- HTTPS Only: Always use HTTPS in production
- CSRF Protection: Implement CSRF tokens for state-changing operations
- Input Validation: Validate all inputs on both client and server
🛠️ Development
Building the Library
npm run buildRunning Tests
npm test
npm run test:watchLinting and Formatting
npm run lint
npm run lint:fix
npm run format📝 License
MIT
📋 Changelog
v1.0.1 (2025-09-11)
- Fixed: ApiClient URL construction for full URLs with ports
- Fixed: Automatic
/apiprefix being added to all requests - Enhanced: Smart URL concatenation that handles both full URLs and relative paths
- Added: Comprehensive URL construction tests
- Improved: Documentation with URL construction examples
v1.0.0 (2025-09-11)
- Initial release
- React Context-based authentication service
- HTTP client with automatic token handling
- TypeScript support
- Single-spa and microfrontend compatibility
- Comprehensive test coverage
🤝 Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
📞 Support
For issues and questions, please open an issue on the GitHub repository.
