JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q90268F
  • License MIT

React authentication middleware for University of Jaffna Auth Service with time-bound roles and permissions

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.

npm version License: MIT

✨ 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-react

Peer 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:

  1. Detects 401 responses
  2. Attempts to refresh the token using the refresh token
  3. Retries the original request with the new token
  4. 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:

  • @uoj-lk/auth-node - Node.js/Express backend middleware (coming soon)

Made with ❤️ by University of Jaffna