Fix authentication callback issues: add cache control headers, prevent double callbacks, improve error handling, and ensure server-side sessions work properly
This commit is contained in:
1 parent
d92f557b4b
commit
6654ae30c5
1 file changed
+64
-27
@@ -1,4 +1,4 @@
|
||||
from flask import Flask, request, render_template, send_file, flash, redirect, url_for, jsonify, session
|
||||
from flask import Flask, request, render_template, send_file, flash, redirect, url_for, jsonify, session, make_response
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
@@ -27,7 +27,17 @@ app.config['MAX_CONTENT_LENGTH'] = 1000 * 1024 * 1024 * 1024 # 1000TB
|
||||
app.config['SESSION_TYPE'] = 'filesystem'
|
||||
app.config['SESSION_FILE_DIR'] = '/tmp/flask_sessions'
|
||||
app.config['SESSION_PERMANENT'] = False
|
||||
|
||||
# Ensure session directory exists and is writable
|
||||
os.makedirs('/tmp/flask_sessions', exist_ok=True)
|
||||
|
||||
try:
|
||||
Session(app)
|
||||
print("DEBUG: Flask-Session initialized successfully")
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to initialize Flask-Session: {e}")
|
||||
# Fallback to client-side sessions if server-side fails
|
||||
pass
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
@@ -136,8 +146,22 @@ def auth_callback():
|
||||
print(f"DEBUG: Callback URL: {request.url}")
|
||||
print(f"DEBUG: Session keys: {list(session.keys())}")
|
||||
|
||||
# Add cache control headers to prevent browser caching
|
||||
response = make_response()
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||
response.headers['Pragma'] = 'no-cache'
|
||||
response.headers['Expires'] = '0'
|
||||
|
||||
# Check if already authenticated to prevent double callback
|
||||
if 'oauth_token' in session:
|
||||
print("DEBUG: Already authenticated, redirecting to index")
|
||||
response.set_data(redirect(url_for('index')).get_data())
|
||||
return response
|
||||
|
||||
if 'oauth_state' not in session:
|
||||
return redirect(url_for('login'))
|
||||
print("DEBUG: No oauth_state in session, redirecting to login")
|
||||
response.set_data(redirect(url_for('login')).get_data())
|
||||
return response
|
||||
|
||||
# Parse the authorization response
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
@@ -148,38 +172,31 @@ def auth_callback():
|
||||
|
||||
if 'error' in query_params:
|
||||
print(f"DEBUG: Error from Azure AD: {query_params['error'][0]}")
|
||||
return f"Authentication error: {query_params['error'][0]}", 400
|
||||
response.set_data(f"Authentication error: {query_params['error'][0]}")
|
||||
response.status_code = 400
|
||||
return response
|
||||
|
||||
if 'code' not in query_params or 'state' not in query_params:
|
||||
return "Invalid authorization response", 400
|
||||
print("DEBUG: Invalid authorization response - missing code or state")
|
||||
response.set_data("Invalid authorization response")
|
||||
response.status_code = 400
|
||||
return response
|
||||
|
||||
code = query_params['code'][0]
|
||||
state = query_params['state'][0]
|
||||
|
||||
if state != session['oauth_state']:
|
||||
return "State mismatch", 400
|
||||
|
||||
# Use OAuth2Session for token exchange to maintain PKCE consistency
|
||||
oauth_session = OAuth2Session(
|
||||
client_id=AZURE_CLIENT_ID,
|
||||
scope=AZURE_SCOPES,
|
||||
redirect_uri=AZURE_REDIRECT_URI
|
||||
)
|
||||
|
||||
# Set the same PKCE that was used for authorization
|
||||
if 'code_verifier' in session and hasattr(oauth_session, '_client'):
|
||||
oauth_session._client.code_verifier = session['code_verifier']
|
||||
|
||||
# Set the authorization response for the session
|
||||
redirect_uri_with_params = f"{AZURE_REDIRECT_URI}?code={code}&state={state}"
|
||||
print(f"DEBUG: Redirect URI with params: {redirect_uri_with_params}")
|
||||
print(f"DEBUG: State mismatch: {state} != {session['oauth_state']}")
|
||||
response.set_data("State mismatch")
|
||||
response.status_code = 400
|
||||
return response
|
||||
|
||||
# Exchange code for token using direct HTTP request with PKCE
|
||||
import requests
|
||||
|
||||
token_data = {
|
||||
'client_id': AZURE_CLIENT_ID,
|
||||
'client_secret': AZURE_CLIENT_SECRET, # Include client_secret for confidential client
|
||||
'client_secret': AZURE_CLIENT_SECRET,
|
||||
'code': code,
|
||||
'grant_type': 'authorization_code',
|
||||
'redirect_uri': AZURE_REDIRECT_URI,
|
||||
@@ -193,26 +210,46 @@ def auth_callback():
|
||||
|
||||
print(f"DEBUG: Token request data keys: {list(token_data.keys())}")
|
||||
|
||||
token_response = requests.post(TOKEN_URL, data=token_data)
|
||||
try:
|
||||
token_response = requests.post(TOKEN_URL, data=token_data, timeout=30)
|
||||
print(f"DEBUG: Token response status: {token_response.status_code}")
|
||||
print(f"DEBUG: Token response: {token_response.text[:200]}...")
|
||||
|
||||
if token_response.status_code != 200:
|
||||
return f"Token exchange failed: {token_response.text}", 400
|
||||
print(f"DEBUG: Token exchange failed: {token_response.text}")
|
||||
response.set_data(f"Token exchange failed: {token_response.text}")
|
||||
response.status_code = 400
|
||||
return response
|
||||
|
||||
token = token_response.json()
|
||||
print(f"DEBUG: Token received successfully")
|
||||
session['oauth_token'] = token
|
||||
|
||||
# Get user info
|
||||
user_info = oauth_session.get('https://graph.microsoft.com/v1.0/me').json()
|
||||
session['user_info'] = user_info
|
||||
headers = {'Authorization': f"Bearer {token['access_token']}"}
|
||||
user_response = requests.get('https://graph.microsoft.com/v1.0/me', headers=headers, timeout=30)
|
||||
|
||||
# Clean up
|
||||
if user_response.status_code == 200:
|
||||
user_info = user_response.json()
|
||||
session['user_info'] = user_info
|
||||
print(f"DEBUG: User info retrieved: {user_info.get('displayName', 'Unknown')}")
|
||||
else:
|
||||
print(f"DEBUG: Failed to get user info: {user_response.text}")
|
||||
session['user_info'] = {'displayName': 'Unknown User'}
|
||||
|
||||
# Clean up session
|
||||
session.pop('oauth_state', None)
|
||||
session.pop('code_verifier', None)
|
||||
|
||||
return redirect(url_for('index'))
|
||||
print("DEBUG: Authentication successful, redirecting to index")
|
||||
response.set_data(redirect(url_for('index')).get_data())
|
||||
return response
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"DEBUG: Request error during token exchange: {e}")
|
||||
response.set_data(f"Authentication service error: {str(e)}")
|
||||
response.status_code = 500
|
||||
return response
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
|
||||
Reference in new issue
Block a user