#Cross site data leakage in ssr

3 messages · Page 1 of 1 (latest)

upper copper
#

Hi there,

I’m building an application fully in Angular with SSR, and everything works fine most of the time. However, when multiple users are using the app at the same time, the SSR-rendered page occasionally contains data from another user, which is obviously not ideal.

After debugging, I noticed that the problem disappears when I set gcTime to 0, which makes me think the issue is related to the caching layer of TanStack Query.

What would be the best practice to ensure the cache is isolated per request/user so this kind of security issue cannot happen?

upper copper
#

Found out how to fix the issue:

import { InjectionToken, ApplicationConfig } from '@angular/core';
import { QueryClient, provideTanStackQuery } from '@tanstack/angular-query-experimental';

const QUERY_CLIENT_TOKEN = new InjectionToken<QueryClient>('QueryClient', {
  providedIn: 'root',
  factory: () => new QueryClient() // Generates new client per request
});

export const appConfig: ApplicationConfig = {
  providers: [
    // ❌ Previous: provideTanStackQuery(new QueryClient())
    // ✅ New:
    provideTanStackQuery(QUERY_CLIENT_TOKEN) 
  ]
};```

The doc's might need to be updated to this?
prime fulcrum
#

Yeah, SSR support is not that great, it's also missing a build-in way to hydrate the client to avoid going back to the loading state when components load.

You can also do something like this to change some options in the query options on the server, for example, to avoid delaying the render for retries:

// In app.config.ts
export const sharedQueryDefaults = {
  staleTime: 1000 * 30,
  gcTime: 1000 * 60 * 60 * 24,
} as const

export const createBrowserQueryClient = () =>
  new QueryClient({
    defaultOptions: {
      queries: { ...sharedQueryDefaults },
    },
  })

export const getBaseAppConfig = (queryClient: QueryClient): ApplicationConfig => ({
  providers: [
    provideClientHydration(withEventReplay()),
    provideTanStackQuery(queryClient, withDevtools(), withHydration()),
  ],
})

export const getClientAppConfig = () => getBaseAppConfig(createBrowserQueryClient())
 
// In app.server.config
const createServerQueryClient = () =>
  new QueryClient({
    defaultOptions: {
      queries: {
        ...sharedQueryDefaults,
        retry: false,
      },
    },
  })

export const getServerConfig = (_context: BootstrapContext) =>
  mergeApplicationConfig(getBaseAppConfig(createServerQueryClient()), {
    providers: [
      provideServerRendering(withRoutes(serverRoutes)),
    ],
  })