2025, Oct 05 19:50
Flask–Nextcloud OAuth2 में Authlib टोकन एक्सचेंज 500 त्रुटि: कारण और समाधान
Flask और Nextcloud के OAuth2 में Authlib से टोकन एक्सचेंज 500 दे रहा है? विफल फ्लो का कारण, requests से सफल एक्सचेंज का उदाहरण और ट्रबलशूटिंग टिप्स पढ़ें.
Flask ऐप और Nextcloud इंस्टेंस के बीच OAuth2 सामान्यतः बिना झंझट के होना चाहिए: उपयोगकर्ता को अनुमति के लिए रीडायरेक्ट करें, ऑथराइज़ेशन कोड लें, उसे एक्सेस टोकन से बदलें और आगे API कॉल्स पर बढ़ें। व्यवहार में, यही टोकन एक्सचेंज वाला चरण कभी-कभी अटकाता है। नीचे एक वास्तविक परिदृश्य है, जहाँ Authlib-आधारित कोड कॉलबैक तक तो पहुँच गया, लेकिन टोकन रिक्वेस्ट पर हर बार प्रोवाइडर की ओर से 500 मिला; वहीं, साधारण requests पर आधारित न्यूनतम इम्प्लीमेंटेशन शुरू से अंत तक चला और साबित किया कि प्रोवाइडर ठीक काम कर रहा था।
विफल फ्लो कैसा दिखता है
एप्लिकेशन एक Flask सेवा है जिसमें NEXTCLOUD_CLIENT_ID, NEXTCLOUD_SECRET, NEXTCLOUD_API_BASE_URL, NEXTCLOUD_AUTHORIZE_URL और NEXTCLOUD_ACCESS_TOKEN_URL कॉन्फ़िगर हैं। Nextcloud को रीडायरेक्ट सही चलता है, उपयोगकर्ता अनुमति देता है, और Nextcloud state और code के साथ कॉलबैक करता है। गड़बड़ी तब होती है जब उस कोड को एक्सेस टोकन में बदला जाता है।
import requests
from flask import Flask, render_template, jsonify, request, session, url_for, redirect
from flask_session import Session
from authlib.integrations.flask_client import OAuth
web = Flask("portal")
# web.config यहाँ सेट किया गया है, विशेष रूप से ये सेटिंग्स:
# NEXTCLOUD_CLIENT_ID
# NEXTCLOUD_SECRET
# NEXTCLOUD_API_BASE_URL
# NEXTCLOUD_AUTHORIZE_URL
# NEXTCLOUD_ACCESS_TOKEN_URL
Session(web)
oauth_mgr = OAuth(web)
nc_client = oauth_mgr.register('nextcloud')
@web.route("/", methods=["GET"])
def home():
    return render_template("index.html"), 200
@web.route("/nextcloud_login", methods=["GET"])
def begin_nextcloud():
    target = url_for("nc_callback", _external=True)
    return nc_client.authorize_redirect(target)
@web.route('/callback/nextcloud', methods=["GET"])
def nc_callback():
    tok = nc_client.authorize_access_token()
    session["nc_token"] = tok
    return redirect(url_for("home"))
इस बिंदु पर ब्राउज़र ऐसे कॉलबैक पर पहुँचता है: GET /callback/nextcloud?state=some-token&code=even-longer-token. एक्सचेंज चरण चलने पर प्रदाता की ओर से यह संदेश दिखा:
OC\Security\Crypto::calculateHMAC(): Argument #1 ($message) must be of type string, null given, called in /var/www/nextcloud/apps/oauth2/lib/Controller/OauthApiController.php on line 142 in file '/var/www/nextcloud/lib/private/Security/Crypto.php' line 42
असल में गड़बड़ी कहाँ है
रीडायरेक्ट और ऑथराइज़ेशन वाले हिस्से ठीक हैं। समस्या तब आती है जब ऐप authorize_access_token के ज़रिए टोकन एंडपॉइंट को कॉल करता है और बदले में 500 लौटता है। Nextcloud लॉग बताता है कि calculateHMAC को message आर्ग्युमेंट के लिए null मिला। वहीं, requests का उपयोग करने वाला न्यूनतम इम्प्लीमेंटेशन एक्सचेंज पूरा कर देता है और access_token तथा refresh_token दोनों लौटते हैं—यानी दोष प्रदाता का नहीं है।
साधारण requests के साथ काम करने वाला तरीका
requests पर आधारित सीधा-सादा टोकन एक्सचेंज state को वैध करता है, code की जाँच करता है, और authorization_code ग्रांट को NEXTCLOUD_ACCESS_TOKEN_URL पर पोस्ट करता है। यह मार्ग सफलतापूर्वक पूरा होता है और दिखाता है कि कॉन्फ़िगरेशन और प्रदाता दोनों ठीक हैं।
from __future__ import annotations
from pathlib import Path
import io
import uuid
from urllib.parse import urlencode
import requests
from flask import Flask, render_template, jsonify, request, session, url_for, redirect
from flask_session import Session
srv = Flask("site")
# srv.config यहाँ सेट किया गया है, विशेष रूप से ये सेटिंग्स:
# NEXTCLOUD_CLIENT_ID
# NEXTCLOUD_SECRET
# NEXTCLOUD_API_BASE_URL
# NEXTCLOUD_AUTHORIZE_URL
# NEXTCLOUD_ACCESS_TOKEN_URL
Session(srv)
@srv.route("/", methods=["GET"])
def landing():
    if "user_id" not in session:
        session["user_id"] = "__anonymous__"
        session["nc_granted"] = False
    return render_template("index.html", session=session), 200
@srv.route("/nextcloud_login", methods=["GET"])
def start_nc_oauth():
    if "nc_granted" in session and session["nc_granted"]:
        redirect(url_for("landing"))
    session['nc_state'] = str(uuid.uuid4())
    qs = urlencode({
        'client_id': srv.config['NEXTCLOUD_CLIENT_ID'],
        'redirect_uri': url_for('nc_oauth_callback', _external=True),
        'response_type': 'code',
        'scope': "",
        'state': session['nc_state'],
    })
    return redirect(srv.config['NEXTCLOUD_AUTHORIZE_URL'] + '?' + qs)
@srv.route('/callback/nextcloud', methods=["GET"])
def nc_oauth_callback():
    if "nc_granted" in session and session["nc_granted"]:
        redirect(url_for("landing"))
    if request.args["state"] != session["nc_state"]:
        return jsonify({"error": "CSRF warning! Request states do not match."}), 403
    if "code" not in request.args or request.args["code"] == "":
        return jsonify({"error": "Did not receive valid code in NextCloud callback"}), 400
    resp = requests.post(
        srv.config['NEXTCLOUD_ACCESS_TOKEN_URL'],
        data={
            'client_id': srv.config['NEXTCLOUD_CLIENT_ID'],
            'client_secret': srv.config['NEXTCLOUD_SECRET'],
            'code': request.args['code'],
            'grant_type': 'authorization_code',
            'redirect_uri': url_for('nc_oauth_callback', _external=True),
        },
        headers={'Accept': 'application/json'},
        timeout=10
    )
    if resp.status_code != 200:
        return jsonify({"error": "Invalid response while fetching access token"}), 400
    payload = resp.json()
    access_token = payload.get('access_token')
    if not access_token:
        return jsonify({"error": "Could not find access token in response"}), 400
    refresh_token = payload.get('refresh_token')
    if not refresh_token:
        return jsonify({"error": "Could not find refresh token in response"}), 400
    session["nc_access"] = access_token
    session["nc_refresh"] = refresh_token
    session["nc_granted"] = True
    session["user_id"] = payload.get("user_id")
    return redirect(url_for("landing"))
यह कोड अभी एक्सेस टोकन से API कॉल नहीं करता और न ही refresh टोकन से एक्सपायर्ड एक्सेस को नवीनीकृत करता है। ये सुविधाएँ आमतौर पर कोई समर्पित OAuth क्लाइंट लाइब्रेरी संभाल लेती है; यहाँ उद्देश्य केवल एक्सचेंज को सत्यापित करना और विफलता को अलग करना है।
यह क्यों मायने रखता है
OAuth2 को जोड़ते समय, प्रदाता की कॉन्फ़िगरेशन और मूल authorization_code फ्लो को स्वतंत्र रूप से परख पाना बेहद उपयोगी होता है। यहाँ लाइब्रेरी के जरिए टोकन एक्सचेंज पर 500 आया और लॉग में HMAC के लिए null इनपुट दिखा, जबकि वही इनपुट इस्तेमाल करते हुए मैनुअल एक्सचेंज पहले ही प्रयास में सफल रहा। इससे स्पष्ट है कि एनवायरनमेंट और प्रदाता स्वस्थ हैं, और आगे की जाँच का केंद्र क्लाइंट लाइब्रेरी के टोकन रिक्वेस्ट के विवरण होने चाहिए।
निष्कर्ष
यदि ऑथराइज़ेशन रीडायरेक्ट ठीक चलता है और कॉलबैक में code व state आते हैं लेकिन टोकन एक्सचेंज 500 देता है, तो एक न्यूनतम requests-आधारित फ्लो से ग्रांट को परखें। सुनिश्चित करें कि state जाँच हो रही है, code मौजूद है, grant_type authorization_code है, और redirect_uri ठीक वैसा ही है जैसा प्रदाता अपेक्षा करता है। एक बार आपके पास भरोसेमंद बेसलाइन आ जाए, तो उसे लाइब्रेरी के व्यवहार से मिलाएँ और ज़रूरत पड़े तो बग रिपोर्ट उठाएँ। इस परिदृश्य में मैनुअल फ्लो भरोसेमंद साबित हुआ, और अगला कदम OAuth क्लाइंट लाइब्रेरी में अपस्ट्रीम फिक्स का पीछा करना है।
यह लेख StackOverflow पर प्रश्न (लेखक: Etienne Ott) और Etienne Ott द्वारा दिए गए उत्तर पर आधारित है।