Introduction
If you've ever built business web applications, you've probably hit these friction points:
- Passing large amounts of data between pages — do you really need to round-trip through the server?
- Server-side sessions are heavy — resource consumption, session sharing across scaled-out nodes, timeout management…
- Building an auth system from scratch is painful — LDAP integration, JWT issuance and validation, deciding where to store tokens…
This article presents three design approaches that tackle these problems together:
- Scoped browser storage — pass data between pages safely, organized by URL scope
- JS-variable session management — client-side session that auto-syncs with the server
- Transparent JWT authentication — LDAP + JWT with zero authentication code in business logic
These three are tightly coupled. Combined, they produce a state where no auth or data-management code leaks into your business layer.
1. Scoped Browser Storage — Let Directory Structure Be Your Scope
The Problem: Page-to-Page Data Passing Gets Messy
Without a single-page app, every page transition requires a data handoff. Common approaches and their drawbacks:
| Approach | Problem |
|---|---|
| URL parameters | Explodes in length with many fields. Timestamps and binary data are awkward. |
| Hidden fields | Embedded in HTML, hard to manage. Accidentally receiving stale data is easy. Detail lists are especially painful. |
| Cookies | Size-limited. Sent to the server on every request. |
Raw sessionStorage
|
Keys collide easily. No way to tell which page's data is which. |
The Solution: URL-Scoped Storage
Rather than using sessionStorage directly, SIcore wraps it with automatic scope detection derived from the URL path.
| Scope | Purpose | Example |
|---|---|---|
| Page scope | Data valid only within the current page | Temporary search conditions |
| Module scope | Shared across pages in the same directory | Passing header data to a detail-edit page |
| System scope | Shared across the entire application | User preferences |
/app/exmodule/head-edit.html → module key: "/app/exmodule/"
/app/exmodule/detail-edit.html → module key: "/app/exmodule/" ← same!
Pages in the same directory automatically share data. There's no naming convention to memorize — your directory layout is your scope design.
How It Works in SIcore
SIcore provides the StorageUtil class for this.
Header edit page (head-edit.js):
// Save the whole form to storage, then navigate to the next page
const headObj = PageUtil.getValues();
StorageUtil.setModuleObj('headData', headObj);
HttpUtil.movePage('detail-edit.html');
Detail edit page (detail-edit.js):
// Retrieve from storage and populate the form
const headObj = StorageUtil.getModuleObj('headData');
PageUtil.setValues(headObj);
The caller doesn't need to know which page stored the data. As long as both pages live in the same module directory, data is naturally shared. The actual storage key becomes something like _module@/app/exmodule/headData — the scope and URL path are injected automatically, so different modules never collide.
Benefits
- No key collisions — scopes are automatically isolated
- Directory design = data design — split by feature directory and scopes fall into place
- No URL parameters needed — works cleanly regardless of field count
2. JS-Variable Session Management — Auto-Sync With the Server
Session data is held in JavaScript variables, not in localStorage or sessionStorage. A page reload clears it — and that turns out to be a security feature, not just a limitation.
The Problem: Server-Side Sessions Are Heavy
Traditional server-side sessions (HttpSession, etc.) come with known costs:
- Consume server memory (OutOfMemoryError is a real concern at scale)
- Require session sharing mechanisms when scaling horizontally
- Need timeout management and cleanup logic
The Solution: JS Variables + Automatic Sync
SIcore stores session data in a JS variable and automatically sends and receives it under the reserved _session key on every server call.
// Inside the framework's server-call logic (simplified)
// Before sending the request: attach session data automatically
req['_session'] = sessionData;
// After receiving the response: update session data automatically
sessionData = res['_session'];
On the server side, business code simply reads from and writes to the _session key in the JSON. The server holds no session state — it's entirely client-driven.
When to Use Storage vs. JS-Variable Session
| Browser Storage | JS-Variable Session | |
|---|---|---|
| Where stored | sessionStorage |
JS variable |
| Server sync | None | Auto-synced on every call |
| Survives reload | Yes | No — cleared on reload |
| Use for | Page-to-page data handoff | Data shared with the server |
If you want to carry data between pages, use storage. If you want data shared with the server, use session. The distinction is clear.
Benefits
- Stateless server — no session state on the server side; easy horizontal scaling
- Clean business code — the framework handles sync; business code never thinks about it
- Clears on reload — JS variables vanish on page refresh; nothing lingers in storage, which is a security advantage
3. Transparent JWT Authentication — Business Code Doesn't Know Auth Exists
Building auth from scratch — LDAP integration, JWT issuance and validation, choosing where to store tokens — is genuinely tedious. SIcore isolates authentication into three layers, hiding it completely from business code.
| Layer | Responsibility | Business code involvement |
|---|---|---|
| Sign-in | LDAP auth → JWT issuance → store in JS variable | Sign-in page only |
| Token management | Hold token in JS variable; attach automatically to every request | None |
| Server validation | Validate JWT on every request; return 401 on failure | None |
Business pages call the server with zero authentication code.
Full Flow
At sign-in:
[Browser] User enters ID and password on the sign-in page
↓
[Server] Authenticate via LDAP → issue JWT on success
↓ Response JSON: { _session: { token: "hoge..." } }
[Browser] Receive _session.token
↓ Store token in JS variable
[Browser] Navigate to the business page
All subsequent business operations (fully automatic):
[Browser] Business page calls callJsonService() — no auth code
↓ Framework automatically adds: Authorization: Bearer hoge...
[Server] Validate JWT → success → run business logic → return response
Why Store the Token in a JS Variable?
localStorage and sessionStorage persist the token across page reloads, eliminating the need to re-authenticate. But both are readable by scripts via XSS attacks.
Storing in a JS variable means the user re-authenticates on every reload — but it also eliminates the token theft vector that persisted storage introduces.
In typical business app usage, page reloads are rare during normal operation. And in SI project environments, "closing the browser signs you out" is often the expected behavior — many clients prefer it that way.
Server-Side JWT Validation (Java)
// AbstractHttpHandler.java (excerpt)
private boolean validateJwt(final HttpExchange exchange) {
final String authHeader = exchange.getRequestHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return false;
}
JwtUtil.validateToken(authHeader.substring(7));
return true;
}
Sign-in service (Java) — LDAP auth → JWT issuance:
// SigninService.java (excerpt)
try {
(new InitialDirContext(param)).close(); // LDAP authentication
} catch (final NamingException e) {
io.session().put("token", ValUtil.BLANK);
io.putMsg(MsgType.ERROR, "es001");
return;
}
final String token = JwtUtil.createToken(id);
io.session().put("token", token);
Automatic token attachment (JavaScript) — business code is not involved:
// Inside the framework's server-call logic (simplified)
const token = SessionUtil._token;
if (!ValUtil.isBlank(token)) {
header['Authorization'] = 'Bearer ' + token;
}
Benefits
- Clean business code — token management, header injection, and 401 handling are all transparent
- XSS resistance — no persisted token in storage means no storage-based token theft
- Stateless — JWT-based; no server-side session required
Conclusion
Here's a summary of the three approaches:
| Approach | Problem | Solution | Benefit |
|---|---|---|---|
| Scoped storage | Messy page-to-page data passing | Auto-detect scope from URL path | No key collisions; directory = scope |
| JS-variable session | Heavy server-side sessions | Client-side JS variable + auto-sync | Stateless server; easy scaling |
| Transparent JWT auth | Auth boilerplate is painful | Layer separation; hidden from business code | Clean code; XSS resistance |
All three share the same philosophy: keep business code free from infrastructure concerns. Data management, session sync, and authentication are all handled by the framework layer — so you can stay focused on the actual business logic.
Related Articles
Check out the other articles in this series!
- 001 Why I Built a Framework for SI Projects
- 002 Direct URL-to-Class Mapping
- 003 JSON-Only Communication
- 004 Mockup = Implementation
- 005 Dynamic List Rendering
- 006 Custom HTML Attributes
- 007 Map-Based Design
- 008 Single-File CSS Design
- 009 Client-Side Data Management and JWT Auth (this article)
SIcore Framework Links
All implementation code and documentation are available here:
- GitHub: https://github.com/sugaiketadao/sicore
- How to verify sample screens (VS Code): https://github.com/sugaiketadao/sicore#%EF%B8%8F-how-to-verify-sample-screens---vs-code
- Getting started with AI development: https://github.com/sugaiketadao/sicore#-getting-started-with-ai-development
Thank you for reading!
❤ Reactions are appreciated!
Top comments (0)