Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Long Living Tokens, Die, Die, Die! (or: The One...

Avatar for sylph01 sylph01
October 16, 2026

Long Living Tokens, Die, Die, Die! (or: The One Ring is Banned in Modern)

@ Kaigi on Rails 2026, 2026/10/16
https://kaigionrails.org/2026/talks/sylph01/
(English version of the slides)

Avatar for sylph01

sylph01

October 16, 2026

More Decks by sylph01

Other Decks in Programming

Transcript

  1. Long Living Tokens, Die, Die, Die! (or: The One Ring

    is Banned in Modern) Ryo Kajiwara (sylph01) @ Kaigi on Rails 2026, 2026/10/16 1
  2. Slides are available from the QR code → or from

    my Speaker Deck (username sylph01 ) 2
  3. whoami Ryo Kajiwara / sylph01 GitHub @sylph01, Twitter @s01, bsky

    @sylph01.s01.ninja Freelance web developer, focused on cryptography and authz/authn Lives in Matsuyama, Ehime I currently work at: codeTakt (did stuff related to this talk) Leaner Technologies Does many things (see background) 3
  4. Purpose of this talk Provide ways to eliminate long-living/permanent tokens

    with strong capabilities that live in your web services. 4
  5. The Usual Disclaimer ...doesn't apply this time as much. We're

    not using frontier cryptography, and we're not hand-rolling cryptographic libraries this time. So, do try this in production, but still be aware of your risks. 5
  6. 6

  7. Problem space What is a "long-living token" ? An access

    token that's embedded to a service Which usually has strong capabilities 7
  8. The One Ring Admin access token is The One Ring

    (as in The Lord of the Rings) As long as The One Ring exists, malicious actors can abuse The One Ring, and the world is in constant danger Throw The One Ring away into Mount Doom And you don't have to do a 1000+ pages worth adventure for that image: https://www.needpix.com/photo/742654/ring-gold-middle-earth-golden-ring-metallic-map-cgi-jewellery-free-pictures 8
  9. Note: Token Revocation There is a standard. RFC 7009 might

    be the Mount Doom you're looking for Self-contained JWT access tokens have to wait until expiration If expiration never happens... If you can't destroy it normally, it needs to vanish before it exists 10
  10. Token vs Password Both are credentials, but: Password is a

    phrase used for authentication Token is a signifier of authorization It is essentially a ticket (see background) Tickets only come with the result of authorization. They don't have identity verification (= authentication) image: https://www.flickr.com/photos/kolix/2539213620 by K.L. (CC BY-NC-SA 2.0). New York Metro's token 11
  11. Wait, but we shouldn't do regular password resets, right? You

    might be talking about NIST SP 800-63B-4, but: Forcing password resets should be avoided in order to avoid selecting weak passwords Of course both passwords and tokens need to be hard to predict 12
  12. Wait, but we shouldn't do regular password resets, right? We

    are aware of the use of passwords, but tokens are often left unattended Especially long-living tokens for automation 13
  13. Attack Surface of Tokens (# of simultaneously active tokens) x

    (period of expiration) x (# of servers tokens are sent) Taking any one active token is a successful attack If the expiry never happens, the time you can attempt an attack is infinite More servers to be sent, more vulnerable against server/traffic misconfiguration 14
  14. 15

  15. Prerequisites We will assume that the access tokens in question

    are: minted/handed out via OAuth and signify the result of authorization and we will address the problem mostly with the OAuth ecosystem. 17
  16. Whose token is that? User privilege Access tokens that manipulate

    your data only Application/system privilege Access tokens that manipulate multiple users' data 18
  17. Who acts with that token? A person, synchronous A machine,

    unattended by a person, asynchronous 19
  18. Which OAuth mode? user attended unattended AuthCode Grant offline_access system

    Client Credentials Grant Client Credentials Grant 20
  19. Isn't a refresh token "a long-living token?" Yes, but it's

    a bit better than a long-living access token. Access tokens are used mulitple times, against multiple resource servers Refresh tokens are used only against the authorization server, and some implementations make it single-use. The refresh token being single-use is implementation dependent, not forced by the standard 23
  20. Why offline_access is special It's defined by OpenID Connect, not

    by OAuth OIDC Core §11 "When offline access is requested, a prompt parameter value of consent MUST be used unless other conditions for processing the request permitting offline access to the requested resources are in place." It is essentially a different grant The token still lives after logout, outlives the session 24
  21. Stop using offline_access with strong privileges wide scope, unattended, lives

    after logout = Leaving The One Ring on the table offline_access = "Give me a long-living token for the user" You don't just request offline_access because you want a refresh token 25
  22. Then how do I make admin screens? You don't need

    a token most of the time. It's the same application, use a session handler. If the admin UI and the API is separate: User identity of the admin, AuthCode grant sub claim of the admin scope signifying admin capabilities Token expires with the UI session 26
  23. Then how do I make admin screens? sub claim of

    the admin We can figure out who took administrative actions = auditable Token expires with the UI session Stop using admin offline_access 27
  24. When it gets complicated: Async Attempt batch processing -> Enqueue

    jobs -> Sidekiq picks it up later We don't have an admin access token when Sidekiq picks it up Let's persist the admin access token! ... in this way we've created a "long-living token"... 28
  25. When it gets complicated: Async We enqueue the job with

    admin's credentials The credential that Sidekiq uses is Client Credentials We record the audit trail that the admin enqueued the job Splits who is accountable for the action and who is accountable for the enqueue itself 29
  26. Client Credentials Grant OAuth is not only AuthCode grant... (A)

    Client Authentication Client Authorization Server (B) Access Token 30
  27. 31

  28. Client Authentication in OAuth Confidential Client Clients that can hold

    secrets for authentication Server-side apps Public Client Clients that are not confidential clients = clients that cannot hold secrets safely Native apps, browser-based apps (SPAs) 32
  29. Client Authentication in OAuth (more like a sidenote, but recently

    relevant) DO NOT IMPLANT client_secret IN YOUR NATIVE APPS Especially in the age of LLMs, reverse engineering is free To use Authorization Code Grant with public clients, use Proof Key for Code Exchange (PKCE) 33
  30. Client Authentication in OAuth shared secret client_secret_post , client_secret_basic ,

    client_secret_jwt no shared secret = uses public key encryption private_key_jwt , tls_client_auth , self_signed_tls_client_auth https://qiita.com/TakahikoKawasaki/items/63ed4a9d8d6e5109e401 (JA) 34
  31. Wait, isn't client_secret also a "long-living token"? It's more like

    a password It's only sent to the authorization server 35
  32. Client Authentication that doesn't depend on persistent shared secrets FAPI

    (Financial-grade API) requires methods that don't rely on shared secrets. private_key_jwt Sign the payload of the token request with a private key AS verifies the signature with the pre-registered public key mTLS (mutual TLS) RFC 7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants 36
  33. Caveats of using mTLS with Rails Rails apps usually run

    behind load balancers and reverse proxies that terminate TLS connections. In that case, these endpoints communicate mTLS information with HTTP headers, which sometimes causes security issues. 37
  34. Caveats of using mTLS with Rails: ALB Rails apps under

    ALB use headers such as X-Amzn-Mtls-Clientcert to obtain mTLS information If user-specified headers directly reach the Rails app, mTLS guarantees are nullified When headers aren't split at ingress When Rails apps can be directly accessed without ALB 38
  35. Caveats of using mTLS with Rails: ALB modes of ALB

    operation passthrough: Backend has to extract the certificate from X-AmznMtls-Clientcert , then manually authenticate the client. This includes the verification of the full certificate chain verify: ALB verifies the certificate against a trust store, parsed metadata is passed in the header Using verify mode is simpler 39
  36. When RP compromise is in scope Using private_key_jwt or DPoP

    (Demonstrating Proof of Possession) with hardware-stored keys Protects keys from copying Attested Workload Identity SPIFFE 40
  37. Using hardware to store keys Chips that can generate/store public/private

    key pairs Operations are only accessible via APIs, keys are not accessible Trusted Platform Module (TPM) Secure Enclave Hardware Security Module (HSM) External hardware 41
  38. Sender-Constrained Access Token Restricts the use of the access token

    to a specific sender Does not just accept the token by its face value (= Bearer tokens) private_key_jwt protects token retrieveal, Sender-Constrained ATs protect token usage 42
  39. DPoP (RFC 9449) Client generates key pair Token request includes

    {public key, payload, signature} = DPoP proof JWT AS issues an access token bound to the public key Each resource requests send the access token + the DPoP proof JWT for that request RS verifies DPoP proof JWT, checks the binding of token to key Stealing the access token becomes insufficient for an attack! 43
  40. SPIFFE A method for service authentication by giving workloads IDs

    and verifying them SPIRE is a reference implementation of the standard To issue IDs, SPIFFE uses attestation of evidences that the workload has instead of shared secrets Who the node is (based on cloud/Kubernetes attributes), who the process is (workload seen from the SPIFFE agent) This could totally be a full talk by itself 44
  41. Relationship of SPIFFE with OAuth? SPIFFE itself is sufficient for

    inter-service authentication for now. There is an Internet-Draft called draft-ietf-oauth-spiffe-client-auth that tries to bridge SPIFFE with OAuth. As of 2026/06, it is a WG Item, and the draft number is -02. 45
  42. Minimize privileges Do less with operator scope Use The Three,

    The Seven, and The Nine for lesser operations Separate strong operations into a different scope The Nine might not be a good example of privilege minimization, because the bearers of The Nine became Nazgûls... 47
  43. Minimize audiences RFC 8707 tells you how to specify which

    audience the access token you're requesting is intended for 48
  44. 50

  45. What if updates conflict? When multiple workers exist and their

    cached tokens expire at the same time It's your usual parallelism/concurrency problem 51
  46. Refresh token Conclusion: Do not use refresh tokens on multiple

    workers. Refresh tokens are used in user contexts If you really want to automate then create multiple sessions, but having multiple sessions increases the risk of compromise... 54
  47. Client Credentials Grant No correctness race, because all authentication requests

    will succeed and return a valid token. But that will increase the number of valid tokens... 55
  48. Thundering herd problem A problem where multiple workers react to

    an event at the same time and performs an operation, but only one worker succeeds in operating, and the others will fail. Also called cache stampede when used in terms of caching. When the cycle of expiration is constant, refreshes happen simultaneously, causing this problem. 56
  49. Thundering herd problem Give a random jitter on the refresh

    timing instead of refreshing at the same time Avoids simlutaneous refresh race_condition_ttl option in Rails.cache.write / fetch First request goes through normally For the next requests, this will return the first request's result for the duration of race_condition_ttl instead of actually performing the request again 57
  50. How to cache the token We have Solid Cache these

    days I recommend Solid Cache with Active Record Encryption Being written to the database means it gets backed up, so encryption is important Add encrypt: true , bin/rails db:encryption:init 58
  51. Rails.cache.fetch( ACCESS_TOKEN_CACHE_KEY, race_condition_ttl: RACE_CONDITION_TTL ) do |_name, options| # block

    runs when entry does not exist response = request_token # call token request jitter = rand(0.7..0.9) # random value from 70 to 90 percent options.expires_in = (response[:expires_in] * jitter).seconds response.fetch(:access_token) { raise TokenRequestError } end 59
  52. Advanced: Sender-Constrained vs shared token among workers? We want Sender-Constrainedness

    for security, but: You can't share a Sender-Constrained Access Token Especially with hardware-backed keys Per-worker Sender-Constrained keys will worsen cache hit rates You can have a process that handles the token and proxies requests, but that process will bear the full load 60
  53. 61

  54. And we moved away from fixed tokens using the CCG

    functionality in the Authorization Server. (Similar PRs in other repos exist) The race condition prevention code is almost the same as production code. 64
  55. Up to which point? Uses client_secret_post for now non-hardware-backed private_key_jwt

    could be easy DPoP needs non-trivial implementation in the AS side 65
  56. 66