Cannot prevent replay attacks with cached session tokens

Reposting this issue here as requested in [IMPORTANT] Stop creating issues here, use the Shopify Community forum instead · Issue #2521 · Shopify/ui-extensions · GitHub


Please list the package(s) involved in the issue, and include the version you are using

@shopify/ui-extensions-react@2024.1.1

Describe the bug

useSessionToken produces a SessionToken. SessionToken.get() is annotated with the following note:

 // https://github.com/Shopify/ui-extensions/blob/c22cbdaf0b3bcc887aa554c30bfdbcdeb4d96750/packages/ui-extensions/src/surfaces/checkout/api/standard/standard.ts#L733-L741
 export interface SessionToken { 
   /** 
    * Requests a session token that hasn't expired. You should call this method every 
    * time you need to make a request to your backend in order to get a valid token. 
    * This method will return cached tokens when possible, so you don’t need to worry 
    * about storing these tokens yourself. 
    */ 
   get(): Promise<string>; 
 } 

Also, SessionToken.jti is described]() as “A unique identifier (a nonce) to prevent replay attacks”.

  // https://github.com/Shopify/ui-extensions/blob/c22cbdaf0b3bcc887aa554c30bfdbcdeb4d96750/packages/ui-extensions/docs/surfaces/checkout/reference/examples/session-token-jwt.example.json#L12-L13
  // A unique identifier (a nonce) to prevent replay attacks
  "jti": "6c992878-dbaf-48d1-bb9d-6d9b59814fd1",

In order to effectively use SessionToken.jti to prevent replay attacks, my backend server should reject requests that include session tokens that have already been sent. This requires my checkout extension frontend to send a new session token for each request to my backend server.

However, I cannot generate a new session token for each request because SessionToken.get() returns cached tokens. Therefore my backend server thinks that subsequent requests from the frontend are replay attacks, and rejects the requests.

Steps to reproduce the behavior:

  1. Generate a session token with SessionToken.get().
  2. Send the token to your backend server in the Authorization header.
  3. Try to generate a new session token with SessionToken.get(). You should end up with the same session token as before.
  4. Sending the token to your backend server will be rejected as a replay attack.

Expected behavior

SessionToken.get() should not cache tokens. Instead, each call to SessionToken.get() should return a new token.

1 Like

Hi masonmcelvain,

Thanks for the detailed description of this issue - I’ve connected with the product team in this area and will report back when I learn more.

Hi again - just wanted to update that our product team is looking into this

1 Like

This is a significant issue, and I would love if it could be resolved as well.

Hey @Patrick_Jakubik - is the issue you’re experiencing the same as reported in the first post? If you could share any additional context that would be helpful!

Hi Liam,

Yes, same. Session tokens are cached, I wouldn’t call it bug, because it’s a documented behaviour. In my opinion, they shouldn’t be cached. This means that a malicious actor could easily tamper with our APIs, which poses a significant security risk.

Thanks for the additional context Patrick - have also passed this on to the product team who are reviewing this behaviour.