@@ -35,56 +35,19 @@ export function getRefreshTokenCookieTTL(refreshTokenExpiresInSLASValue, isGuest
3535}
3636
3737/**
38- * Decodes the SLAS access token JWT, extracts claims, and sets non-HttpOnly metadata cookies
39- * (expires-at, dnt, uido) so the client can read them. Same field extraction as
38+ * Decodes the SLAS access token JWT and extracts claims. Same field extraction as
4039 * commerce-sdk-react parseSlasJWT.
41- *
42- * Returns {isGuest} for the caller to determine the refresh token cookie name.
4340 * @private
4441 */
45- function setTokenClaimCookies ( res , siteId , accessToken , expiresInSeconds ) {
42+ function getTokenClaims ( accessToken ) {
4643 let payload
4744 try {
4845 payload = jwtDecode ( accessToken )
4946 } catch ( error ) {
5047 throw new Error ( `Failed to decode access token JWT: ${ error . message || error } . ` )
5148 }
5249
53- const accessExpires = new Date ( Date . now ( ) + expiresInSeconds * 1000 )
54-
55- // Expiry timestamp — use JWT iat when available (non-HttpOnly so client can check expiry)
56- let expiresAt = Math . floor ( Date . now ( ) / 1000 ) + expiresInSeconds
57- if ( typeof payload . iat === 'number' ) {
58- expiresAt = payload . iat + expiresInSeconds
59- }
60- res . append (
61- SET_COOKIE ,
62- cookieAsString ( {
63- name : `cc-at-expires-at_${ siteId } ` ,
64- value : String ( expiresAt ) ,
65- path : '/' ,
66- secure : true ,
67- sameSite : 'lax' ,
68- httpOnly : false ,
69- expires : accessExpires
70- } )
71- )
72-
73- // Do-not-track flag from JWT (non-HttpOnly so client can read it)
74- if ( payload . dnt !== undefined ) {
75- res . append (
76- SET_COOKIE ,
77- cookieAsString ( {
78- name : `cc-at-dnt_${ siteId } ` ,
79- value : String ( payload . dnt ) ,
80- path : '/' ,
81- secure : true ,
82- sameSite : 'lax' ,
83- httpOnly : false ,
84- expires : accessExpires
85- } )
86- )
87- }
50+ const accessExpires = new Date ( payload . exp * 1000 )
8851
8952 // Extract isGuest and uido from JWT isb claim
9053 let isGuest = true
@@ -96,66 +59,7 @@ function setTokenClaimCookies(res, siteId, accessToken, expiresInSeconds) {
9659 if ( uidoPart ) uido = uidoPart
9760 }
9861
99- // uido: IDP origin (e.g. "slas", "ecom"); non-HttpOnly so client can read for useCustomerType/isExternal
100- if ( uido ) {
101- res . append (
102- SET_COOKIE ,
103- cookieAsString ( {
104- name : `uido_${ siteId } ` ,
105- value : uido ,
106- path : '/' ,
107- secure : true ,
108- sameSite : 'lax' ,
109- httpOnly : false ,
110- expires : accessExpires
111- } )
112- )
113- }
114-
115- return { isGuest}
116- }
117-
118- /**
119- * Sets the IDP access token as an HttpOnly cookie.
120- * @private
121- */
122- function setIdpAccessTokenCookie ( res , siteId , idpAccessToken , expiresInSeconds ) {
123- const idpExpires = new Date ( Date . now ( ) + expiresInSeconds * 1000 )
124- res . append (
125- SET_COOKIE ,
126- cookieAsString ( {
127- name : `idp_access_token_${ siteId } ` ,
128- value : idpAccessToken ,
129- path : '/' ,
130- secure : true ,
131- sameSite : 'lax' ,
132- httpOnly : true ,
133- expires : idpExpires
134- } )
135- )
136- }
137-
138- /**
139- * Sets the refresh token as an HttpOnly cookie. Cookie name depends on guest vs registered user.
140- * @private
141- */
142- function setRefreshTokenCookie ( res , siteId , refreshToken , refreshTokenExpiresIn , isGuest ) {
143- const refreshTTL = getRefreshTokenCookieTTL ( refreshTokenExpiresIn , isGuest )
144- const refreshExpires = new Date ( Date . now ( ) + refreshTTL * 1000 )
145- const refreshCookieName = isGuest ? `cc-nx-g_${ siteId } ` : `cc-nx_${ siteId } `
146-
147- res . append (
148- SET_COOKIE ,
149- cookieAsString ( {
150- name : refreshCookieName ,
151- value : refreshToken ,
152- path : '/' ,
153- secure : true ,
154- sameSite : 'lax' ,
155- httpOnly : true ,
156- expires : refreshExpires
157- } )
158- )
62+ return { accessExpires, expiresAt : payload . exp , dnt : payload . dnt , isGuest, uido}
15963}
16064
16165/**
@@ -180,13 +84,20 @@ export function applyHttpOnlySessionCookies(responseBuffer, proxyRes, req, res,
18084 }
18185
18286 const site = siteId . trim ( )
183- const expiresInSeconds = typeof parsed . expires_in === 'number' ? parsed . expires_in : 1800
18487
185- // Decode JWT, set metadata cookies (expires-at, dnt, uido), get isGuest
88+ // Decode JWT and extract claims
18689 let isGuest = true
18790 if ( parsed . access_token ) {
91+ const {
92+ accessExpires,
93+ expiresAt,
94+ dnt,
95+ uido,
96+ isGuest : guest
97+ } = getTokenClaims ( parsed . access_token )
98+ isGuest = guest
99+
188100 // Access token (HttpOnly)
189- const accessExpires = new Date ( Date . now ( ) + expiresInSeconds * 1000 )
190101 res . append (
191102 SET_COOKIE ,
192103 cookieAsString ( {
@@ -200,23 +111,91 @@ export function applyHttpOnlySessionCookies(responseBuffer, proxyRes, req, res,
200111 } )
201112 )
202113
203- const claims = setTokenClaimCookies ( res , site , parsed . access_token , expiresInSeconds )
204- isGuest = claims . isGuest
205- }
114+ // Expiry timestamp from JWT exp claim (non-HttpOnly so client can check expiry)
115+ res . append (
116+ SET_COOKIE ,
117+ cookieAsString ( {
118+ name : `cc-at-expires_${ site } ` ,
119+ value : String ( expiresAt ) ,
120+ path : '/' ,
121+ secure : true ,
122+ sameSite : 'lax' ,
123+ httpOnly : false ,
124+ expires : accessExpires
125+ } )
126+ )
206127
207- // IDP access token
208- if ( parsed . idp_access_token ) {
209- setIdpAccessTokenCookie ( res , site , parsed . idp_access_token , expiresInSeconds )
128+ // Do-not-track flag from JWT (non-HttpOnly so client can read it)
129+ if ( dnt !== undefined ) {
130+ res . append (
131+ SET_COOKIE ,
132+ cookieAsString ( {
133+ name : `cc-at-dnt_${ site } ` ,
134+ value : String ( dnt ) ,
135+ path : '/' ,
136+ secure : true ,
137+ sameSite : 'lax' ,
138+ httpOnly : false ,
139+ expires : accessExpires
140+ } )
141+ )
142+ }
143+
144+ // uido: IDP origin (e.g. "slas", "ecom"); non-HttpOnly so client can read for useCustomerType/isExternal
145+ if ( uido ) {
146+ res . append (
147+ SET_COOKIE ,
148+ cookieAsString ( {
149+ name : `uido_${ site } ` ,
150+ value : uido ,
151+ path : '/' ,
152+ secure : true ,
153+ sameSite : 'lax' ,
154+ httpOnly : false ,
155+ expires : accessExpires
156+ } )
157+ )
158+ }
159+
160+ // IDP access token (HttpOnly)
161+ if ( parsed . idp_access_token ) {
162+ res . append (
163+ SET_COOKIE ,
164+ cookieAsString ( {
165+ name : `idp_access_token_${ site } ` ,
166+ value : parsed . idp_access_token ,
167+ path : '/' ,
168+ secure : true ,
169+ sameSite : 'lax' ,
170+ httpOnly : true ,
171+ expires : accessExpires
172+ } )
173+ )
174+ }
210175 }
211176
212- // Refresh token
177+ // Refresh token (HttpOnly) — uses its own TTL, independent of access token expiry
213178 if ( parsed . refresh_token ) {
214- setRefreshTokenCookie (
215- res ,
216- site ,
217- parsed . refresh_token ,
179+ const commerceAPI = options . mobify ?. app ?. commerceAPI || { }
180+ const refreshTTL = getRefreshTokenCookieTTL (
218181 parsed . refresh_token_expires_in ,
219- isGuest
182+ isGuest ,
183+ commerceAPI
184+ )
185+ const refreshExpires = new Date ( Date . now ( ) + refreshTTL * 1000 )
186+ const refreshCookieName = isGuest ? `cc-nx-g_${ site } ` : `cc-nx_${ site } `
187+
188+ res . append (
189+ SET_COOKIE ,
190+ cookieAsString ( {
191+ name : refreshCookieName ,
192+ value : parsed . refresh_token ,
193+ path : '/' ,
194+ secure : true ,
195+ sameSite : 'lax' ,
196+ httpOnly : true ,
197+ expires : refreshExpires
198+ } )
220199 )
221200 }
222201
0 commit comments