Mark Ku's Blog

Problems Encountered When Implementing JWT Authentication

Understanding JWT Tokens

JWT was originally designed to enable secure data exchange across organizational boundaries. Because JWT alone cannot guarantee full security, JWE was introduced as an extension. However, the JWE spec defines many attributes and naming conventions that most projects don't need — I think it's fine to borrow the spirit of the approach without following the spec to the letter.

Goals

  1. Mobile app can share the same authentication flow and API as the web, without maintaining two separate systems.
  2. Stateless APIs make it easier to scale servers horizontally — each additional server contributes proportionally to overall throughput.
  3. Resolves Same-Site Cookie issues (only relevant on the product side, not the platform side).

Risks

  1. Security is fully controlled by the developer.

Existing System Dependencies on Session

  1. Login state
  2. Failed login attempt count
  3. IP blocking
  4. CAPTCHA
  1. Sticky cookie
  2. Data cookie
  3. SignalR uses .NET Forms Authentication (cookie-based) — switch to passing the JWT token on connect instead

JWT Validation Mechanism

To prevent token forgery, the token is written to Redis upon login. Every request must be validated against Redis.

Where to Store the JWT Token

ApproachIssues
LocalStorageXSS vulnerability; doesn't work in mobile private/incognito mode; not supported on older mobile browsers
LocalStorage → SessionStorageLocalStorage has compatibility issues
redux-state-syncRequires EA tech review process — deferred for now
In-Memory (chosen)Cross-window state sync is more complex to implement, but has the best compatibility

Note: Both cookies and LocalStorage create physical files on the client side. Modern browsers encrypt these files, but they can potentially be cracked. SessionStorage is more secure than LocalStorage. Keeping the token in memory (no persistence to disk) is the safest option.

JWT Token Length

The existing online user count is calculated from the SessionId column in the login table, which currently has a maximum length of 100 characters. Standard JWTs are typically longer than 100 characters, so database columns need to be adjusted accordingly.

Cross-Window Race Condition on Token Refresh

The desktop web app has a "window.open" feature for viewing account history. From Angular's perspective, this spawns an independent app instance. Both the parent window and the child window can trigger token refresh simultaneously, causing a race condition.

My approach: child windows never refresh the token themselves. Instead, they use postMessage to notify the parent window to refresh. Subsequent requests are queued in the child window until the parent completes the refresh and signals back to unlock the queue. If the wait exceeds a timeout threshold, the queue is released via RxJS timeout.

Forced Logout (Kick) Logic

The JWT spec doesn't define forced logout behavior — all kick logic must be implemented manually:

  1. Idle timeout (no trades placed)
  2. Token expiry
  3. Double login (duplicate session)
  4. Manual logout
  5. Session not properly closed
  6. Maintenance kick (from the BO site)

The kick mechanism works by clearing the token in Redis, effectively invalidating it. Log the kick reason for easier debugging later.

Making JWT More Secure

  1. The site must use HTTPS.
  2. Shorten the access token lifetime.
  3. Bind the access token to a browser fingerprint (Device ID): https://fingerprintjs.com/?fbclid=IwAR1I0nRPyiJJUHosyXa80GwJi45-4tHjxV5uZPnTBks-XT3Kp3-tyi1ln1M
  4. Disable DevTools access (F12) — leave a back door for yourself.
  5. Log member login history.
  6. Bind to IP — though clients may switch networks and mobile IPs can change frequently, this could still be a valid policy as long as the business team accepts that IP changes require re-login. Show the logout reason when it happens.

Coexisting Session/Cookie Fallback

After going live, if a critical issue arises that takes a long time to fix, it's helpful to be able to quickly roll back to the old mechanism. A coexistence mode is retained where a config flag determines whether Autofac injects JWT or Session logic:

   if (IsJWTAuthentication)
   {
        builder.RegisterType<JWTSessionData>().As<ISessionData>();
        builder.RegisterType<JWTAuthenticationBLL>().As<IAuthenticationBLL>();
   }
   else
   {
        builder.RegisterType<SessionData>().As<ISessionData>();
        builder.RegisterType<AuthenticationBLL>().As<IAuthenticationBLL>();
   }

Rough Scope of Work

  1. Eliminate Session dependencies
  2. Eliminate Cookie dependencies
  3. Implement JWT token issuance
  4. Implement JWT login/authentication
  5. Implement forced logout (kick) functionality
  6. Implement token refresh
  7. Swap out Session/Cookie mechanism in API layer

Addendum

Is HTTPS Always Secure?

Not necessarily. The following scenarios can make HTTPS insecure:

  1. Using outdated encryption algorithms
  2. Bugs in the cryptographic library implementation
  3. The Certificate Authority (CA) is compromised
  4. Private key leakage
  5. Government-level interception (e.g., FBI)

Side Effects of Binding JWT to IP

Client IPs can change when users switch networks, and mobile IPs are particularly volatile. Binding a token to an IP may not be appropriate for all use cases. That said, it can be a valid policy — as long as the business team agrees that any IP change triggers a forced logout with a reason shown to the user.

Additional Findings

  1. Redux DevTools was not disabled in the production build.

Author

Mark Ku

擁有 10+ 年經驗的資深軟體工程師,現為 AI 應用 Builder,專注於大型平台架構與簡化複雜系統設計,從電商系統到訂閱與收費平台,結合 AI Agent、AI 整合與自動化開發,打造高效率且可持續演進的產品技術基礎。Read More

Found this useful?

The author's free tools, daily podcasts and newsletter are all here.

Mark Ku · This article is licensed under CC BY 4.0. Credit the author and link back to the original when reusing it.

Comments

Subscribe to Newsletter

Subscribe to get new posts delivered instantly — never miss a tech share.

By submitting, you agree to receive emails. You can anytime.

Popular Posts

View all
Mark Ku
··602

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution
Mark Ku
··490

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.
Mark Ku
··333

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki
Mark Ku
··264

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning
Mark Ku
··221

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1
Mark Ku
··215

Setting Up Samba on Ubuntu to Share Folders with Windows 11

Setting Up Samba on Ubuntu to Share Folders with Windows 11
實作JWT驗證所遇到的問題 - Mark Ku's Tech Notes