docs
  1. Developer Guide
  2. Technical Foundation
  3. Sessions

Sessions

SCAYLE Storefront leverages robust session management and authentication mechanisms to ensure secure and personalized user experiences. This includes supporting user logins, persistent baskets and wishlists, and seamless Single Sign-On (SSO) capabilities via specifiable Identity Provider (IDP).

Maintaining User State via Sessions

Storefront maintains stateful sessions, crucial for user login and the persistence of baskets and wishlists. These sessions are designed to be consistent across various servers, thanks to a shared persistent storage provider.

Session Storage

Session data is stored using a shared persistent and configurable storage provider, abstracted via Unstorage. Configuration is managed via the storefront.storage.session options.

The in-memory storage provider should only be used for development.

Check Storefront Guide - Storage for detailed information on how to configure the session storage.

For production, a reliable persistent storage solution is essential. Refer to the Storefront Guide - Caching for detailed storage configuration.

Session Cookies

The session ID is stored in a cookie, named $session-${shopId} by default. The session cookies are HttpOnly for security, preventing JavaScript access.

  • The session cookie is shop-based via its cookie name but applies to all requests, lacking path scope.
  • The session cookie's domain is set to the current domain.

Guest and Logged-In Users

Guest users receive a randomly generated UUID as their session ID. Logged-in users have session IDs that combine their user ID with a random UUID.

Session Configuration

Session storage and cookie serialization are configurable through the session property in the module configuration:

ParameterTypeDescription
session.cookieNamestringThe name used for the session cookie. Defaults to $session-${shopId}.
session.sameSite'none' | 'lax' | 'strict'The sameSite policy to use for the session cookie. Defaults to 'lax'.
session.maxAgenumberThe default maxAge (in seconds) set on the session cookie and default TTL for session store. Defaults to 86400 (one day).
session.secret

string | string[]

The secret used for signing session cookies. If an array is passed, the last value is used for signing new cookies, but all values are used to verify cookies.

This allows to rotate secrets in case your session secret get's leaked.
session.domainstringControls the domain option on the session cookie

Configuration can be set globally or per shop to allow for shop-specific behavior, with shop-specific settings taking precedence.

The session.provider configuration option has been removed with scalye/storefront-nuxt@8.0.0; use storage.session instead.

Configuration Example

// (...)
  runtimeConfig: {
    // (...)
    storefront: {
      // (...)
      session: {
        cookieName: '$session',
        sameSite: 'none',
        maxAge: 2419200, // four weeks in seconds
        secret: 'my-secret',
        domain: 'my.domain',
      },
      // (...)
    },
    // (...)
  }
// (...)

User-Bound Sessions

Upon login, the session ID is updated to include the user ID. Existing guest user basket and wishlist items are merged into the logged-in user's data.

RPCContext Session Methods

RPC methods can access and manipulate sessions through RPCContext:

MethodDescription
context.user: ShopUserProvides access to the user data saved on the session
context.updateUser: (user: ShopUser) => voidUpdate the user data saved on the session
context.destroySession: () => Promise<void>Destroys the current session, logging out the user in the process
context.destroySessionsForUserId: (userId: number, sessionsToKeep?: string[]) => Promise<void>Destroys all sessions for a specific user, except for those listed in sessionsToKeep

Securing User Access via Authentication:

Storefront supports secure authentication features, including token-based authentication (JWKS), password reset URLs, Identity Providers (IDPs), and app keys for baskets and wishlists. Storefront relies hereby on OAuth to communicate with the SCAYLE Checkout.

Token-Based Authentication (JWKS)

Token-based authentication allows to implement custom login forms instead of relying on forms provided by the Checkout service.

To implement custom login forms, Storefront acts as an OAuth client and uses the Get JSON Web Key Set Authentication API endpoint ({OAUTH_API_HOST}/.well-known/jwks.json):

Token-based authentication flow

Token verification

Configuration is done via the runtimeConfig.storefront.oauth property:

Create a new API client using the SCAYLE Panel to get oauth.clientId and oauth.clientSecret.

nuxt.config.ts
// (...)
  runtimeConfig: {
    // (...)
    storefront: {
      // (...)
      oauth: {
        apiHost: 'https://{{tenant-space}}.storefront.api.scayle.cloud/v1/',
        clientId: '1',
        clientSecret: '', // Set in .env NUXT_STOREFRONT_OAUTH_CLIENT_SECRET
      },
      auth: {
        redirect: {
          home: '',
          logout: ''
        }
      },
      // (...)
    },
    // (...)
  }
// (...)

These configuration values can be overridden via the following environment variables:

OAUTH_API_HOSTURL of the Oauth service
OAUTH_CLIENT_IDID that the authorization server assigned to this application
OAUTH_CLIENT_SECRETSecret is known only to the application and the authorization server. It is essentially the application’s password.

Protected RPC

Protected RPCs must verify user access based on the access token and user data.

For this the RPC in question must check if there is a user within the request and the requested data / action can be requested / done by the user. This can be achieved by e.g. matching user IDs within the access token against the user ID within an order.

For user-related actions, like changing the password, this will be handled by the underlying SCAYLE Authentication Service itself.

Password Reset URL

A password reset URL can be manually configured in nuxt.config.ts, including the hash={hash} query parameter on a per-shop basis on the runtimeConfig.storefront.shops.{UNIQUE_IDENTIFIER}.auth.resetPasswordUrl property:

nuxt.config.ts
// (...)
  runtimeConfig: {
    // (...)
    storefront: {
      // (...)
      shops: {
        // (...)
        auth: {
          resetPasswordUrl: `
            https://${baseShopDomain(shop.code)}/${shop.locale}/signin/
          `, // 'http://localhost:3000/signin?hash={hash}'
          }
      },
      // (...)
    },
    // (...)
  }
// (...)

The password reset URL should include the query param hash={hash}. When the password-forgotten mail is sent, {hash} will be replaced with an actual hash, which is needed to reset the password.

Identity Providers (IDPs)

Identity Provider (IDP) support enables the use Single Sign-On (SSO). This simplifies the login process by reducing the number of passwords users need to remember, as one set of credentials is used across multiple websites.

The backbone of the Storefront integrated IDP support consists of two functionalities:

  1. An internal part that runs useIDP() composable and generates redirect URLs for all of the configured IDPs.
  2. A handleIDPLoginCallback function performs the actual login..

For more information, check the Authentication API Guide.

Configure Identity Provider

The available Identity Providers can be configured in the nuxt.config.ts, where Google, Facebook, Keycloak, Apple, and Okta are currently supported:

// (...)
  runtimeConfig: {
    storefront: {
      // Global IDP configuration for all shops
      idp: {
        enabled: true,
        idpKeys: ['facebook', 'google'],
       idpRedirectURL: 'http://localhost:3000/signin',
      },
      //...
      shops: {
        1001: {
          // (...)
          // Shop-specific overrides for the IDP configuration
          idp: {
            enabled: true,
            idpKeys: ['facebook'],
            idpRedirectURL: 'http://localhost:3000/signin',
          },
        },
      // (...)
      },
    // (...)
    },
  // (...)
  }
// (...)

IDP Redirect URL

Redirects towards the URLs for the respective configured IDPs are handled by the useIDP composable.

Calling the useIDP composable checks if IDP is enabled, validates necessary configurations, and returns a list of redirect URLs for each IDP based on your shop configuration as the data return value:

const idpParams = computed(() => ({
  queryParams:
    typeof route.query.redirectUrl === 'string'
      ? { redirectUrl: route.query.redirectUrl }
      : undefined,
}))

const idp = useIDP(idpParams)

const externalIDPRedirects = computed(() => {
  return idp.data.value && Object.keys(idp.data.value).length > 0
    ? (idp.data.value as Record<string, string>)
    : undefined
})

Login with IDP

Once the user clicks on the IDP button, the user is redirected to the IDP login page. After successful login, the user is redirected to the callback URL. That is the shop URL with the query parameter code appended to it. This code is used to generate the access token and refresh token.

const { loginIDP } = useAuthentication('login')

const idpCode = computed(() => {
  const code = route.query.code
  
  return typeof code === 'string' ? code : undefined
})

onMounted(async () => {
  if (!idpCode.value) {
    return
  }

  await loginIDP(idpCode.value)
})

IDP Authentication flow

App keys for baskets and wishlists

The appKeys configuration defines how unique keys are generated for baskets and wishlists. These keys are used to create a relation between the user (session) and the respective basket or wishlist.

appKeys must remain confidential. Exposure may enable unauthorized modifications to customer baskets and wishlists.

wishlistKey and basketKey are template strings. When creating the key Storefront Core will replace {shopId} and {userId} with the current shopId and userId. It will then hash the result with the chosen hash algorithm.

sha256 is recommended as hash algorithm due to increased security, md5 is only supported to provide backward-compatibility. Further usage is discouraged.


// (...)
  runtimeConfig: {
    storefront: {
      // (...)
      appKeys: {
        wishlistKey: 'wishlist_{shopId}_{userId}',
        basketKey: 'basket_{shopId}_{userId}',
        hashAlgorithm: 'sha256',
      },
      // (...)
      },
    // (...)
    },
  // (...)
  }
// (...)
Provide Feedback