Managing Sessions

The guide shows you how to manage sessions when working with upstream APIs.

General Approach

Since websites implemented with React Storefront typically contain both PWA and non-PWA pages that share a session, the session management approach that we recommend more closely follows typical web session semantics, such as using a cookie to store the session id, rather than a pure API-only solution that might be implemented for a native mobile app.

We recommend using a session cookie to propagate session identity across requests.

All API calls should accept the following request headers:

Request Headers

namevalue
x-api-keyservice level API key
cookiesessionid={session id}

If an API method does not receive a valid session id in the cookie header, it may send back a set-cookie header to establish a new session:

Response Headers

namevalue
set-cookiesessionid={session id}

Authorization

Once a session is established, all requests that return user-specific data, such as account details, order history, and cart, should require a valid session ID to be passed in the cookie along with the service-level API key.

If a session is invalid or expired, the API should respond with a 401 status code (Unauthorized).

Note: user authorization should follow the same architecture as a regular web request. Specifically, clients will initially make requests with an anonymous session (similar to how a user might browse a website without logging in) and then the session will be converted to a logged in user when the user’s username and password credentials are successfully applied to a specific authorization endpoint.

Invalid Service-Level API Key

If the x-api-key header does not contain a valid service level API key, the API should return a 401 (Unauthorized) status code, with an additional x-api-key-invalid header with a value of true.

Invalid and Expired Sessions

If a session is invalid or expired, the API should respond with a 401 (Unauthorized) status code.

Preventing Unauthorized Access to Information

If the user attempts to access information that they have not be granted permission to, the API should return a 403 (Forbidden) status code.

It is important that the API enforce both service-level and session-level authorization.

Being a stateless and serverless platform, by default React Storefront does not have the ability to manage sessions or authorization on its own. It relies on the underlying ecommerce platform API to do so. In cases where session management or data storage is required, the client or Moovweb will need to supply a storage mechanism via HTTPS (e.g. a database, etc.) for an additional cost and implementation overhead. Please talk to your technical dev lead and account manager if you believe this is required.

Additional Hardening

In addition to the above, we recommend that the API implement the following security measures:

  • SSL should be required for all calls. If a client attempts to access the API without using SSL, the API should return a 426 (Upgrade Required) status.
  • The API can require an x-api-client header in addition to the x-api-key header
  • The API should restrict client access to a specific set of IP addresses (an API whitelist)

To relay the session cookie from the API's response header back to the browser in a fromServer route handler, use fetchWithCookies:

import { fetchWithCookies } from 'fetch'

export default async function cartHandler(params, request, response) {
  
  const result = await fetchWithCookies('/cart', {
    headers: {
      "x-api-key": Config.get('apiKey')
    }
  })

  return result.json()
}

React Storefront will automatically relay any cookies received from upstream APIs unless your route contains a cache handler that specifies that the response should be cached on the server.

Handling Unauthorized Access

If the user tries to access sensitive information with an invalid or expired session, the upstream API should return a 401 status. Here's how you can handle that response in a fromServer route handler:

import { fetchWithCookies } from 'fetch'

export default async function myAccountHandler(params, request, response) {
  
  try {
    return await fetchWithCookies('/my-account', {
      headers: {
        "x-api-key": Config.get('apiKey')
      }
    }).then(res => res.json())
  } catch (e) {
    // all non 2xx status codes result in an error
    if (e.response && e.response.status === 401) {
      response
        .status(401)
        .redirectTo('/login')
    } else {
      response.status(500)
    }
  }

}