Skip to content

jmndao/auth-flow

Repository files navigation

AuthFlow

Simple, clean authentication flow with separated permission system for client-side applications.

Features

  • JWT access + refresh token authentication
  • Automatic token refresh on expiration
  • Clean separation between authentication and permissions
  • Component-wrappable permission system
  • Custom validation support
  • Lightweight with zero dependencies
  • Client-side focused

Installation

npm install @jmndao/auth-flow

Architecture

AuthFlow uses a clean separation of concerns:

  • Auth: Handles authentication (login, logout, token management)
  • Permissions: Handles authorization (roles, permissions, guards)

Quick Start

import { createAuthFlow, Permissions } from '@jmndao/auth-flow';

// Create auth instance
const auth = createAuthFlow({
  baseURL: 'https://api.example.com',
});

// Login
await auth.login({
  email: 'user@example.com',
  password: 'password',
});

// Make authenticated requests
const profile = await auth.get('/user/profile');

// Check authentication (token validity)
if (auth.isAuthenticated()) {
  console.log('User is authenticated');
}

// Create permission checker
const permissions = Permissions.createPermissionChecker(auth);

// Check permissions separately
if (permissions.hasRole('admin')) {
  console.log('User is admin');
}

if (permissions.hasPermission('posts:write')) {
  console.log('User can write posts');
}

Authentication with Custom Validation

Configuration-Based Custom Validation

const auth = createAuthFlow({
  baseURL: 'https://api.example.com',
  validateAuth: (tokens) => {
    // Custom business logic
    if (!tokens) return false;

    // Example: Check if user is in working hours
    const now = new Date();
    const isWorkingHours = now.getHours() >= 9 && now.getHours() < 17;

    return isWorkingHours && customBusinessLogic(tokens);
  },
});

// Uses custom validator
auth.isAuthenticated();

Parameter-Based Override

const auth = createAuthFlow({
  baseURL: 'https://api.example.com',
  validateAuth: () => false, // Config always denies
});

// Default behavior (uses config)
auth.isAuthenticated(); // false

// Override with parameter
auth.isAuthenticated(() => true); // true
auth.isAuthenticated(customValidator);

Using Permission Validators in Auth

const auth = createAuthFlow({
  baseURL: 'https://api.example.com',
  // Use permission validator as auth validator
  validateAuth: Permissions.RBAC.requireRole('admin'),
});

// Only admin users will be considered "authenticated"
auth.isAuthenticated();

Permission System

Permission Checker

const permissions = Permissions.createPermissionChecker(auth);

// Role checks
permissions.hasRole('admin');
permissions.hasAnyRole('admin', 'moderator');
permissions.hasAllRoles('editor', 'reviewer');

// Permission checks
permissions.hasPermission('posts:write');
permissions.hasAnyPermission('posts:read', 'posts:write');
permissions.hasAllPermissions('posts:write', 'posts:publish');

// Attribute checks
permissions.hasAttribute('department', 'engineering');

// Get raw claims
const claims = permissions.getClaims();

// Custom validation
permissions.check((tokens) => customLogic(tokens));

Permission Validators

Use as standalone validators or in auth configuration:

// RBAC validators
const adminValidator = Permissions.RBAC.requireRole('admin');
const editorValidator = Permissions.RBAC.requireAnyRole('editor', 'author');

// ABAC validators
const writeValidator = Permissions.ABAC.requirePermission('posts:write');
const deptValidator = Permissions.ABAC.create({
  rules: [Permissions.Rules.inDepartment('engineering')],
  mode: 'all',
});

// Combine validators
const complexValidator = Permissions.combineValidators(adminValidator, writeValidator, (tokens) =>
  customBusinessLogic(tokens)
);

// Use in auth
const auth = createAuthFlow({
  baseURL: 'https://api.example.com',
  validateAuth: complexValidator,
});

Component Guards

Create framework-specific permission guards:

// React example
const RequireRole = Permissions.createRoleGuard((hasRole, children, fallback) => {
  return hasRole ? children : (fallback || null);
});

const RequirePermission = Permissions.createPermissionGrantGuard((hasPermission, children, fallback) => {
  return hasPermission ? children : (fallback || null);
});

// Usage in components
function AdminPanel() {
  return (
    <RequireRole
      auth={auth}
      role="admin"
      fallback={<AccessDenied />}
      onDenied={() => console.log('Access denied')}
    >
      <AdminContent />
    </RequireRole>
  );
}

function PostEditor() {
  return (
    <RequirePermission
      auth={auth}
      permission="posts:write"
      fallback={<ReadOnlyView />}
    >
      <EditForm />
    </RequirePermission>
  );
}

Combining Auth and Permissions

Flexible Validation Patterns

const auth = createAuthFlow({
  baseURL: 'https://api.example.com',
  validateAuth: (tokens) => {
    // Base business logic validation
    return tokens !== null && isWorkingHours();
  },
});

const permissions = Permissions.createPermissionChecker(auth);

// Different validation levels
if (auth.isAuthenticated()) {
  // User passes business logic validation
}

if (auth.isAuthenticated(Permissions.RBAC.requireRole('admin'))) {
  // User passes business logic AND has admin role
}

if (permissions.hasRole('admin')) {
  // User has admin role (independent of auth validation)
}

// Complex combined validation
const isSeniorManager = (tokens) => {
  const roleCheck = Permissions.RBAC.requireRole('manager')(tokens);
  const attrCheck = permissions.hasAttribute('level', 'senior');
  const businessCheck = customBusinessLogic(tokens);

  return roleCheck && attrCheck && businessCheck;
};

if (auth.isAuthenticated(isSeniorManager)) {
  // Senior manager with business logic validation
}

API Reference

Auth Methods

  • auth.login(credentials) - Authenticate user
  • auth.logout() - Log out and clear tokens
  • auth.isAuthenticated(validator?) - Check authentication
  • auth.getTokens() - Get stored tokens
  • auth.setTokens(tokens) - Set tokens manually
  • auth.get/post/put/patch/delete(url, data?, config?) - HTTP methods

Permission Methods

  • permissions.hasRole(role) - Check single role
  • permissions.hasAnyRole(...roles) - Check any role
  • permissions.hasAllRoles(...roles) - Check all roles
  • permissions.hasPermission(permission) - Check single permission
  • permissions.hasAnyPermission(...permissions) - Check any permission
  • permissions.hasAllPermissions(...permissions) - Check all permissions
  • permissions.hasAttribute(key, value) - Check attribute value
  • permissions.getClaims() - Get JWT claims
  • permissions.check(validator) - Custom validation

Validators

  • Permissions.RBAC.requireRole(role) - Role validator
  • Permissions.RBAC.requireAnyRole(...roles) - Any role validator
  • Permissions.RBAC.requireAllRoles(...roles) - All roles validator
  • Permissions.ABAC.requirePermission(permission) - Permission validator
  • Permissions.ABAC.create(config) - Custom ABAC validator
  • Permissions.combineValidators(...validators) - Combine multiple

JWT Token Structure

Expected JWT payload structure:

{
  "sub": "user123",
  "roles": ["admin", "user"],
  "permissions": ["posts:read", "posts:write"],
  "department": "engineering",
  "level": "senior",
  "exp": 1640995200
}

License

MIT

About

Simple, clean authentication flow for client-side applications with automatic token management

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published