Spaces:
Running
Fix: Add backend OAuth config API for client_id
Browse filesπ Problem:
- HF OAuth requires client_id parameter in authorization URL
- Frontend can't directly access server environment variables
- Getting 400 'client_id is required' error
β
Solution:
- Created /api/auth/oauth-config endpoint in backend
- Returns public OAuth config (client_id, scopes, provider_url)
- Frontend fetches config before OAuth redirect
- Secure: Only returns public info, never client_secret
π§ Technical Changes:
Backend:
- Added get_oauth_config_for_frontend() route
- Returns OAuth config from environment variables
- Includes safety checks for auth_enabled status
Frontend:
- Fetch OAuth config from backend API before login
- Use backend-provided client_id in OAuth URL
- Enhanced error handling for config fetch failures
- Debug logging for OAuth config validation
π― Expected Result:
- OAuth URL now includes proper client_id parameter
- Should successfully redirect to HF OAuth page
- No more '400 client_id required' errors
- backend/routers/auth.py +20 -0
- frontend/src/context/AuthContext.tsx +42 -24
|
@@ -49,6 +49,26 @@ async def auth_status(request: Request):
|
|
| 49 |
}
|
| 50 |
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
@router.get("/login")
|
| 53 |
async def login(request: Request):
|
| 54 |
"""
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
|
| 52 |
+
@router.get("/oauth-config")
|
| 53 |
+
async def get_oauth_config_for_frontend():
|
| 54 |
+
"""Get OAuth configuration for frontend use (public information only)."""
|
| 55 |
+
if not should_enable_auth():
|
| 56 |
+
return {"oauth_enabled": False}
|
| 57 |
+
|
| 58 |
+
config = get_oauth_config()
|
| 59 |
+
if not config:
|
| 60 |
+
return {"oauth_enabled": False, "error": "OAuth not configured"}
|
| 61 |
+
|
| 62 |
+
# Only return public information (never return client_secret)
|
| 63 |
+
return {
|
| 64 |
+
"oauth_enabled": True,
|
| 65 |
+
"client_id": config["client_id"],
|
| 66 |
+
"scopes": config["scopes"],
|
| 67 |
+
"provider_url": config["provider_url"],
|
| 68 |
+
"is_hf_spaces": is_huggingface_space()
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
@router.get("/login")
|
| 73 |
async def login(request: Request):
|
| 74 |
"""
|
|
@@ -217,30 +217,48 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
| 217 |
document.querySelector('meta[name="hf:space"]') !== null;
|
| 218 |
|
| 219 |
if (isHFSpaces) {
|
| 220 |
-
// In HF Spaces,
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
} else {
|
| 245 |
// For local development, show a message or redirect to HF
|
| 246 |
dispatch({
|
|
|
|
| 217 |
document.querySelector('meta[name="hf:space"]') !== null;
|
| 218 |
|
| 219 |
if (isHFSpaces) {
|
| 220 |
+
// In HF Spaces, get OAuth config from backend API
|
| 221 |
+
console.log("π Getting OAuth config from backend");
|
| 222 |
+
|
| 223 |
+
try {
|
| 224 |
+
const response = await fetch('/api/auth/oauth-config');
|
| 225 |
+
const oauthConfig = await response.json();
|
| 226 |
+
|
| 227 |
+
if (!oauthConfig.oauth_enabled) {
|
| 228 |
+
throw new Error("OAuth not enabled or configured on backend");
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
console.log("β
OAuth config received:", {
|
| 232 |
+
client_id: oauthConfig.client_id?.substring(0, 8) + "...",
|
| 233 |
+
scopes: oauthConfig.scopes,
|
| 234 |
+
provider_url: oauthConfig.provider_url
|
| 235 |
+
});
|
| 236 |
+
|
| 237 |
+
// Direct OAuth URL construction with backend-provided client_id
|
| 238 |
+
const baseUrl = window.location.origin;
|
| 239 |
+
const redirectUri = `${baseUrl}/oauth-callback`;
|
| 240 |
+
|
| 241 |
+
// Create state for CSRF protection
|
| 242 |
+
const state = Math.random().toString(36).substring(2, 15);
|
| 243 |
+
localStorage.setItem("oauth_state", state);
|
| 244 |
+
|
| 245 |
+
// Use HF OAuth endpoint with proper client_id from backend
|
| 246 |
+
const oauthUrl =
|
| 247 |
+
`${oauthConfig.provider_url}/oauth/authorize?` +
|
| 248 |
+
`response_type=code&` +
|
| 249 |
+
`client_id=${encodeURIComponent(oauthConfig.client_id)}&` +
|
| 250 |
+
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
|
| 251 |
+
`scope=${encodeURIComponent(oauthConfig.scopes)}&` +
|
| 252 |
+
`state=${state}&` +
|
| 253 |
+
`prompt=consent`;
|
| 254 |
+
|
| 255 |
+
console.log("π Redirecting to HF OAuth with client_id");
|
| 256 |
+
window.location.href = oauthUrl;
|
| 257 |
+
|
| 258 |
+
} catch (configError) {
|
| 259 |
+
console.error("Failed to get OAuth config from backend:", configError);
|
| 260 |
+
throw new Error("OAuth configuration not available from backend");
|
| 261 |
+
}
|
| 262 |
} else {
|
| 263 |
// For local development, show a message or redirect to HF
|
| 264 |
dispatch({
|