8000 GitHub - theriturajps/itoken: iToken is a robust Node.js library for token-based authentication inspired by JWT
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

iToken is a robust Node.js library for token-based authentication inspired by JWT

License

Notifications You must be signed in to change notification settings

theriturajps/itoken

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

iToken

npm version license node

iToken is a robust Node.js library for creating, verifying, and managing authentication tokens with enhanced security features. Inspired by JWT but designed for stricter security practices, it simplifies token-based authentication while prioritizing protection against tampering, expiration, and misuse.

Installation

Install the package using npm:

npm install itoken

Or using yarn:

yarn add itoken

Quick Start

const { iToken } = require('itoken');

// Create a token
const payload = { userId: '123', role: 'admin' };
const secret = 'your-secure-secret-key';
const token = iToken.encode(payload, secret, { expiresIn: '1h' });

// Verify and decode a token
try {
  const decoded = iToken.decode(token, secret);
  console.log(decoded.payload); // { userId: '123', role: 'admin', iat: 1649267348, exp: 1649270948 }
} catch (error) {
  console.error('Token validation failed:', error.message);
}

// Simple verification (returns boolean)
const isValid = iToken.verify(token, secret);

API Reference

iToken.encode(payload, secret, options)

Creates a signed token.

Parameters:

  • payload <Object>: The data to encode (must be a plain object)
  • secret <String>: The secret key used for signing (minimum 8 characters)
  • options <Object> (optional):
    • expiresIn <String|Number>: Token expiration time (e.g., '1h', '7d', or seconds)
    • notBefore <String|Number>: Time before which the token is not valid
    • issuer <String>: Token issuer identifier (iss claim)
    • subject <String>: Token subject identifier (sub claim)
    • audience <String>: Token audience identifier (aud claim)
    • header <Object>: Additional header values to include

Returns: <String> - A signed iToken token string.

Example:

// Creating a token that expires in 1 hour with custom claims
const token = iToken.encode(
  { userId: '123', role: 'admin' },
  'your-secret-key',
  { 
    expiresIn: '1h',
    issuer: 'api.example.com',
    audience: 'client-app'
  }
);

iToken.decode(token, secret, options)

Verifies and decodes a token.

Parameters:

  • token <String>: The token to decode
  • secret <String>: The secret key used for signing
  • options <Object> (optional):
    • skipTimeValidation <Boolean>: Skip expiration and notBefore validation
    • ignoreExpiration (Boolean): Bypass expiration check
    • ignoreNotBefore (Boolean): Bypass activation check

Returns: <Object> - An object with header, payload, and signature properties.

Example:

try {
  const { header, payload, signature } = iToken.decode(token, 'your-secret-key');
  console.log(`User ID: ${payload.userId}, Role: ${payload.role}`);
  console.log(`Token was issued at: ${new Date(payload.iat * 1000)}`);
  console.log(`Token expires at: ${new Date(payload.exp * 1000)}`);
} catch (error) {
  console.error(`Decoding error: ${error.message}`);
}

iToken.verify(token, secret, options)

Quick validation without returning the decoded data.

Parameters:

  • token <String>: The token to verify
  • secret <String>: The secret key used for signing
  • options <Object> (optional):
    • ignoreExpiration <Boolean>: Ignore expiration time validation
    • ignoreNotBefore <Boolean>: Ignore notBefore time validation

Returns: <Boolean> - true if the token is valid, otherwise false.

Example:

// Standard verification - checks signature, expiration, and notBefore claims
if (iToken.verify(token, 'your-secret-key')) {
  console.log('Token is valid');
} else {
  console.log('Token is invalid');
}

// Verification with options - ignore expiration
if (iToken.verify(token, 'your-secret-key', { ignoreExpiration: true })) {
  console.log('Token signature is valid (ignoring expiration)');
}

iToken.refresh(token, secret, newOptions)

Refreshes an existing token with same payload but fresh timestamps.

Parameters:

  • token <String>: The token to refresh
  • secret <String>: The secret key used for signing
  • newOptions <Object> (optional): New options for the refreshed token

Returns: <String> - A new token string with updated options.

Example:

// Refreshing an expired token with new expiration time
const refreshedToken = iToken.refresh(
  oldToken,
  'your-secret-key',
  { 
    expiresIn: '2h',
    audience: 'updated-client-app' 
  }
);

Token Structure

Each iToken token consists of three parts separated by periods:

[header].[payload].[signature]
  1. Header: Contains metadata about the token (base64url encoded JSON)

    • alg: Algorithm used for signing (default: "HS256")
    • typ: Token type identifier (default: "AEG1")
    • Any custom header fields provided in options
  2. Payload: Contains the claims (base64url encoded JSON)

    • Custom claims (user data)
    • Standard claims (iat, exp, nbf, iss, aud, sub)
  3. Signature: HMAC-SHA256 hash of the header and payload using the secret key

Standard Claims

iToken automatically handles these standard claims:

Claim Name Description
iat Issued At Timestamp when the token was created (automatically added)
exp Expiration Time Timestamp when the token expires (added when expiresIn option is used)
nbf Not Before Timestamp before which the token is not valid (added when notBefore option is used)
iss Issuer Identifies who issued the token (added when issuer option is used)
aud Audience Identifies the recipients of the token (added when audience option is used)
sub Subject Identifies the subject of the token (added when subject option is used)

Error Handling

iToken provides specific error types for different validation scenarios:

const {
  iToken, 
  iTokenError,            // Base error type
  iTokenExpiredError,     // Token has expired
  iTokenNotBeforeError,   // Token is not yet valid
  iTokenTamperError,      // Token signature validation failed
  iTokenValidationError,  // Token payload or options validation failed
  iTokenFormatError       // Token format is invalid
} = require('itoken');

try {
  const decoded = iToken.decode(token, secret);
  // Token is valid
} catch (error) {
  if (error instanceof iTokenExpiredError) {
    console.log(`Token expired at: ${new Date(error.expiredAt * 1000)}`);
  } else if (error instanceof iTokenNotBeforeError) {
    console.log(`Token not valid until: ${new Date(error.notBefore * 1000)}`);
  } else if (error instanceof iTokenTamperError) {
    console.log('Token signature verification failed - possible tampering detected');
  } else if (error instanceof iTokenValidationError) {
    console.log('Token validation error:', error.message);
  } else if (error instanceof iTokenFormatError) {
    console.log('Token format error:', error.message);
  } else {
    console.log('Unknown error:', error.message);
  }
}

Working Examples

API Access Token

const { iToken } = require('itoken');

const token = iToken.encode(
  { userId: 456 },
  SECRET,
  {
    issuer: 'api-server',
    audience: 'mobile-app',
    expiresIn: '30m',
    notBefore: '5s' // Prevent immediate use
  }
);

Scheduled Maintenance Token

const { iToken } = require('itoken');

// Valid during maintenance window (next 2 hours)
const maintenanceStart = Math.floor(Date.now() / 1000) + 3600; // 1h from now
const token = iToken.encode(
  { access: 'maintenance' },
  SECRET,
  {
    notBefore: maintenanceStart,
    expiresIn: 7200 // 2h
  }
);

Refresh Token Flow

const { iToken } = require('itoken');

// Create long-lived refresh token
const refreshToken = iToken.encode(
  { userId: 789 },
  SECRET,
  { expiresIn: '7d' }
);

// Refresh when access token expires
function refreshAccess(oldToken) {
  return iToken.refresh(oldToken, SECRET, { expiresIn: '1h' });
}

Basic Authentication

const { iToken } = require('itoken');

// User login function
function login(username, password) {
  // In a real app, verify credentials against a database
  if (username === 'user@example.com' && password === 'correct-password') {
    // Create a user object (excluding sensitive info)
    const user = {
      id: 'user-123',
      email: username,
      name: 'John Doe'
    };
    
    // Create a token that expires in 24 hours
    const token = iToken.encode(
      user, 
      'your-secure-key',
      { expiresIn: '24h' }
    );
    
    return { user, token };
  }
  
  throw new Error('Invalid credentials');
}

// Example usage
try {
  const { user, token } = login('user@example.com', 'correct-password');
  console.log('Login successful:', user);
  console.log('Access token:', token);
} catch (error) {
  console.error('Login failed:', error.message);
}

Role-Based Access Control

const { iToken, iTokenError } = require('itoken');

const SECRET_KEY = 'your-secure-key';

// Authorization function
function checkPermission(token, requiredRole) {
  try {
    const { payload } = iToken.decode(token, SECRET_KEY);
    
    // Check if user has the required role
    if (!payload.roles || !payload.roles.includes(requiredRole)) {
      return { authorized: false, message: 'Insufficient permissions' };
    }
    
    return { authorized: true, user: payload };
  } catch (error) {
    if (error instanceof iTokenError) {
      return { authorized: false, message: error.message };
    }
    return { authorized: false, message: 'Authorization failed' };
  }
}

// Example token with role information
const adminToken = iToken.encode(
  { 
    id: 'user-456', 
    name: 'Admin User', 
    roles: ['user', 'editor', 'admin'] 
  },
  SECRET_KEY,
  { expiresIn: '8h' }
);

const editorToken = iToken.encode(
  { 
    id: 'user-789', 
    name: 'Editor User', 
    roles: ['user', 'editor'] 
  },
  SECRET_KEY,
  { expiresIn: '8h' }
);

// Check permissions
console.log(checkPermission(adminToken, 'admin'));   // { authorized: true, user: {...} }
console.log(checkPermission(editorToken, 'admin'));  // { authorized: false, message: 'Insufficient permissions' }

Token Refresh Mechanism

const { iToken, iTokenExpiredError } = require('itoken');

const SECRET_KEY = 'your-secure-key';

// Function to get a valid token, refreshing if expired
function getValidToken(currentToken) {
  try {
    // Try to decode the current token
    const { payload } = iToken.decode(currentToken, SECRET_KEY);
    return { valid: true, token: currentToken, payload };
  } catch (error) {
    // Check if token is expired
    if (error instanceof iTokenExpiredError) {
      try {
        // Refresh the token with a new expiration time
        const newToken = iToken.refresh(currentTo
A3DB
ken, SECRET_KEY, { expiresIn: '1h' });
        const { payload } = iToken.decode(newToken, SECRET_KEY);
        return { valid: true, token: newToken, payload, refreshed: true };
      } catch (refreshError) {
        return { valid: false, error: 'Could not refresh token: ' + refreshError.message };
      }
    }
    
    return { valid: false, error: error.message };
  }
}

// Example usage
const originalToken = iToken.encode({ userId: '123' }, SECRET_KEY, { expiresIn: '1s' });

// Wait 2 seconds for token to expire
setTimeout(() => {
  const result = getValidToken(originalToken);
  
  if (result.valid) {
    console.log('Token is valid' + (result.refreshed ? ' (refreshed)' : ''));
    console.log('Current token:', result.token);
    console.log('Payload:', result.payload);
  } else {
    console.error('Invalid token:', result.error);
  }
}, 2000);

Express Middleware Integration

const express = require('express');
const { iToken, iTokenError } = require('itoken');

const app = express();
app.use(express.json());

const SECRET_KEY = 'your-secure-key';

// Authentication middleware
function authenticate(req, res, next) {
  // Get authorization header
  const authHeader = req.headers.authorization;
  
  // Check if header exists and has the right format
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ 
      error: 'Authentication required',
      details: 'Missing or invalid authorization header'
    });
  }
  
  // Extract token
  const token = authHeader.split(' ')[1];
  
  try {
    // Verify and decode token
    const { payload } = iToken.decode(token, SECRET_KEY);
    
    // Attach user info to the request object
    req.user = payload;
    
    // Continue to the route handler
    next();
  } catch (error) {
    // Handle specific errors
    if (error instanceof iTokenError) {
      let statusCode = 401;
      let errorType = 'authentication_failed';
      
      if (error.name === 'iTokenExpiredError') {
        errorType = 'token_expired';
      } else if (error.name === 'iTokenNotBeforeError') {
        errorType = 'token_not_active';
      } else if (error.name === 'iTokenTamperError') {
        statusCode = 403;
        errorType = 'token_invalid';
      }
      
      return res.status(statusCode).json({
        error: errorType,
        details: error.message
      });
    }
    
    // Handle unexpected errors
    return res.status(500).json({
      error: 'internal_error',
      details: 'Authentication process failed'
    });
  }
}

// Role-based authorization middleware factory
function authorize(requiredRole) {
  return (req, res, next) => {
    // Check if user exists (authenticate middleware should run first)
    if (!req.user) {
      return res.status(401).json({
        error: 'authentication_required',
        details: 'You must be logged in'
      });
    }
    
    // Check if user has the required role
    if (!req.user.roles || !req.user.roles.includes(requiredRole)) {
      return res.status(403).json({
        error: 'insufficient_permissions',
        details: `Role '${requiredRole}' is required`
      });
    }
    
    // User has the required role, proceed
    next();
  };
}

// Login endpoint
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // Simple user verification (replace with database lookup in real app)
  let user;
  
  if (username === 'admin@example.com' && password === 'admin123') {
    user = { id: '1', username, name: 'Admin User', roles: ['user', 'admin'] };
  } else if (username === 'user@example.com' && password === 'user123') {
    user = { id: '2', username, name: 'Regular User', roles: ['user'] };
  } else {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  
  // Create token
  const token = iToken.encode(
    user,
    SECRET_KEY,
    { 
      expiresIn: '1h',
      issuer: 'api.example.com'
    }
  );
  
  // Return user info and token
  res.json({
    message: 'Login successful',
    user: {
      id: user.id,
      name: user.name,
      roles: user.roles
    },
    token,
    expiresIn: 3600 // seconds
  });
});

// Protected route - requires authentication
app.get('/profile', authenticate, (req, res) => {
  res.json({
    message: 'Profile accessed successfully',
    user: req.user
  });
});

// Admin route - requires authentication and admin role
app.get('/admin/dashboard', authenticate, authorize('admin'), (req, res) => {
  res.json({
    message: 'Admin dashboard accessed successfully',
    adminData: {
      users: 1045,
      newToday: 27,
      activeNow: 165
    }
  });
});

// Public endpoint - no authentication required
app.get('/public', (req, res) => {
  res.json({ message: 'This is public information' });
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Security Best Practices πŸ”

Follow these security recommendations when working with iToken tokens:

  1. Use strong secrets: Minimum 32 random characters
  2. Set reasonable expiration: Typically 15-60 minutes
  3. Validate all claims:
    const { payload } = iToken.decode(token, SECRET);
    if (payload.aud !== 'api.example.com') throw new Error('Invalid audience');
  4. Always use HTTPS for token transmission
  5. Rotate secrets regularly

Comparing iToken vs JWT

Feature iToken Traditional JWT
Token Type Uses "AEG1" type identifier Uses "JWT" type identifier
Error Handling Provides specific error types Generic errors in most libraries
Time Validation Built-in expiration and notBefore handling Varies by library
Signature Verification Timing-safe comparison by default Varies by library
API Streamlined, focused API Often more complex
Refresh Mechanism Built-in token refresh Usually requires custom implementation

TypeScript Support

iToken provides TypeScript definitions for better integration with TypeScript projects:

import { iToken, iTokenError } from 'itoken';

interface UserPayload {
  id: string;
  username: string;
  roles: string[];
}

// Create a token with typed payload
const payload: UserPayload = {
  id: '123',
  username: 'user@example.com',
  roles: ['user', 'editor']
};

const token = iToken.encode(payload, 'secret', { expiresIn: '1h' });

try {
  // Decoded payload will have UserPayload type plus standard claims
  const { payload } = iToken.decode<UserPayload>(token, 'secret');
  
  // TypeScript knows about these properties
  console.lo
6931
g(payload.id);
  console.log(payload.roles);
  console.log(payload.iat); // Standard claim
} catch (error) {
  if (error instanceof iTokenError) {
    console.error('iToken error:', error.message);
  }
}

FAQ ❓

Q: How is this different from JWT?
A: Focuses on identity security with stricter defaults and zero dependencies.

Q: Can I use existing JWT tokens?
A: No - iToken uses AEG1 token format for enhanced security.

Q: Is this production-ready?
A: Yes! Follows Node.js crypto best practices.

Q: How to handle clock skew?
A: Add buffer to expiration times:

iToken.encode(payload, SECRET, { expiresIn: 3600 + 300 }); // 1h + 5m buffer

License: MIT | Author: Ritu Raj Pratap Singh
GitHub: github.com/theriturajps/itoken
Report Issues: Issues

Releases

No releases published

Packages

No packages published
0