@spidy092/auth-client 2.1.1 → 2.1.3

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 +29 -5
  2. package/package.json +1 -1
  3. package/token.js +50 -28
package/core.js CHANGED
@@ -233,15 +233,31 @@ export async function refreshToken() {
233
233
  refreshInProgress = true;
234
234
  refreshPromise = (async () => {
235
235
  try {
236
+ // Get stored refresh token (for HTTP development)
237
+ const storedRefreshToken = getRefreshToken();
238
+
236
239
  console.log('🔄 Refreshing token:', {
237
240
  clientKey,
238
- mode: isRouterMode() ? 'ROUTER' : 'CLIENT'
241
+ mode: isRouterMode() ? 'ROUTER' : 'CLIENT',
242
+ hasStoredRefreshToken: !!storedRefreshToken
239
243
  });
240
244
 
241
- const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, {
245
+ // Build request options - send refresh token in body and header for HTTP dev
246
+ const requestOptions = {
242
247
  method: 'POST',
243
- credentials: 'include', // ✅ Include httpOnly cookies
244
- });
248
+ credentials: 'include', // ✅ Include httpOnly cookies (for HTTPS)
249
+ headers: {
250
+ 'Content-Type': 'application/json'
251
+ }
252
+ };
253
+
254
+ // For HTTP development, send refresh token in body and header
255
+ if (storedRefreshToken) {
256
+ requestOptions.headers['X-Refresh-Token'] = storedRefreshToken;
257
+ requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
258
+ }
259
+
260
+ const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
245
261
 
246
262
  if (!response.ok) {
247
263
  const errorText = await response.text();
@@ -249,7 +265,8 @@ export async function refreshToken() {
249
265
  throw new Error(`Refresh failed: ${response.status}`);
250
266
  }
251
267
 
252
- const { access_token } = await response.json();
268
+ const data = await response.json();
269
+ const { access_token, refresh_token: new_refresh_token } = data;
253
270
 
254
271
  if (!access_token) {
255
272
  throw new Error('No access token in refresh response');
@@ -257,6 +274,13 @@ export async function refreshToken() {
257
274
 
258
275
  // ✅ This will trigger token listeners
259
276
  setToken(access_token);
277
+
278
+ // ✅ Store new refresh token if provided (token rotation)
279
+ if (new_refresh_token) {
280
+ setRefreshToken(new_refresh_token);
281
+ console.log('🔄 New refresh token stored from rotation');
282
+ }
283
+
260
284
  console.log('✅ Token refresh successful, listeners notified');
261
285
  return access_token;
262
286
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spidy092/auth-client",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
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
@@ -145,55 +145,77 @@ export function clearToken() {
145
145
  });
146
146
  }
147
147
 
148
- export function setRefreshToken(token) {
149
- // SECURITY: Refresh tokens should ONLY be in httpOnly cookies set by server
150
- // This function should NOT be used - refresh tokens must come from server cookies
151
- // Keeping for backwards compatibility but logging warning
148
+ // ========== REFRESH TOKEN STORAGE FOR HTTP DEVELOPMENT ==========
149
+ // In production (HTTPS), refresh tokens should ONLY be in httpOnly cookies set by server
150
+ // For HTTP development (cross-origin cookies don't work), we store in localStorage
151
+ const REFRESH_TOKEN_KEY = 'auth_refresh_token';
152
152
 
153
+ function isHttpDevelopment() {
154
+ try {
155
+ return typeof window !== 'undefined' &&
156
+ window.location?.protocol === 'http:';
157
+ } catch (err) {
158
+ return false;
159
+ }
160
+ }
161
+
162
+ export function setRefreshToken(token) {
153
163
  if (!token) {
154
164
  clearRefreshToken();
155
165
  return;
156
166
  }
157
167
 
158
- console.warn('⚠️ SECURITY WARNING: setRefreshToken() called - refresh tokens should only be in httpOnly cookies!');
159
- console.warn('⚠️ Refresh tokens set client-side are insecure and should be removed');
160
-
161
- // ❌ DO NOT store refresh token in client-side storage
162
- // The server sets it in httpOnly cookie, which is the only secure way
163
-
164
- // Only clear any existing client-side storage
165
- try {
166
- sessionStorage.removeItem(REFRESH_COOKIE);
167
- } catch (err) {
168
- // Ignore
168
+ // For HTTP development, store in localStorage (since httpOnly cookies don't work cross-origin)
169
+ if (isHttpDevelopment()) {
170
+ try {
171
+ localStorage.setItem(REFRESH_TOKEN_KEY, token);
172
+ console.log('📦 Refresh token stored in localStorage (HTTP dev mode)');
173
+ } catch (err) {
174
+ console.warn('Could not store refresh token:', err);
175
+ }
176
+ } else {
177
+ // In production (HTTPS), refresh token should be in httpOnly cookie only
178
+ console.log('🔒 Refresh token managed by server httpOnly cookie (production mode)');
169
179
  }
170
180
  }
171
181
 
172
182
  export function getRefreshToken() {
173
- // Refresh tokens are stored in httpOnly cookies by the server
174
- // We cannot read httpOnly cookies from JavaScript - they're only sent with requests
175
- // This function is kept for backwards compatibility but returns null
176
- // The refresh endpoint will automatically use the httpOnly cookie via credentials: 'include'
177
-
178
- // DO NOT try to read refresh token from client-side storage
179
- // httpOnly cookies are not accessible via document.cookie
180
-
181
- console.warn('⚠️ getRefreshToken() called - refresh tokens are in httpOnly cookies and cannot be read from JavaScript');
182
- console.warn('⚠️ The refresh endpoint will automatically use the httpOnly cookie via credentials: "include"');
183
-
184
- return null; // Refresh token is in httpOnly cookie, not accessible to JavaScript
183
+ // For HTTP development, read from localStorage
184
+ if (isHttpDevelopment()) {
185
+ try {
186
+ const token = localStorage.getItem(REFRESH_TOKEN_KEY);
187
+ return token;
188
+ } catch (err) {
189
+ console.warn('Could not read refresh token:', err);
190
+ return null;
191
+ }
192
+ }
193
+
194
+ // In production, refresh token is in httpOnly cookie (not accessible via JS)
195
+ // The refresh endpoint uses credentials: 'include' to send the cookie
196
+ return null;
185
197
  }
186
198
 
187
199
  export function clearRefreshToken() {
200
+ // Clear localStorage (for HTTP dev)
201
+ try {
202
+ localStorage.removeItem(REFRESH_TOKEN_KEY);
203
+ } catch (err) {
204
+ // Ignore
205
+ }
206
+
207
+ // Clear cookie (for production)
188
208
  try {
189
209
  document.cookie = `${REFRESH_COOKIE}=; Path=/; SameSite=Strict${secureAttribute()}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
190
210
  } catch (err) {
191
211
  console.warn('Could not clear refresh token cookie:', err);
192
212
  }
213
+
214
+ // Clear sessionStorage
193
215
  try {
194
216
  sessionStorage.removeItem(REFRESH_COOKIE);
195
217
  } catch (err) {
196
- console.warn('Could not clear refresh token from sessionStorage:', err);
218
+ // Ignore
197
219
  }
198
220
  }
199
221