Moradok Lanna
Developer Notes

Case study · PlatformSecurity

One account across the site – sign-in with IDUnify

One IDUnify account for the site, the blog and the sister site; a token exchanged once for our own session, display vs session cookies, and the "signed in here, not there" bug.

Published 2026-10-17 · 5 min read

Moradok Lanna has three places people meet: the main site (dictionary, tools, boards, the AI teacher), a blog on its own subdomain, and a sister site. The goal was one account for all of them, without the project ever storing or resetting a password, and without making anyone sign in to learn. This page explains how it fits together, and the bug that taught us the most about it.

The goal: optional, single, boring

  • Almost nothing needs an account. The dictionary, the calendar, the games, the typing tools and the boards all work for visitors. Signing in only adds things: saved words and conversations, AI credit, and your boards on every device.
  • One account, many places. The same account signs you in to the main site and the sister site, and the blog shows who you are without having accounts of its own.
  • No passwords here. People sign in with Google or with an email account held by IDUnify, a central account service shared with the sister site. This project never sees or stores a password.

The architecture

Browser ── sign in (Google or email) ──▶ IDUnify (central accounts)
   │                                        │  short-lived, signed ID token
   └── hands the token over once ──▶ main site ── verifies the signature ──▶ its own session
                                          ├─ display cookie: first name + photo, readable on every subdomain
                                          └─ session cookie: the only thing trusted for access (main site only)

blog ── "am I signed in?" ──▶ main site   (answer: yes / no + display name)

The sign-in page is the only page that loads IDUnify's script. It signs you in there, then sends the resulting ID token to the main site exactly once. The server checks that the token is genuine (its signature, its issuer, its audience and its expiry), creates or updates the member record, and starts its own session. From then on, pages never talk to IDUnify: they read the site's own session, which is fast and keeps third-party scripts off every page.

on sign-in:
    token  = IDUnify.currentUser.getIdToken()
    send token to the main site (once)
server:
    claims = verify(token)            # signature, issuer, audience, expiry
    member = upsert(claims.uid, name, email, photo)
    session.start(); session.uid = member.uid
    set display cookie (first name, photo)

Two cookies, two jobs

The top bar on every page, including the blog, shows your first name and photo. That needs something every subdomain can read. Access to your saved words or your boards needs something nobody can forge or read from scripts. These are different jobs, so they are different cookies:

Display cookieSession cookie
Holdsfirst name, photo addressa random session id
Readable byevery *.moradoklanna.com page, and scriptsthe main site's server only
Used for accessneveralways

The display cookie is treated as a hint and nothing more. Every action that touches your data checks the session on the server.

The blog has no accounts

The blog is a fast, static-like Markdown site, and it should stay that way. Its "sign in" link goes to the main site's sign-in page with a return address, so after signing in you land back on the same article. The account menu on the blog links to the main site (credit, conversations, saved words).

Because the blog only sees the display cookie, it asks the main site after the page loads: is this person really signed in? The main site answers only that exact blog address, with a yes or no and the display name, and nothing else. If the answer is no, the blog swaps the name for "sign in".

Visitors without an account

Boards can be created and edited by anyone. To make history and live editing readable without accounts:

  • Each browser gets an anonymous visitor id. The server keeps only a one-way hash of it, which is enough to say "the same person edited this twice" and nothing more.
  • Visitors choose a display name, or get a Lanna fruit nickname (🥭 บ่าม่วง, 🍌 ก้วย…) taken from the dictionary.
  • A board belongs to the browser that created it. When that person signs in, on any device, the boards that browser created move into the account, so they can edit them everywhere. Boards made by others stay theirs, and earlier edits keep the nickname they were made under.
on every board visit by a signed-in member:
    for board in boards this browser created:
        if board has no account yet: attach it to member     # first account wins
    member owns every board attached to them, on any device

Case study: "signed in here, not there"

The symptom. A member saw their name and photo in the top bar, yet the boards page said "sign in to keep your boards in your account". Sometimes, not always.

Finding it. The two cookies disagreed: the display cookie said "signed in", while the server found no session. The sessions themselves were fine; they were not expiring or being cleaned up. The pattern only made sense once we noticed the site also answered on a second address (the www. form). The display cookie is shared by every subdomain, but a session cookie belongs to one exact host. Sign in on one address, and the other address showed your name with no session behind it.

The fix, in three parts.

  1. One address. The second address now redirects permanently to the main one, keeping the path and query. Every page already declared the main address as its canonical form, so search engines get a clean, single address too.
  2. An honest top bar. If the display cookie says "signed in" but the server has no session, the top bar shows "sign in" and removes the stale display cookie. It can no longer claim something the server disagrees with, however a session was lost.
  3. The blog checks too, as described above.

Proving it. Each case is tested in a real browser: a stale display cookie with no session (the top bar and the blog both show "sign in", and the stale cookie is gone), and a real test session (the name stays, boards treat the visitor as a member). Test accounts are flagged and never counted in statistics.

Privacy by design

  • Stored for members: the name, email and photo address from IDUnify, the member's plan, and what they choose to save. No passwords, ever.
  • Not stored: IP addresses in the AI and feedback logs; who liked a board (only a count); anything about visitors beyond the hashed visitor id and the nickname they chose.
  • Leaving: members can delete their data from their account page, which also ends the session and clears both cookies.

What we would do next

  • A short "you were signed out" notice when a session ends unexpectedly, instead of silently showing "sign in".
  • Signing in once for the sister site and this site in the same browser session, when both are open, without a second click.

Designed and built by Jarus Soontornsing (Toni Maxx) (จรุส สุนทรสิงห์) · บริษัท ลอจิกเบเกอร์ จำกัด (LogicBaker Company Limited)

Lanna knowledge and dictionary: อ.จรีย์ สุนทรสิงห์, founder of มรดกล้านนา