') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - pardnio/node-jwt-auth: A JWT authentication package for Node.js providing both Access Token and Refresh Token mechanisms, featuring fingerprint recognition, Redis storage, and automatic refresh functionality · GitHub
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JWT Auth (Node.js)

A JWT authentication package providing both Access Token and Refresh Token mechanisms, featuring fingerprint recognition, Redis storage, and automatic refresh functionality.
version Golang can get here

version

Feature

  • Dual Token System

    • Access Token (short-term) + Refresh Token (long-term)
    • Automatic token refresh without requiring re-login
    • ES256 algorithm (Elliptic Curve Digital Signature)
  • Device Fingerprinting

    • Generates unique fingerprints based on User-Agent, Device ID, OS, and Browser
    • Prevents token misuse across different devices
    • Automatic device type detection (Desktop, Mobile, Tablet)
  • Token Revocation

    • Adds Access Token to blacklist upon logout
    • Redis TTL automatically cleans expired revocation records
    • Prevents reuse of logged-out tokens
  • Version Control Protection

    • Refresh Token version tracking
    • Auto-generates new Refresh ID after 5 refresh attempts
    • Prevents replay attacks
  • Smart Refresh Strategy

    • Auto-regenerates when Refresh Token has less than half lifetime remaining
    • 5-second grace period for old tokens to reduce concurrency issues
    • Minimizes database queries
  • Multiple Authentication Methods

    • Automatic cookie reading
    • Authorization Bearer Header
    • Custom Headers (X-Device-ID, X-Refresh-ID)
  • Flexible Configuration

    • Supports file paths or direct key content
    • Customizable Cookie names
    • Production/Development environment auto-switching

How to use

  • Installation

    npm install @pardnchiu/jwt-auth
  • Initialize

    import{JWTAuth}from'@pardnchiu/jwt-auth';// initialize the JWT instanceawaitJWTAuth.init({privateKeyPath: "./keys/private.pem",publicKeyPath: "./keys/public.pem",// or paste keys directly:// privateKey: "-----BEGIN EC PRIVATE KEY-----...",// publicKey: "-----BEGIN PUBLIC KEY-----...",accessTokenExpires: 900,// secondsrefreshTokenExpires: 604800,// seconds// true: domain=domain, samesite=none, secure=true// false: domain=localhost, samesite=lax, secure=falseisProd: false,domain: "pardn.io",// cookie key, default access_token/refresh_idAccessTokenCookieKey: "access_token",RefreshTokenCookieKey: "refresh_id",// store with redisredis: {host: "localhost",port: 6379,password: "",// optionaldb: 0// optional},checkUserExists: async(userId: string): Promise<boolean>=>{// return true if user exists, false otherwisereturntrue;}});process.on("SIGINT",async()=>{awaitJWTAuth.close();process.exit(0);});
  • CreateJWT

    import{Request,Response}from'express';import{JWTAuth}from'@pardnchiu/jwt-auth';asyncfunctionloginHandler(req: Request,res: Response){// after verifying user login info...constuserData={id: "user123",name: "",email: "john@example.com",thumbnail: "avatar.jpg",role: "user",level: 1,scope: ["read","write"]};try{consttokenResult=awaitJWTAuth.CreateJWT(req,res,userData);// automatically set in cookiesres.json({success: true,token: tokenResult.token,refresh_id: tokenResult.refresh_id});}catch(error){res.status(500).json({error: error.message});}}
  • VerifyJWT

    import{Request,Response}from'express';import{JWTAuth}from'@pardnchiu/jwt-auth';asyncfunctionprotectedHandler(req: Request,res: Response){try{constresult=awaitJWTAuth.VerifyJWT(req,res);if(!result.isAuth){// Authentication failedreturnres.status(result).json({error: result.isError ? "Bad Request" : "Unauthorized"});}// Authentication success, user result.data to get user datares.json({message: "Protected resource accessed",user: result.data});}catch(error){res.status(500).json({error: error.message});}}
  • RevokeJWT

    import{Request,Response}from"express";import{JWTAuth}from"@pardnchiu/jwt-auth";asyncfunctionlogoutHandler(req: Request,res: Response){try{awaitJWTAuth.RevokeJWT(req,res);res.json({message: "Successfully logged out"});}catch(error){res.status(500).json({error: error.message});}}

Configuration

Config

  • privateKeyPath / privateKey: private key file path or content
  • publicKeyPath / publicKey: public key file path or content
  • accessTokenExpires: access token expire time
  • refreshTokenExpires: refresh id expire time
  • isProd: is production or not (affects cookie setting)
  • domain: cookie domain
  • redis: redis connection
    • host: redis host
    • port: redis port
    • password: redis password (optional)
    • db: redis db (optional)
  • checkUserExists: user existence check function
  • AccessTokenCookieKey: access token cookie name (default: 'access_token')
  • RefreshTokenCookieKey: refresh id cookie name (default: 'refresh_id')

Supported methods

  1. Cookie: Automatically reads token from cookie
  2. Authorization Header: Authorization: Bearer <token>
  3. Custom Headers:
    • X-Device-ID: Device ID
    • X-Refresh-ID: Custom Refresh ID

Token refresh

The system automatically generates a new Refresh ID in the following cases:

  • Refresh version exceeds 5 times
  • Remaining Refresh Token time is less than half

The new tokens are returned via:

  • HTTP Header: X-New-Access-Token
  • HTTP Header: X-New-Refresh-ID
  • Cookie auto-update

Security features

  • Fingerprint recognition: Generates a unique fingerprint based on User-Agent, Device-ID, OS, Browser, and Device type
  • Token revocation: Adds token to a blacklist on logout
  • Automatic expiration: Supports TTL to automatically clean up expired tokens
  • Version control: Tracks Refresh Token versions to prevent replay attacks
  • Fingerprint validation: Ensures tokens are used from the same device/browser

Error handling

The VerifyJWT method returns:

  • AuthData object on successful authentication
  • HTTP status code number on failure:
    • 401: Unauthorized (invalid/expired tokens, user doesn't exist)
    • 400: Bad Request (invalid fingerprint, malformed tokens)

Common error scenarios:

  • Token revoked
  • Fingerprint mismatch
  • Refresh data not found
  • JWT expired or invalid
  • User not found

License

This source code project is licensed under the MIT license.

Creator

邱敬幃 Pardn Chiu


©️ 2025 邱敬幃 Pardn Chiu

About

A JWT authentication package for Node.js providing both Access Token and Refresh Token mechanisms, featuring fingerprint recognition, Redis storage, and automatic refresh functionality

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages