wu981526092 commited on
Commit
5a2b931
·
1 Parent(s): 3c7f84c

Implement HF Spaces best practice: new tab login flow

Browse files

🔧 HF Spaces OAuth Best Practice:
- Login button now opens in new tab (target='_blank')
- Added success page that notifies parent window
- Implemented message passing between tabs
- Added periodic auth status checking during login
- Beautiful success page with auto-close functionality

📚 Based on HF documentation:
'You should use target=_blank on the button to open the
sign-in page in a new tab, unless you run the space outside
its iframe. Otherwise, you might encounter issues with cookies.'

This should resolve the session cookie issues in iframe environment.

backend/routers/auth.py CHANGED
@@ -253,8 +253,81 @@ async def oauth_callback(request: Request, code: str, state: str):
253
 
254
  logger.info(f"User logged in: {user_info.get('name')} ({user_info.get('login')})")
255
 
256
- # Redirect to main application
257
- return RedirectResponse(url="/", status_code=302)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
 
260
  @router.get("/logout")
 
253
 
254
  logger.info(f"User logged in: {user_info.get('name')} ({user_info.get('login')})")
255
 
256
+ # For HF Spaces, show a success page that notifies parent window and closes
257
+ if is_huggingface_space():
258
+ success_html = """
259
+ <!DOCTYPE html>
260
+ <html>
261
+ <head>
262
+ <title>Login Successful</title>
263
+ <style>
264
+ body {
265
+ font-family: Arial, sans-serif;
266
+ text-align: center;
267
+ padding: 50px;
268
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
269
+ color: white;
270
+ margin: 0;
271
+ min-height: 100vh;
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: center;
275
+ flex-direction: column;
276
+ }
277
+ .success-card {
278
+ background: rgba(255,255,255,0.1);
279
+ padding: 40px;
280
+ border-radius: 20px;
281
+ backdrop-filter: blur(10px);
282
+ border: 1px solid rgba(255,255,255,0.2);
283
+ }
284
+ .checkmark {
285
+ font-size: 60px;
286
+ margin-bottom: 20px;
287
+ color: #4CAF50;
288
+ }
289
+ </style>
290
+ </head>
291
+ <body>
292
+ <div class="success-card">
293
+ <div class="checkmark">✅</div>
294
+ <h1>Login Successful!</h1>
295
+ <p>You have successfully logged in with your Hugging Face account.</p>
296
+ <p>This tab will close automatically...</p>
297
+ </div>
298
+
299
+ <script>
300
+ // Try to send message to parent window
301
+ try {
302
+ if (window.opener) {
303
+ window.opener.postMessage({
304
+ type: 'HF_LOGIN_SUCCESS',
305
+ timestamp: Date.now()
306
+ }, window.location.origin);
307
+ console.log('✅ Sent login success message to parent window');
308
+ }
309
+ } catch (e) {
310
+ console.log('Could not send message to parent:', e);
311
+ }
312
+
313
+ // Close this tab after a short delay
314
+ setTimeout(() => {
315
+ try {
316
+ window.close();
317
+ } catch (e) {
318
+ console.log('Could not close window:', e);
319
+ // Redirect to main app if we can't close
320
+ window.location.href = '/';
321
+ }
322
+ }, 2000);
323
+ </script>
324
+ </body>
325
+ </html>
326
+ """
327
+ return HTMLResponse(content=success_html)
328
+ else:
329
+ # For local development, redirect normally
330
+ return RedirectResponse(url="/", status_code=302)
331
 
332
 
333
  @router.get("/logout")
frontend/src/components/auth/LoginModal.tsx CHANGED
@@ -63,19 +63,24 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
63
 
64
  {/* CTA Section */}
65
  <div className="space-y-4">
66
- <button
67
- onClick={handleLogin}
68
- className="w-full bg-primary hover:bg-primary/90 text-primary-foreground font-semibold py-3 px-6 rounded-lg transition-colors flex items-center justify-center gap-2"
69
- >
70
- {isHFSpaces ? (
71
- <>
72
- Login with Hugging Face
73
- <ExternalLink className="w-4 h-4" />
74
- </>
75
- ) : (
76
- "Continue to Platform"
77
- )}
78
- </button>
 
 
 
 
 
79
 
80
  <div className="bg-muted/30 p-4 rounded-lg">
81
  <p className="text-sm text-muted-foreground">
 
63
 
64
  {/* CTA Section */}
65
  <div className="space-y-4">
66
+ {isHFSpaces ? (
67
+ <a
68
+ href="/auth/login"
69
+ target="_blank"
70
+ rel="noopener noreferrer"
71
+ className="w-full bg-primary hover:bg-primary/90 text-primary-foreground font-semibold py-3 px-6 rounded-lg transition-colors flex items-center justify-center gap-2"
72
+ >
73
+ Login with Hugging Face
74
+ <ExternalLink className="w-4 h-4" />
75
+ </a>
76
+ ) : (
77
+ <button
78
+ onClick={handleLogin}
79
+ className="w-full bg-primary hover:bg-primary/90 text-primary-foreground font-semibold py-3 px-6 rounded-lg transition-colors flex items-center justify-center gap-2"
80
+ >
81
+ Continue to Platform
82
+ </button>
83
+ )}
84
 
85
  <div className="bg-muted/30 p-4 rounded-lg">
86
  <p className="text-sm text-muted-foreground">
frontend/src/context/AuthContext.tsx CHANGED
@@ -155,12 +155,37 @@ export function AuthProvider({ children }: AuthProviderProps) {
155
  }
156
  };
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  window.addEventListener("auth-required", handleAuthRequired);
 
159
 
160
  return () => {
161
  window.removeEventListener("auth-required", handleAuthRequired);
 
 
162
  };
163
- }, []);
164
 
165
  const value: AuthContextType = {
166
  user,
 
155
  }
156
  };
157
 
158
+ // Listen for messages from login popup/tab
159
+ const handleMessage = (event: MessageEvent) => {
160
+ if (event.origin !== window.location.origin) {
161
+ return; // Only accept messages from same origin
162
+ }
163
+
164
+ if (event.data.type === "HF_LOGIN_SUCCESS") {
165
+ console.log("✅ Login success message received from popup");
166
+ setShowLoginModal(false);
167
+ // Refresh auth status after successful login
168
+ setTimeout(() => checkAuthStatus(), 1000);
169
+ }
170
+ };
171
+
172
+ // Periodically check auth status while login modal is open (for new tab flow)
173
+ const interval = showLoginModal
174
+ ? setInterval(() => {
175
+ console.log("🔄 Periodic auth check while login modal is open");
176
+ checkAuthStatus();
177
+ }, 2000)
178
+ : null;
179
+
180
  window.addEventListener("auth-required", handleAuthRequired);
181
+ window.addEventListener("message", handleMessage);
182
 
183
  return () => {
184
  window.removeEventListener("auth-required", handleAuthRequired);
185
+ window.removeEventListener("message", handleMessage);
186
+ if (interval) clearInterval(interval);
187
  };
188
+ }, [showLoginModal]);
189
 
190
  const value: AuthContextType = {
191
  user,