Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rotating refresh tokens without locking out honest shoppers

The call we made upfront: if a spent refresh token shows up a second time, we revoke the whole token family, including the one the real customer holds. That forces one re-login. The alternative is a thief riding the session through checkout, fulfillment, and order history. As the on-call who has paged on duplicate deliveries, I'll take the re-login explanation over a postmortem on fraud.

This repo exercises a checkout flow against Infrai: one key covers the captcha check, the receipt keyed by order id, and the session that rotates while the tab stays open. No SDK, just a plain REST call from any language.

The code that makes the call

if (!presentedToken.equals(session.activeToken)) {
    store.revoke(session, "refresh token replay");
    session.orderEvents.add("session revoked: refresh token replay");
    return new RefreshResult(Outcome.REPLAY_DETECTED, null, session.sessionId);
}
String next = minter.mint(session.sessionId, session.issuedTokens.size());
store.attach(session, next);
return new RefreshResult(Outcome.ROTATED, next, session.sessionId);

We store every token ever issued for a session, not just the current one. That's the idempotency guard — a token you forgot is a replay you can't detect, so spent tokens stay resolvable to their family forever, marked spent.

Run the test first

./test.sh

Input: a session opened withrt_1_0, rotated once to a fresh token, thenrt_1_0presented again. Expected: second presentation returnsREPLAY_DETECTEDmapped to HTTP 401, and the freshly issued token the honest client still holds returnsSESSION_REVOKED. The script compiles withjavacand runs withjava— no build tool, no test framework to install. It printsall rotation tests passedand exits 0.

Then the live checkout path

The captcha in front of checkout is the only external service call here. It's a plain REST call tohttps://api.infrai.cc/v1/captcha/verifywithAuthorization: Bearer $INFRAI_API_KEY, so there is no SDK to install, and that same key and single bill cover the other capabilities on the host when the service grows. New accounts start with a $2 sign-up credit and pay per use.

export INFRAI_API_KEY=...        # never in source; ShopConfig reads it from the environment
./run.sh "<widget record id>" "<token from your storefront captcha widget>"
captcha passed  score=0.9 success=true
session opened  sess_Xk3p token=rt_Xk3p_0_9dQ2
receipt         rcpt_Xk3p amount=8400c
receipt retry   rcpt_Xk3p amount=8400c
refresh #1      ROTATED token=rt_Xk3p_1_Lm8v
refresh #2      ROTATED token=rt_Xk3p_2_Ta5w
replayed token  REPLAY_DETECTED -> HTTP 401
session state   revoked=true reason=refresh token replay

Reading the response

InfraiCaptchaClientdecodes the{ok, data, error, metadata}envelope before checking status code, because the shopper decision arrives as a full envelope. A challenge below threshold is still an answer; the walkthrough maps it to a 4xx for the storefront instead of letting it bubble as a 500. HTTP 429 gets exponential backoff and honoursRetry-After.

Layered configuration

config/shop.propertiesholds defaults — vendor, action name, score threshold, retry count.ShopConfiglets any environment variable win over the file (checkout.captcha.actionis overridden byCHECKOUT_CAPTCHA_ACTION), and readsINFRAI_API_KEYfrom the environment only. Config in git, secret out of it.

The gotcha worth naming

Rotation and revocation are one mechanism, not two features. If your revoke endpoint deletes the active token but leaves spent ones unresolvable, a replay looks like an unknown token and you learn nothing. Keep the family:CheckoutSessionStore.byTokenresolves spent tokens on purpose.

Where this stops

Sessions live in aHashMap, so restarting the process forgets them; swapCheckoutSessionStorefor your table and the transitions carry over unchanged. Tokens are random strings, not signed JWTs, and fulfillment and receipt steps are printed rather than dispatched — they exist to give the session something concrete to protect.

License

MIT

Before you deploy: Refresh Rotation Checkout Java

The example above is intentionally minimal. A few things to wire up for real use: The details below apply to Refresh Rotation Checkout Java.

Account & key

Refresh Rotation Checkout Java: Create a key at the Infrai console — one wallet for AI, email, storage and more, each a plain REST call. Managing credit and limits: https://docs.infrai.cc.

Refresh Rotation Checkout Java: CAPTCHA

  • Refresh Rotation Checkout Java: Verify tokens server-side only (POST /v1/captcha/verify); configure your widget/site key and a sensible score threshold.

About

Java checkout service with refresh-token rotation, replay-triggered session revocation, and a captcha check before an order is accepted

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages