Package Exports
- @uoj-lk/auth-react
- @uoj-lk/auth-react/package.json
Readme
@uoj-lk/auth-react
React authentication middleware for University of Jaffna Auth Service with time-bound roles, permissions, and organization context.
✨ Features
- 🔐 Complete Authentication Flow - Login, logout, automatic token refresh
- ⏰ Time-Bound Roles - Support for roles with start/end dates
- 🏢 Organization Context - Multi-organization role management
- 🎯 Permission-Based UI - Show/hide components based on permissions
- 🛡️ 8 Middleware Components - Ready-to-use access control components
- 📊 Role Expiry Tracking - Automatic warnings for expiring roles
- 🔄 Flexible Permission Checks - Support for multiple formats and combinations
- 🎨 TypeScript Support - Full type definitions included
📦 Installation
npm install @uoj-lk/auth-reactPeer Dependencies
This package requires the following peer dependencies:
npm install react react-dom react-router-dom🚀 Quick Start
1. Wrap Your App with AuthProvider
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { AuthProvider, ProtectedRoute } from "@uoj-lk/auth-react";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
function App() {
return (
<Router>
<AuthProvider
apiUrl="https://your-auth-api.com/api"
clientId="your-client-id"
clientSecret="your-client-secret"
>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</AuthProvider>
</Router>
);
}
export default App;2. Use Auth in Your Components
import { useAuth } from "@uoj-lk/auth-react";
function Dashboard() {
const { user, logout, hasPermission } = useAuth();
return (
<div>
<h1>Welcome, {user?.firstName}!</h1>
<button onClick={logout}>Logout</button>
{hasPermission("course", "create") && (
<button>Create Course</button>
)}
</div>
);
}🔐 Middleware Components
ProtectedRoute
Requires user to be authenticated.
import { ProtectedRoute } from "@uoj-lk/auth-react";
<Route
path="/dashboard"
element={
<ProtectedRoute redirectTo="/login">
<Dashboard />
</ProtectedRoute>
}
/>RequireRole
Requires user to have a specific role.
import { RequireRole } from "@uoj-lk/auth-react";
<RequireRole role="Admin">
<AdminPanel />
</RequireRole>RequirePermission
Requires user to have a specific permission.
import { RequirePermission } from "@uoj-lk/auth-react";
// Method 1: Separate resource and action
<RequirePermission resource="course" action="create">
<CreateCourseButton />
</RequirePermission>
// Method 2: Combined permission string
<RequirePermission permission="course:create">
<CreateCourseButton />
</RequirePermission>RequireRoleInOrganization
Requires user to have a role in a specific organization.
import { RequireRoleInOrganization } from "@uoj-lk/auth-react";
<RequireRoleInOrganization role="Instructor" organizationId={1}>
<CourseList />
</RequireRoleInOrganization>RequireAnyPermission
Requires user to have at least one of the specified permissions.
import { RequireAnyPermission } from "@uoj-lk/auth-react";
<RequireAnyPermission permissions={["course:create", "course:update"]}>
<CourseEditor />
</RequireAnyPermission>RequireAllPermissions
Requires user to have all of the specified permissions.
import { RequireAllPermissions } from "@uoj-lk/auth-react";
<RequireAllPermissions permissions={["course:create", "student:read"]}>
<FullCourseManager />
</RequireAllPermissions>RequireOrganization
Requires user to belong to a specific organization.
import { RequireOrganization } from "@uoj-lk/auth-react";
<RequireOrganization organizationId={1}>
<DepartmentDashboard />
</RequireOrganization>ConditionalRender
Custom condition-based rendering.
import { ConditionalRender } from "@uoj-lk/auth-react";
<ConditionalRender
condition={({ hasPermission, hasRole }) =>
hasPermission("report:generate") && hasRole("Admin")
}
>
<AdvancedReports />
</ConditionalRender>🎯 useAuth Hook
The useAuth hook provides access to authentication state and helper functions.
Available Properties
const {
// State
user, // Current user object
loading, // Loading state
error, // Error message
isAuthenticated, // Boolean flag
// Actions
login, // Login function
logout, // Logout function
// Role Checks
hasRole, // Check if user has a role
hasRoleInOrganization, // Check role in specific org
getActiveRoles, // Get all active roles
// Permission Checks
hasPermission, // Check single permission
hasAnyPermission, // Check if has any of multiple
hasAllPermissions, // Check if has all of multiple
// Organization
belongsToOrganization, // Check org membership
getOrganizations, // Get all organizations
getPermissionsForOrganization, // Get org permissions
// Time-Bound Roles
calculateDaysRemaining, // Calculate days to expiry
getExpiringRoles, // Get roles expiring soon
} = useAuth();Examples
Login
const { login } = useAuth();
const handleSubmit = async (e) => {
e.preventDefault();
const result = await login({ username, password });
if (result.success) {
navigate("/dashboard");
} else {
setError(result.error);
}
};Check Permissions
const { hasPermission, hasAnyPermission } = useAuth();
// Single permission
if (hasPermission("course", "create")) {
// Show create button
}
// Alternative format
if (hasPermission("course:create")) {
// Show create button
}
// Multiple permissions (any)
if (hasAnyPermission(["course:create", "course:update"])) {
// Show editor
}Role Expiry Tracking
const { getExpiringRoles, calculateDaysRemaining } = useAuth();
const expiringRoles = getExpiringRoles(30); // Roles expiring in 30 days
expiringRoles.forEach(role => {
const daysLeft = calculateDaysRemaining(role.endDate);
console.log(`${role.name} expires in ${daysLeft} days`);
});📊 User Object Structure
interface User {
id: number;
username: string;
email: string;
firstName: string;
lastName: string;
phone?: string;
isLdapUser: boolean;
isActive: boolean;
// Simple role names (backward compatibility)
roles: string[];
// Full role details with time-bound support
roleDetails: RoleDetail[];
// Aggregated permissions from all active roles
permissions: Permission[];
// Organizations user belongs to
organizations: Organization[];
}⚙️ Configuration
AuthProvider Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
ReactNode | Yes | - | Child components |
apiUrl |
string | No | http://localhost:5000/api |
Backend API URL |
clientId |
string | No | - | OAuth client ID |
clientSecret |
string | No | - | OAuth client secret |
Environment Variables
If you're using Vite, you can set these in .env:
VITE_API_URL=https://your-auth-api.com/api
VITE_CLIENT_ID=your-client-id
VITE_CLIENT_SECRET=your-client-secret🔄 Automatic Token Refresh
The package automatically handles token refresh when the access token expires. The API client includes an interceptor that:
- Detects 401 responses
- Attempts to refresh the token using the refresh token
- Retries the original request with the new token
- Redirects to login if refresh fails
🎨 Custom Fallbacks
All middleware components support custom fallback UI:
<RequirePermission
resource="course"
action="delete"
fallback={
<div className="alert alert-danger">
You don't have permission to delete courses.
Contact your administrator.
</div>
}
>
<DeleteButton />
</RequirePermission>📚 Advanced Examples
Conditional Navigation Menu
import { ConditionalRender } from "@uoj-lk/auth-react";
function Navigation() {
return (
<nav>
<ConditionalRender condition={({ hasPermission }) => hasPermission("course:read")}>
<NavItem to="/courses">My Courses</NavItem>
</ConditionalRender>
<ConditionalRender condition={({ hasRole }) => hasRole("Admin")}>
<NavItem to="/admin">Admin Panel</NavItem>
</ConditionalRender>
</nav>
);
}Role Expiry Warning
import { useAuth } from "@uoj-lk/auth-react";
function RoleExpiryAlert() {
const { getExpiringRoles, calculateDaysRemaining } = useAuth();
const expiringRoles = getExpiringRoles(30);
if (expiringRoles.length === 0) return null;
return (
<div className="alert alert-warning">
<strong>Warning!</strong> You have {expiringRoles.length} role(s) expiring soon:
<ul>
{expiringRoles.map((role, idx) => (
<li key={idx}>
{role.name} in {role.organization?.name} -
{calculateDaysRemaining(role.endDate)} days remaining
</li>
))}
</ul>
</div>
);
}Organization Switcher
import { useAuth } from "@uoj-lk/auth-react";
function OrganizationSelector() {
const { getOrganizations, user } = useAuth();
const organizations = getOrganizations();
return (
<select>
{organizations.map(org => (
<option key={org.id} value={org.id}>
{org.name} ({org.code})
</option>
))}
</select>
);
}🐛 Troubleshooting
"useAuth must be used within AuthProvider"
Make sure AuthProvider wraps all components that use useAuth:
// ❌ Wrong
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
<AuthProvider>...</AuthProvider>
</BrowserRouter>
// ✅ Correct
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</AuthProvider>
</BrowserRouter>Token Refresh Not Working
Ensure your backend returns a refresh token on login and has a /auth/refresh endpoint.
Permissions Not Showing
Check that your backend returns the enriched user object with:
roleDetails(array of role objects with permissions)permissions(aggregated permission array)organizations(user's organizations)
📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
Contributions are welcome! Please open an issue or submit a pull request.
📧 Support
For issues and questions:
- GitHub Issues: https://github.com/UoJ-LK/myuoj-auth/issues
- Documentation: https://github.com/UoJ-LK/myuoj-auth
🔗 Related Packages
@uoj-lk/auth-node- Node.js/Express backend middleware (coming soon)
Made with ❤️ by University of Jaffna