From 4c4e48533f256b83ee0658dd6cbb8994d50e99a7 Mon Sep 17 00:00:00 2001 From: Lukas Freimonas Date: Thu, 23 Apr 2026 17:59:09 +0300 Subject: [PATCH 1/2] feat: implement x-id-oidc-signedin header handling for initial sign-in state --- .../Account/AccountHeader/index.test.tsx | 3 -- .../AccountPromotionalBanner/index.test.tsx | 3 -- .../contexts/AccountContext/index.test.tsx | 23 ------------ src/app/contexts/AccountContext/index.tsx | 15 +------- .../lib/idcta/getIdctaConfig/index.test.ts | 37 +++++++++++++++++++ src/app/lib/idcta/getIdctaConfig/index.ts | 13 ++----- src/app/models/types/account.ts | 3 -- ws-nextjs-app/pages/_app.page.tsx | 7 ++-- 8 files changed, 45 insertions(+), 59 deletions(-) diff --git a/src/app/components/Account/AccountHeader/index.test.tsx b/src/app/components/Account/AccountHeader/index.test.tsx index 21be09c88a0..2b4fcd25839 100644 --- a/src/app/components/Account/AccountHeader/index.test.tsx +++ b/src/app/components/Account/AccountHeader/index.test.tsx @@ -17,9 +17,6 @@ const idctaConfig: IdctaConfig = { signout_url: 'https://example.com/signout', foryou_url: 'https://example.com/foryou', initialIsSignedIn: false, - identity: { - idSignedInCookieName: 'ckns_id', - }, }; const renderWithProviders = (overrides = {}) => diff --git a/src/app/components/Account/AccountPromotionalBanner/index.test.tsx b/src/app/components/Account/AccountPromotionalBanner/index.test.tsx index b255465d1e6..5a27f3bd7c3 100644 --- a/src/app/components/Account/AccountPromotionalBanner/index.test.tsx +++ b/src/app/components/Account/AccountPromotionalBanner/index.test.tsx @@ -15,9 +15,6 @@ const idctaConfig: IdctaConfig = { settings_url: 'https://example.com/settings', signout_url: 'https://example.com/signout', foryou_url: 'https://example.com/foryou', - identity: { - idSignedInCookieName: 'ckns_id', - }, } as unknown as IdctaConfig; jest.mock('#app/hooks/useToggle'); diff --git a/src/app/contexts/AccountContext/index.test.tsx b/src/app/contexts/AccountContext/index.test.tsx index 493f3bdf69e..71ea094b444 100644 --- a/src/app/contexts/AccountContext/index.test.tsx +++ b/src/app/contexts/AccountContext/index.test.tsx @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { use } from 'react'; -import onClient from '#app/lib/utilities/onClient'; import { IdctaConfig } from '#app/models/types/account'; -import Cookie from 'js-cookie'; import { AccountContext } from '.'; import { render, @@ -10,8 +8,6 @@ import { waitFor, } from '../../components/react-testing-library-with-providers'; -jest.mock('#app/lib/utilities/onClient'); - const mockIdctaConfig = { 'id-availability': 'GREEN', signin_url: 'https://example.com/signin', @@ -21,15 +17,11 @@ const mockIdctaConfig = { foryou_url: 'https://example.com/foryou', unavailable_url: 'https://example.com/unavailable', initialIsSignedIn: true, - identity: { - idSignedInCookieName: 'ckns_id', - }, } as IdctaConfig; describe('AccountContext', () => { beforeEach(() => { jest.clearAllMocks(); - (onClient as jest.Mock).mockReturnValue(true); delete (window as any).location; window.location = { href: 'https://example.com/current-page' } as any; @@ -176,21 +168,6 @@ describe('AccountContext', () => { expect(context.isSignedIn).toBe(false); }); - it('should set isSignedIn to true when ckns_id cookie is present', () => { - jest.spyOn(Cookie, 'get').mockReturnValue('ckns_id_cookie_value' as any); - - render(, { - idctaConfig: { ...mockIdctaConfig, initialIsSignedIn: false }, - service: 'hindi', - }); - - const testEl = screen.getByTestId('test-component'); - const context = JSON.parse(testEl.textContent as string); - - expect(Cookie.get).toHaveBeenCalledWith('ckns_id'); - expect(context.isSignedIn).toBe(true); - }); - it('should handle null initialConfig gracefully', () => { render(, { idctaConfig: null, diff --git a/src/app/contexts/AccountContext/index.tsx b/src/app/contexts/AccountContext/index.tsx index 60718814dda..19a2e54ec93 100644 --- a/src/app/contexts/AccountContext/index.tsx +++ b/src/app/contexts/AccountContext/index.tsx @@ -10,8 +10,6 @@ import { AccountContextProps, IdctaConfig } from '#app/models/types/account'; import appendCtaQueryParams from '#app/lib/idcta/appendCtaQueryParams'; import { ServiceContext } from '#app/contexts/ServiceContext'; import { RequestContext } from '#app/contexts/RequestContext'; -import onClient from '#app/lib/utilities/onClient'; -import Cookie from 'js-cookie'; import { getIdctaUserOrigin } from '#app/lib/idcta/getIDCTAUserOrigin'; export const AccountContext = createContext( @@ -22,10 +20,6 @@ type AccountProviderProps = { initialConfig: IdctaConfig | null; }; -const getSignedInCookie = (cookieName = 'ckns_id') => { - return onClient() ? Cookie.get(cookieName) : false; -}; - export const AccountProvider = ({ children, initialConfig, @@ -62,15 +56,8 @@ export const AccountProvider = ({ const signOutUrl = buildAccountUrl(initialConfig?.signout_url); const forYouUrl = buildAccountUrl(initialConfig?.foryou_url); - // TODO: initialIsSignedIn is always false in test/live env due to filtered cookie header, - // it will be improved to detect signed-in status server side in the future - // Ticket: https://bbc.atlassian.net/browse/WS-2388 - const clientSignedInState = getSignedInCookie( - initialConfig?.identity?.idSignedInCookieName, - ); const isSignedIn = - isIdctaAvailable && - Boolean(initialConfig?.initialIsSignedIn || clientSignedInState); + isIdctaAvailable && Boolean(initialConfig?.initialIsSignedIn); const value = useMemo( () => ({ diff --git a/src/app/lib/idcta/getIdctaConfig/index.test.ts b/src/app/lib/idcta/getIdctaConfig/index.test.ts index 1cc52c54921..dc53b2ab329 100644 --- a/src/app/lib/idcta/getIdctaConfig/index.test.ts +++ b/src/app/lib/idcta/getIdctaConfig/index.test.ts @@ -1,3 +1,4 @@ + import getToggleDefinitions from '#app/lib/utilities/getToggleDefinition'; import isLocal from '#app/lib/utilities/isLocal'; import fetchIdctaConfig from '../fetchIdctaConfig'; @@ -102,4 +103,40 @@ describe('getIdctaConfig', () => { expect(result).toBeNull(); }); + + it('should set initialIsSignedIn to true when x-id-oidc-signedin header is "1"', async () => { + mockFetchIdctaConfig.mockResolvedValue(mockIdctaConfig); + + const result = await getIdctaConfig(mockToggles, mockService, '1'); + + + expect(result?.initialIsSignedIn).toBe(true); + }); + + it('should set initialIsSignedIn to false when x-id-oidc-signedin header is "0"', async () => { + mockFetchIdctaConfig.mockResolvedValue(mockIdctaConfig); + + const result = await getIdctaConfig(mockToggles, mockService, '0'); + + + expect(result?.initialIsSignedIn).toBe(false); + }); + + it('should set initialIsSignedIn to false when x-id-oidc-signedin header is absent', async () => { + mockFetchIdctaConfig.mockResolvedValue(mockIdctaConfig); + + const result = await getIdctaConfig(mockToggles, mockService); + + + expect(result?.initialIsSignedIn).toBe(false); + }); + + it('should set initialIsSignedIn to false when x-id-oidc-signedin header has an invalid value', async () => { + mockFetchIdctaConfig.mockResolvedValue(mockIdctaConfig); + + const result = await getIdctaConfig(mockToggles, mockService, 'invalid'); + + + expect(result?.initialIsSignedIn).toBe(false); + }); }); diff --git a/src/app/lib/idcta/getIdctaConfig/index.ts b/src/app/lib/idcta/getIdctaConfig/index.ts index 6d946a7d5f5..1e1532ee77c 100644 --- a/src/app/lib/idcta/getIdctaConfig/index.ts +++ b/src/app/lib/idcta/getIdctaConfig/index.ts @@ -3,7 +3,6 @@ import getToggleDefinitions from '#app/lib/utilities/getToggleDefinition'; import isLocal from '#app/lib/utilities/isLocal'; import { IdctaConfig } from '#app/models/types/account'; import { Toggles, Services } from '#app/models/types/global'; -import hasCookie from '#app/lib/utilities/hasCookie'; import fetchIdctaConfig from '../fetchIdctaConfig'; const logger = nodeLogger(__filename); @@ -12,13 +11,13 @@ const logger = nodeLogger(__filename); * Gets IDCTA config with toggle validation and config verification * @param toggles - Feature toggles * @param service - Service name - * @param cookieHeader - Cookie header from request + * @param signedInHeader - Value of x-id-oidc-signedin header forwarded by Belfrage * @returns Validated IdctaConfig with initialIsSignedIn or null */ export default async function getIdctaConfig( toggles: Toggles, service: Services, - cookieHeader?: string, + signedInHeader?: string, ): Promise { const toggleDefinitions = getToggleDefinitions(toggles); const { enabled: isAccountEnabled, value: accountService = '' } = @@ -47,12 +46,6 @@ export default async function getIdctaConfig( return null; } - const cookieName = config?.identity?.idSignedInCookieName; - const initialIsSignedIn = Boolean( - cookieHeader && cookieName - ? hasCookie(cookieHeader, cookieName) - : undefined, - ); - + const initialIsSignedIn = signedInHeader === '1'; return { ...config, initialIsSignedIn }; } diff --git a/src/app/models/types/account.ts b/src/app/models/types/account.ts index c7dd6d9a285..2f7665a5c4b 100644 --- a/src/app/models/types/account.ts +++ b/src/app/models/types/account.ts @@ -10,9 +10,6 @@ export type IdctaConfig = { signout_url: string; foryou_url: string; initialIsSignedIn?: boolean; - identity: { - idSignedInCookieName: string; - }; }; export type AccountContextProps = { diff --git a/ws-nextjs-app/pages/_app.page.tsx b/ws-nextjs-app/pages/_app.page.tsx index a9d32bfb211..8a0c3d16cf2 100644 --- a/ws-nextjs-app/pages/_app.page.tsx +++ b/ws-nextjs-app/pages/_app.page.tsx @@ -96,11 +96,12 @@ export default class CustomApp extends App { ? (navResult.value?.data?.items ?? null) : null; - const cookieHeader = ctx.req?.headers?.cookie; - const idctaResult = await getIdctaConfig(toggles, service, cookieHeader); + const signedInHeader = ctx.req?.headers?.['x-id-oidc-signedin'] as + | string + | undefined; + const idctaResult = await getIdctaConfig(toggles, service, signedInHeader); const pageType = (ctx.req?.headers['page-type'] as PageTypes) || derivePageType(asPath); - const serverSideExperiments = getServerExperiments({ headers: ctx.req?.headers || {}, service, From 3f94ea57cba2c1a3b8158a819e744636787391c0 Mon Sep 17 00:00:00 2001 From: Lukas Freimonas Date: Thu, 23 Apr 2026 18:14:22 +0300 Subject: [PATCH 2/2] chore: formatting --- src/app/lib/idcta/getIdctaConfig/index.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/lib/idcta/getIdctaConfig/index.test.ts b/src/app/lib/idcta/getIdctaConfig/index.test.ts index dc53b2ab329..3d2b4ecb9d9 100644 --- a/src/app/lib/idcta/getIdctaConfig/index.test.ts +++ b/src/app/lib/idcta/getIdctaConfig/index.test.ts @@ -1,4 +1,3 @@ - import getToggleDefinitions from '#app/lib/utilities/getToggleDefinition'; import isLocal from '#app/lib/utilities/isLocal'; import fetchIdctaConfig from '../fetchIdctaConfig'; @@ -109,7 +108,6 @@ describe('getIdctaConfig', () => { const result = await getIdctaConfig(mockToggles, mockService, '1'); - expect(result?.initialIsSignedIn).toBe(true); }); @@ -118,7 +116,6 @@ describe('getIdctaConfig', () => { const result = await getIdctaConfig(mockToggles, mockService, '0'); - expect(result?.initialIsSignedIn).toBe(false); }); @@ -127,7 +124,6 @@ describe('getIdctaConfig', () => { const result = await getIdctaConfig(mockToggles, mockService); - expect(result?.initialIsSignedIn).toBe(false); }); @@ -136,7 +132,6 @@ describe('getIdctaConfig', () => { const result = await getIdctaConfig(mockToggles, mockService, 'invalid'); - expect(result?.initialIsSignedIn).toBe(false); }); });