<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged uuid-token</title>
<link>https://coderlegion.com/tag/uuid-token</link>
<description>Powered by Question2Answer</description>
<item>
<title>Why I Chose a Simple Capability Token for Waitless — and When I Would Harden It</title>
<link>https://coderlegion.com/29812/why-i-chose-a-simple-capability-token-for-waitless-and-when-i-would-harden-it</link>
<description>&lt;p&gt;While working on &lt;strong&gt;Waitless&lt;/strong&gt;, I needed customers to be able to track their position in a queue and leave the queue without creating an account.&lt;/p&gt;
&lt;p&gt;That led to a small but interesting security design question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;How can I authorize an anonymous user without introducing a full authentication system?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My current solution is deliberately simple: each queue entry receives a random UUID token, and that token is used as a capability for the tracking link.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;uuid-token-instead-of-a-sequential-id&quot;&gt;UUID token instead of a sequential ID&lt;/h3&gt;
&lt;p&gt;A sequential identifier could look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/track/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is that IDs such as &lt;code&gt;123&lt;/code&gt;, &lt;code&gt;124&lt;/code&gt;, and &lt;code&gt;125&lt;/code&gt; are predictable and enumerable.&lt;/p&gt;
&lt;p&gt;For an anonymous tracking system, I don't want a user to be able to guess another customer's resource simply by changing the ID.&lt;/p&gt;
&lt;p&gt;Instead, an entry has a separate token:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-prisma&quot;&gt;model Entry {
  id      String @id @default(uuid())
  token   String @unique @default(uuid())

  name     String
  email    String?
  position Int
  status   EntryStatus @default(WAITING)

  queueId String
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The public URL therefore looks more like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/track/550e8400-e29b-41d4-a716-446655440000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important point is that the token is not intended to be an ordinary identifier.&lt;/p&gt;
&lt;p&gt;It acts as a &lt;strong&gt;capability&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-a-capability-url-&quot;&gt;What is a capability URL?&lt;/h3&gt;
&lt;p&gt;A capability can be thought of as:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;An unguessable secret that grants access to a specific resource.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my case:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;possess valid token
        ↓
access this queue entry
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The capability is deliberately designed to be difficult to guess.&lt;/p&gt;
&lt;p&gt;That is different from simply hiding a sequential ID and hoping nobody discovers it.&lt;/p&gt;
&lt;p&gt;This distinction matters:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Security by obscurity
→ &quot;Nobody knows the ID.&quot;

Capability-based design
→ &quot;Access requires possession of a deliberately unguessable credential.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second is a much more intentional security model.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-i-kept-the-token-simple-for-now&quot;&gt;Why I kept the token simple for now&lt;/h3&gt;
&lt;p&gt;Waitless is an anonymous queueing system.&lt;/p&gt;
&lt;p&gt;A customer does not need an account, password, or traditional login just to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;see their position&lt;/li&gt;
&lt;li&gt;receive queue updates&lt;/li&gt;
&lt;li&gt;leave the queue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Introducing a complete authentication system would add considerable complexity to the product and to the user experience.&lt;/p&gt;
&lt;p&gt;For the current use case, a capability link gives me a simple model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Join queue
    ↓
Generate random token
    ↓
Give customer tracking link
    ↓
Customer presents token
    ↓
Server identifies the queue entry
    ↓
Allow limited actions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a conscious trade-off.&lt;/p&gt;
&lt;p&gt;I'm choosing &lt;strong&gt;simplicity and frictionless anonymous access&lt;/strong&gt; because the current resource is relatively limited in scope.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;but-the-token-is-also-a-bearer-credential&quot;&gt;But the token is also a bearer credential&lt;/h3&gt;
&lt;p&gt;There is an important consequence.&lt;/p&gt;
&lt;p&gt;If possession of the token grants access, then:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;whoever possesses the token can use it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That makes the token a bearer credential.&lt;/p&gt;
&lt;p&gt;For example, the current &lt;code&gt;leave()&lt;/code&gt; operation relies on the same tracking capability.&lt;/p&gt;
&lt;p&gt;So if the token is leaked, copied, logged, or shared, another person could potentially use it.&lt;/p&gt;
&lt;p&gt;That means the design is intentionally simple, but it is not the strongest possible security model.&lt;/p&gt;
&lt;h4 id=&quot;known-limitation&quot;&gt;Known limitation&lt;/h4&gt;
&lt;blockquote&gt;&lt;p&gt;The tracking token currently also functions as a bearer credential for &lt;code&gt;leave()&lt;/code&gt;, with no rate-limiting or one-time consumption.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I consider this an accepted limitation of the current design rather than something I want to hide.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-stronger-design-capability-url-session-cookie&quot;&gt;A stronger design: capability URL → session cookie&lt;/h3&gt;
&lt;p&gt;One way to harden the system would be to use the tracking link only as an initial capability.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/claim/&amp;lt;secret&amp;gt;
      ↓
validate capability
      ↓
create anonymous session
      ↓
HttpOnly + Secure cookie
      ↓
redirect to /track
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, the token would no longer need to remain in the URL.&lt;/p&gt;
&lt;p&gt;The browser would use the session cookie for future requests.&lt;/p&gt;
&lt;p&gt;This would reduce some of the risks associated with repeatedly carrying a secret in a URL and would give me more control over session lifecycle, expiration and revocation.&lt;/p&gt;
&lt;p&gt;But it introduces another trade-off.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-trade-off-security-vs-convenience&quot;&gt;The trade-off: security vs. convenience&lt;/h3&gt;
&lt;p&gt;The current capability link has an important usability property:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;The link itself works across devices.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A customer can open it on their phone, copy it to another device, bookmark it, or reopen it later.&lt;/p&gt;
&lt;p&gt;With a one-time capability-to-session exchange, that behavior becomes more complicated.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Phone
  ↓
claim token
  ↓
session created
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the same one-time token may no longer be usable on another device.&lt;/p&gt;
&lt;p&gt;Supporting multiple devices would therefore require additional identity/session handling, which increases complexity.&lt;/p&gt;
&lt;p&gt;For a simple anonymous queue, I decided that this trade-off is not currently justified.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-would-this-design-change-&quot;&gt;When would this design change?&lt;/h2&gt;
&lt;p&gt;The answer is: &lt;strong&gt;when the product starts having identity requirements.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Imagine Waitless later introduces a reservation system.&lt;/p&gt;
&lt;p&gt;Now a customer might have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Account
   ↓
Reservations
   ↓
Queue entries
   ↓
Payment history
   ↓
Cancellation rules
   ↓
Personal information
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The security requirements become much stronger.&lt;/p&gt;
&lt;p&gt;At that point, a random tracking capability may no longer be enough as the main authorization mechanism.&lt;/p&gt;
&lt;p&gt;I would probably move toward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User authentication
        ↓
Authenticated session
        ↓
Authorization
        ↓
Reservation / queue access
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The capability token could still exist for specific use cases, but it would no longer be responsible for the entire identity model.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;uuidv4-vs-uuidv7&quot;&gt;UUIDv4 vs. UUIDv7&lt;/h2&gt;
&lt;p&gt;This also made me think more carefully about what an identifier is supposed to do.&lt;/p&gt;
&lt;p&gt;For database identifiers, UUIDv7 is attractive because it is time-ordered and therefore provides better index locality than completely random UUIDv4 values.&lt;/p&gt;
&lt;p&gt;For a public capability, however, my primary requirement is different:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;I care more about unpredictability than database ordering.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's why I would keep the concepts separate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Database ID
    ↓
UUIDv7 can be a good choice

Public capability token
    ↓
Cryptographically random token / UUIDv4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an example of a broader engineering principle:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;The best identifier for the database is not necessarily the best credential for the client.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-about-storing-the-token-itself-&quot;&gt;What about storing the token itself?&lt;/h2&gt;
&lt;p&gt;Another improvement would be to store only a hash of the capability:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;raw token
    ↓
SHA-256
    ↓
tokenHash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the database contains the verifier rather than the original bearer secret.&lt;/p&gt;
&lt;p&gt;The application can hash the presented token and compare the result.&lt;/p&gt;
&lt;p&gt;This gives additional protection if the database contents are exposed.&lt;/p&gt;
&lt;p&gt;Again, it adds some implementation complexity, so it is a hardening step rather than something I needed to introduce simply to make the current Waitless flow work.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;my-current-decision&quot;&gt;My current decision&lt;/h2&gt;
&lt;p&gt;For the current version of Waitless:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Random capability token
        ↓
Tracking URL
        ↓
Anonymous access
        ↓
Limited actions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I deliberately chose this because it provides a very small and understandable authorization mechanism without forcing customers through registration and login.&lt;/p&gt;
&lt;p&gt;The trade-off is that the token is a bearer credential, so the system needs to treat it as sensitive.&lt;/p&gt;
&lt;p&gt;I have therefore documented the limitation rather than pretending the design is perfect.&lt;/p&gt;
&lt;p&gt;My planned evolution is roughly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Current
Random capability URL
        ↓
Simple anonymous access


Later
Rate limiting
+ expiry / revocation
+ stronger token handling


When identity becomes important
        ↓
Capability URL
        ↓
Session / authentication
        ↓
Fine-grained authorization
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting lesson for me was not simply “use UUIDs.”&lt;/p&gt;
&lt;p&gt;It was learning to ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What is this value actually supposed to represent?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;An identifier, a capability, and an authenticated identity are three different concepts.&lt;/p&gt;
&lt;p&gt;Once that distinction is clear, the architecture becomes much easier to reason about.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29812/why-i-chose-a-simple-capability-token-for-waitless-and-when-i-would-harden-it</guid>
<pubDate>Mon, 05 Oct 2026 07:20:50 +0000</pubDate>
</item>
</channel>
</rss>