@spidy092/auth-client 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/core.js +34 -9
  2. package/package.json +1 -1
  3. package/token.js +281 -58
package/core.js CHANGED
@@ -214,31 +214,55 @@ export function resetCallbackState() {
214
214
  console.log('🔄 Callback state reset');
215
215
  }
216
216
 
217
+ // auth-client/core.js
217
218
  export async function refreshToken() {
218
219
  const { clientKey, authBaseUrl } = getConfig();
219
-
220
- console.log('🔄 Refreshing token:', {
221
- clientKey,
222
- mode: isRouterMode() ? 'ROUTER' : 'CLIENT'
220
+ const refreshTokenValue = getRefreshToken(); // ✅ Now checks both cookie & localStorage
221
+
222
+ console.log('🔄 Refreshing token:', {
223
+ clientKey,
224
+ mode: isRouterMode() ? 'ROUTER' : 'CLIENT',
225
+ hasRefreshToken: !!refreshTokenValue
223
226
  });
224
-
227
+
228
+ if (!refreshTokenValue) {
229
+ console.warn('⚠️ No refresh token available for refresh');
230
+ clearToken();
231
+ throw new Error('No refresh token available');
232
+ }
233
+
225
234
  try {
226
235
  const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, {
227
236
  method: 'POST',
228
- credentials: 'include',
237
+ credentials: 'include', // ✅ Sends cookie if available
238
+ headers: {
239
+ 'Content-Type': 'application/json',
240
+ 'X-Refresh-Token': refreshTokenValue // ✅ Also send in header as fallback
241
+ },
242
+ body: JSON.stringify({
243
+ refreshToken: refreshTokenValue // ✅ Also send in body
244
+ })
229
245
  });
230
246
 
231
247
  if (!response.ok) {
232
248
  throw new Error('Refresh failed');
233
249
  }
234
250
 
235
- const { access_token } = await response.json();
236
- // ✅ This will trigger token listeners
251
+ const { access_token, refresh_token: new_refresh_token } = await response.json();
252
+
253
+ // ✅ Update access token (triggers listeners)
237
254
  setToken(access_token);
255
+
256
+ // ✅ Update refresh token in BOTH storages if backend returned new one
257
+ if (new_refresh_token) {
258
+ setRefreshToken(new_refresh_token);
259
+ }
260
+
238
261
  console.log('✅ Token refresh successful, listeners notified');
239
262
  return access_token;
240
263
  } catch (err) {
241
- // This will trigger token listeners
264
+ console.error('❌ Token refresh failed:', err);
265
+ // ✅ Clear everything on refresh failure
242
266
  clearToken();
243
267
  clearRefreshToken();
244
268
  throw err;
@@ -246,6 +270,7 @@ export async function refreshToken() {
246
270
  }
247
271
 
248
272
 
273
+
249
274
  export async function validateCurrentSession() {
250
275
  try {
251
276
  const { authBaseUrl } = getConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spidy092/auth-client",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Scalable frontend auth SDK for centralized login using Keycloak + Auth Service.",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
package/token.js CHANGED
@@ -1,10 +1,13 @@
1
+
1
2
  // auth-client/token.js
3
+
2
4
  import { jwtDecode } from 'jwt-decode';
3
5
 
4
6
  let accessToken = null;
5
7
  const listeners = new Set();
6
8
 
7
9
  const REFRESH_COOKIE = 'account_refresh_token';
10
+ const REFRESH_STORAGE_KEY = 'refresh_token'; // localStorage key
8
11
  const COOKIE_MAX_AGE = 7 * 24 * 60 * 60; // 7 days in seconds
9
12
 
10
13
  function secureAttribute() {
@@ -17,6 +20,7 @@ function secureAttribute() {
17
20
  }
18
21
  }
19
22
 
23
+ // ========== ACCESS TOKEN (localStorage only) ==========
20
24
  function writeAccessToken(token) {
21
25
  if (!token) {
22
26
  try {
@@ -43,22 +47,95 @@ function readAccessToken() {
43
47
  }
44
48
  }
45
49
 
46
- function decode(token) {
50
+ // ========== REFRESH TOKEN (localStorage + Cookie dual storage) ==========
51
+
52
+ /**
53
+ * Store refresh token in BOTH localStorage AND cookie
54
+ * Whichever survives (cross-domain, privacy settings) will work
55
+ */
56
+ export function setRefreshToken(token) {
57
+ if (!token) {
58
+ clearRefreshToken();
59
+ return;
60
+ }
61
+
62
+ const expires = new Date(Date.now() + COOKIE_MAX_AGE * 1000);
63
+
64
+ // 1. Try to set cookie (works for same-domain, cross-subdomain)
47
65
  try {
48
- return jwtDecode(token);
66
+ document.cookie = `${REFRESH_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax${secureAttribute()}; Expires=${expires.toUTCString()}`;
67
+ console.log('✅ Refresh token stored in cookie');
49
68
  } catch (err) {
50
- return null;
69
+ console.warn('⚠️ Could not persist refresh token cookie:', err);
70
+ }
71
+
72
+ // 2. Also store in localStorage as backup (survives browser privacy settings)
73
+ try {
74
+ localStorage.setItem(REFRESH_STORAGE_KEY, token);
75
+ console.log('✅ Refresh token stored in localStorage');
76
+ } catch (err) {
77
+ console.warn('⚠️ Could not persist refresh token to localStorage:', err);
51
78
  }
52
79
  }
53
80
 
54
- function isExpired(token, bufferSeconds = 60) {
55
- if (!token) return true;
56
- const decoded = decode(token);
57
- if (!decoded?.exp) return true;
58
- const now = Date.now() / 1000;
59
- return decoded.exp < now + bufferSeconds;
81
+ /**
82
+ * Get refresh token from cookie OR localStorage (whichever works)
83
+ * Priority: Cookie > localStorage
84
+ */
85
+ export function getRefreshToken() {
86
+ // 1. Try cookie first (preferred for httpOnly scenario)
87
+ let cookieMatch = null;
88
+ try {
89
+ cookieMatch = document.cookie
90
+ ?.split('; ')
91
+ ?.find((row) => row.startsWith(`${REFRESH_COOKIE}=`));
92
+ } catch (err) {
93
+ cookieMatch = null;
94
+ }
95
+
96
+ if (cookieMatch) {
97
+ const token = decodeURIComponent(cookieMatch.split('=')[1]);
98
+ console.log('✅ Retrieved refresh token from cookie');
99
+ return token;
100
+ }
101
+
102
+ // 2. Fallback to localStorage
103
+ try {
104
+ const token = localStorage.getItem(REFRESH_STORAGE_KEY);
105
+ if (token) {
106
+ console.log('✅ Retrieved refresh token from localStorage (fallback)');
107
+ return token;
108
+ }
109
+ } catch (err) {
110
+ console.warn('⚠️ Could not read refresh token from localStorage:', err);
111
+ }
112
+
113
+ console.warn('⚠️ No refresh token found in cookie or localStorage');
114
+ return null;
60
115
  }
61
116
 
117
+ /**
118
+ * Clear refresh token from BOTH cookie AND localStorage
119
+ */
120
+ export function clearRefreshToken() {
121
+ // Clear cookie
122
+ try {
123
+ document.cookie = `${REFRESH_COOKIE}=; Path=/; SameSite=Lax${secureAttribute()}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
124
+ console.log('✅ Cleared refresh token cookie');
125
+ } catch (err) {
126
+ console.warn('⚠️ Could not clear refresh token cookie:', err);
127
+ }
128
+
129
+ // Clear localStorage
130
+ try {
131
+ localStorage.removeItem(REFRESH_STORAGE_KEY);
132
+ console.log('✅ Cleared refresh token from localStorage');
133
+ } catch (err) {
134
+ console.warn('⚠️ Could not clear refresh token from localStorage:', err);
135
+ }
136
+ }
137
+
138
+ // ========== ACCESS TOKEN MANAGEMENT ==========
62
139
  export function setToken(token) {
63
140
  const previousToken = accessToken;
64
141
  accessToken = token || null;
@@ -102,61 +179,21 @@ export function clearToken() {
102
179
  });
103
180
  }
104
181
 
105
- export function setRefreshToken(token) {
106
- if (!token) {
107
- clearRefreshToken();
108
- return;
109
- }
110
-
111
- const expires = new Date(Date.now() + COOKIE_MAX_AGE * 1000);
112
- try {
113
- document.cookie = `${REFRESH_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Strict${secureAttribute()}; Expires=${expires.toUTCString()}`;
114
- } catch (err) {
115
- console.warn('Could not persist refresh token cookie:', err);
116
- }
117
-
118
- try {
119
- sessionStorage.setItem(REFRESH_COOKIE, token);
120
- } catch (err) {
121
- console.warn('Could not persist refresh token to sessionStorage:', err);
122
- }
123
- }
124
-
125
- export function getRefreshToken() {
126
- // Prefer cookie to align with server expectations
127
- let cookieMatch = null;
128
-
129
- try {
130
- cookieMatch = document.cookie
131
- ?.split('; ')
132
- ?.find((row) => row.startsWith(`${REFRESH_COOKIE}=`));
133
- } catch (err) {
134
- cookieMatch = null;
135
- }
136
-
137
- if (cookieMatch) {
138
- return decodeURIComponent(cookieMatch.split('=')[1]);
139
- }
140
-
182
+ // ========== HELPER FUNCTIONS ==========
183
+ function decode(token) {
141
184
  try {
142
- return sessionStorage.getItem(REFRESH_COOKIE);
185
+ return jwtDecode(token);
143
186
  } catch (err) {
144
- console.warn('Could not read refresh token from sessionStorage:', err);
145
187
  return null;
146
188
  }
147
189
  }
148
190
 
149
- export function clearRefreshToken() {
150
- try {
151
- document.cookie = `${REFRESH_COOKIE}=; Path=/; SameSite=Strict${secureAttribute()}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
152
- } catch (err) {
153
- console.warn('Could not clear refresh token cookie:', err);
154
- }
155
- try {
156
- sessionStorage.removeItem(REFRESH_COOKIE);
157
- } catch (err) {
158
- console.warn('Could not clear refresh token from sessionStorage:', err);
159
- }
191
+ function isExpired(token, bufferSeconds = 60) {
192
+ if (!token) return true;
193
+ const decoded = decode(token);
194
+ if (!decoded?.exp) return true;
195
+ const now = Date.now() / 1000;
196
+ return decoded.exp < now + bufferSeconds;
160
197
  }
161
198
 
162
199
  export function addTokenListener(listener) {
@@ -182,3 +219,189 @@ export function isAuthenticated() {
182
219
  return !!token && !isExpired(token, 15);
183
220
  }
184
221
 
222
+
223
+
224
+ // // auth-client/token.js
225
+ // import { jwtDecode } from 'jwt-decode';
226
+
227
+ // let accessToken = null;
228
+ // const listeners = new Set();
229
+
230
+ // const REFRESH_COOKIE = 'account_refresh_token';
231
+ // const COOKIE_MAX_AGE = 7 * 24 * 60 * 60; // 7 days in seconds
232
+
233
+ // function secureAttribute() {
234
+ // try {
235
+ // return typeof window !== 'undefined' && window.location?.protocol === 'https:'
236
+ // ? '; Secure'
237
+ // : '';
238
+ // } catch (err) {
239
+ // return '';
240
+ // }
241
+ // }
242
+
243
+ // function writeAccessToken(token) {
244
+ // if (!token) {
245
+ // try {
246
+ // localStorage.removeItem('authToken');
247
+ // } catch (err) {
248
+ // console.warn('Could not clear token from localStorage:', err);
249
+ // }
250
+ // return;
251
+ // }
252
+
253
+ // try {
254
+ // localStorage.setItem('authToken', token);
255
+ // } catch (err) {
256
+ // console.warn('Could not persist token to localStorage:', err);
257
+ // }
258
+ // }
259
+
260
+ // function readAccessToken() {
261
+ // try {
262
+ // return localStorage.getItem('authToken');
263
+ // } catch (err) {
264
+ // console.warn('Could not read token from localStorage:', err);
265
+ // return null;
266
+ // }
267
+ // }
268
+
269
+ // function decode(token) {
270
+ // try {
271
+ // return jwtDecode(token);
272
+ // } catch (err) {
273
+ // return null;
274
+ // }
275
+ // }
276
+
277
+ // function isExpired(token, bufferSeconds = 60) {
278
+ // if (!token) return true;
279
+ // const decoded = decode(token);
280
+ // if (!decoded?.exp) return true;
281
+ // const now = Date.now() / 1000;
282
+ // return decoded.exp < now + bufferSeconds;
283
+ // }
284
+
285
+ // export function setToken(token) {
286
+ // const previousToken = accessToken;
287
+ // accessToken = token || null;
288
+ // writeAccessToken(accessToken);
289
+
290
+ // if (previousToken !== accessToken) {
291
+ // listeners.forEach((listener) => {
292
+ // try {
293
+ // listener(accessToken, previousToken);
294
+ // } catch (err) {
295
+ // console.warn('Token listener error:', err);
296
+ // }
297
+ // });
298
+ // }
299
+ // }
300
+
301
+ // export function getToken() {
302
+ // if (accessToken) return accessToken;
303
+ // accessToken = readAccessToken();
304
+ // return accessToken;
305
+ // }
306
+
307
+ // export function clearToken() {
308
+ // if (!accessToken) {
309
+ // writeAccessToken(null);
310
+ // clearRefreshToken();
311
+ // return;
312
+ // }
313
+
314
+ // const previousToken = accessToken;
315
+ // accessToken = null;
316
+ // writeAccessToken(null);
317
+ // clearRefreshToken();
318
+
319
+ // listeners.forEach((listener) => {
320
+ // try {
321
+ // listener(null, previousToken);
322
+ // } catch (err) {
323
+ // console.warn('Token listener error:', err);
324
+ // }
325
+ // });
326
+ // }
327
+
328
+ // export function setRefreshToken(token) {
329
+ // if (!token) {
330
+ // clearRefreshToken();
331
+ // return;
332
+ // }
333
+
334
+ // const expires = new Date(Date.now() + COOKIE_MAX_AGE * 1000);
335
+ // try {
336
+ // document.cookie = `${REFRESH_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Strict${secureAttribute()}; Expires=${expires.toUTCString()}`;
337
+ // } catch (err) {
338
+ // console.warn('Could not persist refresh token cookie:', err);
339
+ // }
340
+
341
+ // try {
342
+ // sessionStorage.setItem(REFRESH_COOKIE, token);
343
+ // } catch (err) {
344
+ // console.warn('Could not persist refresh token to sessionStorage:', err);
345
+ // }
346
+ // }
347
+
348
+ // export function getRefreshToken() {
349
+ // // Prefer cookie to align with server expectations
350
+ // let cookieMatch = null;
351
+
352
+ // try {
353
+ // cookieMatch = document.cookie
354
+ // ?.split('; ')
355
+ // ?.find((row) => row.startsWith(`${REFRESH_COOKIE}=`));
356
+ // } catch (err) {
357
+ // cookieMatch = null;
358
+ // }
359
+
360
+ // if (cookieMatch) {
361
+ // return decodeURIComponent(cookieMatch.split('=')[1]);
362
+ // }
363
+
364
+ // try {
365
+ // return sessionStorage.getItem(REFRESH_COOKIE);
366
+ // } catch (err) {
367
+ // console.warn('Could not read refresh token from sessionStorage:', err);
368
+ // return null;
369
+ // }
370
+ // }
371
+
372
+ // export function clearRefreshToken() {
373
+ // try {
374
+ // document.cookie = `${REFRESH_COOKIE}=; Path=/; SameSite=Strict${secureAttribute()}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
375
+ // } catch (err) {
376
+ // console.warn('Could not clear refresh token cookie:', err);
377
+ // }
378
+ // try {
379
+ // sessionStorage.removeItem(REFRESH_COOKIE);
380
+ // } catch (err) {
381
+ // console.warn('Could not clear refresh token from sessionStorage:', err);
382
+ // }
383
+ // }
384
+
385
+ // export function addTokenListener(listener) {
386
+ // if (typeof listener !== 'function') {
387
+ // throw new Error('Token listener must be a function');
388
+ // }
389
+ // listeners.add(listener);
390
+ // return () => {
391
+ // listeners.delete(listener);
392
+ // };
393
+ // }
394
+
395
+ // export function removeTokenListener(listener) {
396
+ // listeners.delete(listener);
397
+ // }
398
+
399
+ // export function getListenerCount() {
400
+ // return listeners.size;
401
+ // }
402
+
403
+ // export function isAuthenticated() {
404
+ // const token = getToken();
405
+ // return !!token && !isExpired(token, 15);
406
+ // }
407
+