Ask
94
@p99_hana ·

banned a user and they kept posting for 27 more minutes with jwt sessions JWT

We use JWT sessions with a 30 minute expiry, no database read on normal requests. Banned an account for obvious reasons and watched them keep posting until their token expired.

I know why this happened. What I do not know is what the normal solution looks like in production, versus what people write in blog posts. Denylist? Shorter tokens? Give up and go back to server sessions?

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @rueben_alsop · last wk. · 3 replies

    You made this trade deliberately when you picked stateless tokens: one database read per request in exchange for revocation being eventually consistent. It is a fine trade. It is just a trade, and you are now paying the part you did not price in.

    The standard shape is a short access token, 5 to 10 minutes, plus a refresh token that does hit the database. Revocation then has a bounded window equal to the access token life. On top of that, a denylist keyed by jti in Redis or KV with a TTL equal to the token's remaining life, so entries clean themselves up and the set stays tiny.

    The honest thing to check first: how many of your requests already read the database for user data? If it is most of them, you do not have stateless sessions, you have server sessions with an extra signature verification step and worse revocation. In that case going back is not a defeat, it is just noticing.

    132
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @pumptest_pat · last wk.

      That last paragraph is the one people do not want to hear. Count the actual queries in a request trace before you defend the architecture. It is usually four.

      40
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @thea_mandel · last wk.

      A session_version integer on the user row is the poor-person's denylist and it is genuinely fine for most products. Bump it on ban, password change and logout-everywhere; put it in the token; compare on refresh. One column, no extra infrastructure.

      15
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @warranty_wes · 2w ago

    Solve it at the write path instead. You do not need every request to know about the ban, you need the endpoints that create content to know. That is usually four or five handlers, and a ban check there is one indexed lookup on the operations where being wrong actually costs you something.

    Reading a page as a banned user for six more minutes is not the incident. Posting is.

    58
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @hemline_hana · last wk. · 2 replies

    Drop the TTL to 5 minutes and stop. Most teams build a whole revocation subsystem for a problem that a smaller number solves. The refresh call is already your natural revocation point.

    39
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @stack_trace_sy · last wk.

      Five minutes means a refresh every five minutes from every active client, and clients have several tabs open. That is a real amount of traffic to an endpoint that touches your database on every call, so measure it rather than assuming small TTL is free.

      18
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @burr_and_edge · last wk.

    Log the ban timestamp and their last successful write, then look at the gap. If it is under your TTL, nothing malfunctioned - the system did what you configured it to do, and the fix is a config number rather than an architecture. If it is over your TTL, you have a second bug and that is the interesting one.

    21
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @late_stage_phd · 2w ago · 2 replies

    Rotate the signing secret. That invalidates the token immediately with no denylist and no schema change.

    16
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @rueben_alsop · last wk.

      It also invalidates every other token in existence, so you log out your entire user base to ban one person. It works in the sense that a fire alarm works on burnt toast.

      There is a narrow real version of this - key rotation with a key id so you can retire one key while others stay valid - but that is a key management design, not a ban button.

      13
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report