@codeimplants/authkit
v2.1.0
Published
Multi-platform authentication module for Node.js with web and mobile support. Includes OTP, JWT, email/password authentication, and role-based authorization.
Maintainers
Readme
🔐 Auth Package
A comprehensive, production-ready authentication package for Node.js applications with full TypeScript support, web and mobile platform support, and multiple authentication methods including OTP, JWT, and role-based authorization.
✨ Features
- 🔑 JWT Authentication - Access and refresh token management
- 📧 Email/Password Authentication - Traditional email and password login with bcrypt encryption
- 📱 Phone OTP - SMS-based one-time password verification
- 📧 Email OTP - Email-based verification (ready for integration)
- 🔒 Role-based Authorization - Flexible permission system
- 🌐 Multi-Platform Support - Automatic detection and handling of web (cookies) and mobile (JSON) clients
- 🔄 Hybrid App Support - Seamless authentication for React + Capacitor, Ionic, and other hybrid frameworks
- 🍪 Cookie-based Tokens - Secure token storage for web applications
- 📱 JSON Response Mode - Token delivery in JSON for mobile applications (React Native, Flutter, Ionic, etc.)
- 🔄 Automatic Token Refresh - Seamless session management for both web and mobile
- ⚡ Express Middleware - Easy integration with Express apps
- 📝 Input Validation - Built-in request validation
- 🛡️ Security Features - Rate limiting, secure cookies, environment-based configs
- 📘 Full TypeScript Support - Complete type definitions and IntelliSense
🚀 Quick Start
Installation
npm install @codeimplants/authkitBasic Usage (JavaScript)
import express from "express";
import mongoose from "mongoose";
import { AuthManager } from "@codeimplants/authkit";
// Define your User model
const userSchema = new mongoose.Schema({
name: String,
phoneNumber: { type: String, unique: true },
userRole: { type: String, default: "user" },
// ... other fields
});
const User = mongoose.model("User", userSchema);
// Initialize Auth Manager
const authManager = new AuthManager({
User,
jwtAccessSecret: process.env.JWT_ACCESS_SECRET,
jwtRefreshSecret: process.env.JWT_REFRESH_SECRET,
otpUrl: process.env.OTP_URL,
otpApiKey: process.env.OTP_API_KEY,
otptemplate: process.env.OTP_TEMPLATE,
security: {
responseMode: "auto", // 'auto', 'cookie', or 'json'
},
});
// Set up routes
app.post(
"/send-register-otp",
authManager.validators.validateSendOtp,
authManager.controllers.otp.sendRegisterOTP,
);
app.post(
"/send-login-otp",
authManager.validators.validateSendOtp,
authManager.controllers.otp.sendLoginOTP,
);
app.post(
"/verify-otp",
authManager.validators.validateVerifyOtp,
authManager.controllers.otp.verifyOtp,
);
// Email OTP routes
app.post("/send-email-otp", authManager.controllers.emailOtp.sendEmailOtp);
app.post("/verify-email-otp", authManager.controllers.emailOtp.verifyEmailOtp);
app.post("/refresh-token", authManager.controllers.auth.refreshAccessToken);
app.post("/logout", authManager.controllers.auth.logoutUser);
// Protected route
app.get("/profile", authManager.middleware.protect, (req, res) => {
res.json({ user: req.user });
});🌐 Web vs Mobile Support
Automatic Detection (Recommended)
The package automatically detects whether the client is a web browser or mobile app and responds accordingly:
const authManager = new AuthManager({
User,
jwtAccessSecret: process.env.JWT_ACCESS_SECRET,
jwtRefreshSecret: process.env.JWT_REFRESH_SECRET,
security: {
responseMode: "auto", // Automatically detects client type
},
});Detection Priority (in order):
X-Client-Type: web→ Always uses cookie-based authentication (HTTP-only cookies)X-Client-Type: mobile→ Always uses JSON-based authentication (tokens in response)- User-Agent fallback → Checks for mobile indicators: Mobile, Android, iPhone, iPad, React Native, Ionic, Flutter
⚠️ Important for Hybrid Apps (React + Capacitor, Ionic, etc.):
Always send theX-Client-Typeheader to ensure correct authentication mode. The User-Agent alone cannot reliably distinguish between web and mobile versions of hybrid apps.
Manual Configuration
You can also manually set the response mode:
// Force cookie mode (web only)
security: {
responseMode: "cookie";
}
// Force JSON mode (mobile only)
security: {
responseMode: "json";
}🔄 Hybrid App Support (React + Capacitor, Ionic, etc.)
The Challenge
Hybrid apps (React + Capacitor, Ionic, etc.) can run in two different environments:
- Web: Running in a browser → Needs cookie-based authentication
- Mobile: Running as a native app on iOS/Android → Needs JSON token authentication
Using the wrong authentication mode can lead to security vulnerabilities!
The Solution
Always send the X-Client-Type header based on the runtime platform:
import { Capacitor } from "@capacitor/core";
import axios from "axios";
// Detect if running as native mobile app
const isNativePlatform = () => {
return Capacitor.isNativePlatform(); // true for iOS/Android, false for web
};
// Get the appropriate client type
const getClientType = () => {
return isNativePlatform() ? "mobile" : "web";
};
// Create axios instance with dynamic configuration
const api = axios.create({
baseURL: "https://api.example.com",
withCredentials: !isNativePlatform(), // Only for web (cookies)
});
// Add interceptor to set X-Client-Type header
api.interceptors.request.use((config) => {
config.headers["X-Client-Type"] = getClientType();
// For mobile: Add access token from secure storage
if (isNativePlatform()) {
const accessToken = await SecureStore.getItemAsync("accessToken");
if (accessToken) {
config.headers["Authorization"] = `Bearer ${accessToken}`;
}
}
// For web: Cookies are sent automatically
return config;
});
export default api;Login Flow for Hybrid Apps
import api from "./api";
import * as SecureStore from "expo-secure-store";
const login = async (phoneNumber: string, otp: string) => {
const response = await api.post("/auth/verify-otp", {
phoneNumber,
otp,
});
if (isNativePlatform()) {
// Mobile: Store tokens in secure storage
const { accessToken, refreshToken } = response.data;
await SecureStore.setItemAsync("accessToken", accessToken);
await SecureStore.setItemAsync("refreshToken", refreshToken);
}
// Web: Tokens are automatically stored in HTTP-only cookies
return response.data;
};Why This Matters
| Scenario | Without X-Client-Type | With X-Client-Type | | ---------------- | -------------------------------------------- | ------------------------------------- | | Capacitor Web | ❌ May get JSON tokens (insecure in browser) | ✅ Gets HTTP-only cookies (secure) | | Capacitor Mobile | ⚠️ May get cookies (won't work) | ✅ Gets JSON tokens (works correctly) | | Pure Web | ✅ Gets cookies | ✅ Gets cookies | | Pure Mobile | ✅ Gets JSON tokens | ✅ Gets JSON tokens |
📱 Mobile Client Integration
React Native Example
// Login request
const login = async (email, password) => {
const response = await fetch("https://api.example.com/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Client-Type": "mobile", // Ensures JSON response
},
body: JSON.stringify({ email, password }),
});
const data = await response.json();
// Store tokens in secure storage
await SecureStore.setItemAsync("accessToken", data.accessToken);
await SecureStore.setItemAsync("refreshToken", data.refreshToken);
return data;
};
// Protected API request
const fetchProfile = async () => {
const accessToken = await SecureStore.getItemAsync("accessToken");
const response = await fetch("https://api.example.com/profile", {
headers: {
Authorization: `Bearer ${accessToken}`,
"X-Client-Type": "mobile",
},
});
// Check for token refresh
const newAccessToken = response.headers.get("X-New-Access-Token");
const newRefreshToken = response.headers.get("X-New-Refresh-Token");
if (newAccessToken) {
await SecureStore.setItemAsync("accessToken", newAccessToken);
await SecureStore.setItemAsync("refreshToken", newRefreshToken);
}
return response.json();
};
// Refresh token
const refreshToken = async () => {
const refreshToken = await SecureStore.getItemAsync("refreshToken");
const response = await fetch("https://api.example.com/auth/refresh-token", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Client-Type": "mobile",
"X-Refresh-Token": refreshToken,
},
});
const data = await response.json();
await SecureStore.setItemAsync("accessToken", data.accessToken);
await SecureStore.setItemAsync("refreshToken", data.refreshToken);
return data;
};Flutter Example
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class AuthService {
final storage = FlutterSecureStorage();
final String baseUrl = 'https://api.example.com';
Future<Map<String, dynamic>> login(String email, String password) async {
final response = await http.post(
Uri.parse('$baseUrl/auth/login'),
headers: {
'Content-Type': 'application/json',
'X-Client-Type': 'mobile',
},
body: jsonEncode({'email': email, 'password': password}),
);
final data = jsonDecode(response.body);
// Store tokens securely
await storage.write(key: 'accessToken', value: data['accessToken']);
await storage.write(key: 'refreshToken', value: data['refreshToken']);
return data;
}
Future<Map<String, dynamic>> fetchProfile() async {
final accessToken = await storage.read(key: 'accessToken');
final response = await http.get(
Uri.parse('$baseUrl/profile'),
headers: {
'Authorization': 'Bearer $accessToken',
'X-Client-Type': 'mobile',
},
);
// Check for token refresh
final newAccessToken = response.headers['x-new-access-token'];
final newRefreshToken = response.headers['x-new-refresh-token'];
if (newAccessToken != null) {
await storage.write(key: 'accessToken', value: newAccessToken);
await storage.write(key: 'refreshToken', value: newRefreshToken);
}
return jsonDecode(response.body);
}
}🌐 Web Client Integration
For web clients, tokens are automatically stored in HTTP-only cookies:
// Login request (browser)
const login = async (email, password) => {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include", // Important: Include cookies
body: JSON.stringify({ email, password }),
});
const data = await response.json();
// Tokens are automatically stored in cookies
return data;
};
// Protected API request (browser)
const fetchProfile = async () => {
const response = await fetch("/api/profile", {
credentials: "include", // Important: Include cookies
});
return response.json();
};📋 Response Formats
Web Response (Cookie Mode)
Login/Register Response:
{
"_id": "user_id",
"isVerified": true,
"message": "Authentication successful"
}Tokens are set in HTTP-only cookies:
jwt- Access tokenrefreshToken- Refresh token
Mobile Response (JSON Mode)
Login/Register Response:
{
"_id": "user_id",
"isVerified": true,
"message": "Authentication successful",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 900
}📋 Configuration Options
Required Configuration
{
User: mongoose.Model, // Your Mongoose User model
jwtAccessSecret: string, // Secret for access tokens
jwtRefreshSecret: string, // Secret for refresh tokens
}Optional Configuration
{
// OTP Configuration
otp: {
ttl: 5, // OTP time-to-live in minutes
length: 6, // OTP length
retryLimit: 3, // Maximum retry attempts
retryDelay: 60, // Delay between retries (seconds)
},
// JWT Configuration
jwt: {
accessExpiresIn: '15m', // Access token expiration
refreshExpiresIn: '7d', // Refresh token expiration
},
// Security Configuration
security: {
nodeEnv: 'development', // Environment
cookieSecure: false, // Secure cookies (auto-set based on env)
cookieSameSite: 'Lax', // SameSite cookie attribute
responseMode: 'auto', // 'auto', 'cookie', or 'json'
},
// OTP Service Configuration
otpUrl: string, // OTP service URL
otpApiKey: string, // OTP service API key
otptemplate: string, // OTP template name
// Email Configuration (for Email OTP)
email: {
provider: 'nodemailer', // Email provider: 'nodemailer', 'sendgrid', 'aws-ses'
fromEmail: '[email protected]',
nodemailer: {
host: 'smtp.gmail.com', // SMTP host
port: 587, // SMTP port
secure: false, // true for 465, false for other ports
auth: {
user: '[email protected]',
pass: 'your-app-password'
},
service: 'gmail' // Optional: service name
}
}
}🔧 API Reference
Controllers
Email/Password Controller
registerUser(req: Request, res: Response)- Register new user with email and passwordloginUser(req: Request, res: Response)- Login user with email and passwordchangePassword(req: Request, res: Response)- Change password (authenticated)resetPassword(req: Request, res: Response)- Reset password (unauthenticated)
OTP Controller
sendOtp(req: Request, res: Response)- Send OTP to phone numberverifyOtp(req: Request, res: Response)- Verify OTP and authenticate user
Email OTP Controller
sendEmailOtp(req: Request, res: Response)- Send OTP to emailverifyEmailOtp(req: Request, res: Response)- Verify email OTP and authenticate user
Auth Controller
refreshAccessToken(req: Request, res: Response)- Refresh access token using refresh tokenlogoutUser(req: Request, res: Response)- Logout user and clear tokens
Middleware
Protect Middleware
app.get("/protected", authManager.middleware.protect, (req: any, res) => {
// req.user contains authenticated user with full type safety
const user = req.user as IUser;
res.json({ user });
});Token Sources (in order of priority):
- Authorization header:
Bearer <token> - Cookie:
jwt - Custom header:
X-Refresh-Token(for refresh tokens)
Authorization Middleware
app.get(
"/admin",
authManager.middleware.protect,
authManager.middleware.authorize("admin"),
(req: any, res) => {
const user = req.user as IUser;
res.json({ message: "Admin access granted", user });
},
);📱 Authentication Flow
Phone OTP Flow
Send OTP
POST /api/auth/send-otp Content-Type: application/json X-Client-Type: mobile { "phoneNumber": "+1234567890" }Verify OTP
POST /api/auth/verify-otp Content-Type: application/json X-Client-Type: mobile { "phoneNumber": "+1234567890", "otp": "123456" }Mobile Response:
{ "_id": "user_id", "isVerified": true, "message": "Authentication successful", "accessToken": "...", "refreshToken": "...", "tokenType": "Bearer", "expiresIn": 900 }Access Protected Resources
GET /api/profile Authorization: Bearer <access-token> X-Client-Type: mobile
Token Refresh Flow
Web (Cookie):
POST /api/auth/refresh-token
Cookie: refreshToken=<refresh-token>Mobile (JSON):
POST /api/auth/refresh-token
Content-Type: application/json
X-Client-Type: mobile
X-Refresh-Token: <refresh-token>
OR
{
"refreshToken": "<refresh-token>"
}🛡️ Security Features
- HTTP-Only Cookies: Tokens stored in secure HTTP-only cookies (web)
- Secure Storage: Tokens delivered in JSON for secure storage (mobile)
- Automatic Token Refresh: Seamless token renewal in middleware for both platforms
- Environment-Based Security: Different security settings for dev/prod
- Input Validation: Built-in request validation using express-validator
- Rate Limiting: Built-in rate limiting for OTP and password endpoints
- Account Lockout: Automatic account lockout after failed attempts
- Multi-Platform Support: Automatic detection and handling of web/mobile clients
🔮 Migration Guide
Updating from Cookie-Only Version
If you're upgrading from a version that only supported cookies, no changes are required! The package is backward compatible:
- Default behavior: Set
responseMode: 'auto'to automatically detect client type - Web-only apps: Keep using
responseMode: 'cookie'or omit the setting - Mobile apps: Add
X-Client-Type: mobileheader in requests or setresponseMode: 'json'
Mobile App Checklist
- [ ] Add
X-Client-Type: mobileheader to all requests - [ ] Use
Authorization: Bearer <token>header for authenticated requests - [ ] Store tokens in secure storage (SecureStore, Keychain, etc.)
- [ ] Handle token refresh by checking response headers
- [ ] Implement token refresh logic when access token expires
Hybrid App Checklist (React + Capacitor, Ionic, etc.)
- [ ] Use
Capacitor.isNativePlatform()to detect runtime environment - [ ] Send
X-Client-Type: webwhen running in browser - [ ] Send
X-Client-Type: mobilewhen running as native app - [ ] Use
withCredentials: truefor web,falsefor mobile - [ ] Store tokens in secure storage for mobile, rely on cookies for web
- [ ] Add request interceptor to automatically set correct headers
- [ ] Test authentication in both web and mobile environments
📝 Environment Variables
Create a .env file:
# JWT Secrets
JWT_ACCESS_SECRET=your-super-secret-access-key
JWT_REFRESH_SECRET=your-super-secret-refresh-key
# OTP Service (for production)
OTP_URL=https://your-otp-service.com/api
OTP_API_KEY=your-otp-api-key
OTP_TEMPLATE=your-otp-template
# App Configuration
NODE_ENV=development
PORT=5000
FRONTEND_URL=http://localhost:3000
# Email Configuration (for Email OTP with Nodemailer)
[email protected]
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
[email protected]
SMTP_PASS=your-app-password
SMTP_SERVICE=gmail🔮 Roadmap
- [x] Multi-platform support (Web & Mobile)
- [x] Automatic client detection
- [x] JSON response mode for mobile
- [x] Hybrid app support with X-Client-Type header
- [ ] OAuth providers (Google, GitHub, Facebook)
- [ ] Two-factor authentication (2FA)
- [ ] Session management
- [ ] Audit logging
- [ ] Enhanced TypeScript types for better IntelliSense
📚 Quick Reference
Platform Configuration Summary
| Platform | X-Client-Type Header | withCredentials | Token Storage | Authorization Header |
| --------------------------------------- | -------------------- | --------------- | ----------------- | --------------------------------------- |
| Pure Web (React, Vue, Angular) | web or omit | true | HTTP-only cookies | Not needed (cookies sent automatically) |
| Pure Mobile (React Native, Flutter) | mobile | false | Secure storage | Bearer <accessToken> |
| Capacitor/Ionic Web | web | true | HTTP-only cookies | Not needed (cookies sent automatically) |
| Capacitor/Ionic Mobile | mobile | false | Secure storage | Bearer <accessToken> |
Response Format by Client Type
| Client Type | Access Token Location | Refresh Token Location | Additional Data in Response |
| ------------------------------------ | ------------------------ | --------------------------------- | -------------------------------------------------------- |
| Web (X-Client-Type: web) | jwt cookie (HTTP-only) | refreshToken cookie (HTTP-only) | _id, isVerified, message |
| Mobile (X-Client-Type: mobile) | accessToken in JSON | refreshToken in JSON | _id, isVerified, message, tokenType, expiresIn |
Common Integration Patterns
Web App (React/Vue/Angular)
// Set once in your API client
axios.defaults.withCredentials = true;
// No need to set X-Client-Type (defaults to web)
// Tokens are automatically managed via cookiesMobile App (React Native/Flutter)
// Always send X-Client-Type header
headers: { 'X-Client-Type': 'mobile' }
// Store tokens in secure storage
// Send access token in Authorization headerHybrid App (Capacitor/Ionic)
// Detect platform and configure accordingly
const isNative = Capacitor.isNativePlatform();
api.defaults.headers["X-Client-Type"] = isNative ? "mobile" : "web";
api.defaults.withCredentials = !isNative;📄 License
MIT
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
