@spidy092/auth-client 2.1.1 → 2.1.5

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 +38 -10
  2. package/package.json +1 -1
  3. package/token.js +50 -28
package/core.js CHANGED
@@ -189,18 +189,22 @@ export function handleCallback() {
189
189
  if (accessToken) {
190
190
  setToken(accessToken);
191
191
 
192
- // ✅ Refresh token should NOT be in URL - it's in httpOnly cookie
193
- // If refresh token is in URL, log warning but don't store it client-side
192
+ // ✅ For HTTP development, store refresh token from URL
193
+ // In HTTPS production, refresh token is in httpOnly cookie (more secure)
194
194
  const refreshTokenInUrl = params.get('refresh_token');
195
195
  if (refreshTokenInUrl) {
196
- console.warn('⚠️ SECURITY WARNING: Refresh token found in URL - this should not happen!');
197
- // DO NOT store refresh token from URL - it should only be in httpOnly cookie
196
+ const isHttpDev = typeof window !== 'undefined' && window.location?.protocol === 'http:';
197
+ if (isHttpDev) {
198
+ console.log('📦 HTTP dev mode: Storing refresh token from callback URL');
199
+ setRefreshToken(refreshTokenInUrl);
200
+ } else {
201
+ console.log('🔒 HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)');
202
+ }
198
203
  }
199
204
 
200
205
  const url = new URL(window.location);
201
206
  url.searchParams.delete('access_token');
202
207
  url.searchParams.delete('refresh_token');
203
- url.searchParams.delete('refresh_token');
204
208
  url.searchParams.delete('state');
205
209
  url.searchParams.delete('error');
206
210
  url.searchParams.delete('error_description');
@@ -233,15 +237,31 @@ export async function refreshToken() {
233
237
  refreshInProgress = true;
234
238
  refreshPromise = (async () => {
235
239
  try {
240
+ // Get stored refresh token (for HTTP development)
241
+ const storedRefreshToken = getRefreshToken();
242
+
236
243
  console.log('🔄 Refreshing token:', {
237
244
  clientKey,
238
- mode: isRouterMode() ? 'ROUTER' : 'CLIENT'
245
+ mode: isRouterMode() ? 'ROUTER' : 'CLIENT',
246
+ hasStoredRefreshToken: !!storedRefreshToken
239
247
  });
240
248
 
241
- const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, {
249
+ // Build request options - send refresh token in body and header for HTTP dev
250
+ const requestOptions = {
242
251
  method: 'POST',
243
- credentials: 'include', // ✅ Include httpOnly cookies
244
- });
252
+ credentials: 'include', // ✅ Include httpOnly cookies (for HTTPS)
253
+ headers: {
254
+ 'Content-Type': 'application/json'
255
+ }
256
+ };
257
+
258
+ // For HTTP development, send refresh token in body and header
259
+ if (storedRefreshToken) {
260
+ requestOptions.headers['X-Refresh-Token'] = storedRefreshToken;
261
+ requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
262
+ }
263
+
264
+ const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
245
265
 
246
266
  if (!response.ok) {
247
267
  const errorText = await response.text();
@@ -249,7 +269,8 @@ export async function refreshToken() {
249
269
  throw new Error(`Refresh failed: ${response.status}`);
250
270
  }
251
271
 
252
- const { access_token } = await response.json();
272
+ const data = await response.json();
273
+ const { access_token, refresh_token: new_refresh_token } = data;
253
274
 
254
275
  if (!access_token) {
255
276
  throw new Error('No access token in refresh response');
@@ -257,6 +278,13 @@ export async function refreshToken() {
257
278
 
258
279
  // ✅ This will trigger token listeners
259
280
  setToken(access_token);
281
+
282
+ // ✅ Store new refresh token if provided (token rotation)
283
+ if (new_refresh_token) {
284
+ setRefreshToken(new_refresh_token);
285
+ console.log('🔄 New refresh token stored from rotation');
286
+ }
287
+
260
288
  console.log('✅ Token refresh successful, listeners notified');
261
289
  return access_token;
262
290
  } 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.5",
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