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.
Install the package using npm:
npm install itoken
Or using yarn:
yarn add itoken
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);
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
- expiresIn
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'
}
);
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
- skipTimeValidation
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}`);
}
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
- ignoreExpiration
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)');
}
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'
}
);
Each iToken token consists of three parts separated by periods:
[header].[payload].[signature]
-
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
-
Payload: Contains the claims (base64url encoded JSON)
- Custom claims (user data)
- Standard claims (iat, exp, nbf, iss, aud, sub)
-
Signature: HMAC-SHA256 hash of the header and payload using the secret key
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) |
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);
}
}
const { iToken } = require('itoken');
const token = iToken.encode(
{ userId: 456 },
SECRET,
{
issuer: 'api-server',
audience: 'mobile-app',
expiresIn: '30m',
notBefore: '5s' // Prevent immediate use
}
);
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
}
);
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' });
}
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);
}
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' }
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);
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}`);
});
Follow these security recommendations when working with iToken tokens:
- Use strong secrets: Minimum 32 random characters
- Set reasonable expiration: Typically 15-60 minutes
- Validate all claims:
const { payload } = iToken.decode(token, SECRET); if (payload.aud !== 'api.example.com') throw new Error('Invalid audience');
- Always use HTTPS for token transmission
- Rotate secrets regularly
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 |
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);
}
}
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