Failed to use Frappe as a OAuth2 provider for gradio web apps, willing to pay for the fix!

OAuth Client setting:

Social Login Key setting:

gradio web app code:

import uvicorn
from fastapi import FastAPI, Depends
from starlette.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
from authlib.integrations.starlette_client import OAuth, OAuthError
from fastapi import Request
import os
import json
from starlette.config import Config
import gradio as gr

import logging
import sys

from dotenv import load_dotenv
load_dotenv(".env")


app = FastAPI()
SECRET_KEY = "SECRET_KEY"
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)


oauth = OAuth()

### frappe
oauth.register(
    name='custom',
    client_id=os.environ.get('FRAPPE_CLIENT_ID'),
    client_secret=os.environ.get('FRAPPE_CLIENT_SECRET'),
    access_token_url='https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.get_token',
    # access_token_params=None,
    authorize_url='https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize',
    # authorize_params=None,
    api_base_url='https://yiouyou.frappe.cloud',
    client_kwargs={
        'scope': 'openid',
        'redirect_uri': 'http://127.0.0.1:8000/auth'
    },
    userinfo_endpoint='https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.openid_profile',
    # redirect_uri='http://127.0.0.1:8000/api/method/frappe.integrations.oauth2_logins.login_via_frappe',
)


custom = oauth.create_client('custom')


# Dependency to get the current user
def get_user(request: Request):
    user = None
    print('get_user.request.session', request.session)
    if 'user' in request.session.keys():
        if isinstance(request.session['user'], dict):
            _user = request.session['user']
        elif isinstance(request.session['user'], str):
            _user = json.loads(request.session['user'])
        # print("\n\nuser",_user)
        if 'name' in _user.keys():
            user = _user['name']
        elif 'login' in _user.keys():
            user = _user['login']
    return user


@app.get('/')
def public(request: Request, user = Depends(get_user)):
    root_url = gr.route_utils.get_root_url(request, "/", None)
    if user:
        return RedirectResponse(url=f'{root_url}/gradio/')
    else:
        return RedirectResponse(url=f'{root_url}/main/')


@app.get('/logout')
async def logout(request: Request):
    request.session.pop('user', None)
    return RedirectResponse(url='/')


@app.get('/login')
async def login(request: Request):
    print('/login', request.session)
    root_url = gr.route_utils.get_root_url(request, "/login", None)
    redirect_uri = f"{root_url}/auth"
    print("/login Redirecting to", redirect_uri)
    return await custom.authorize_redirect(request, redirect_uri)


@app.get('/auth')
async def auth(request: Request):
    print('/auth', request.session)
    try:
        access_token = await custom.authorize_access_token(request)
        print('access_token', access_token)
    except OAuthError:
        print("Error getting access token", str(OAuthError))
        return RedirectResponse(url='/')
    user = access_token.get('userinfo')
    print(user)
    if user:
         request.session['user'] = dict(user)
    print("/auth Redirecting to /gradio")
    return RedirectResponse(url='/gradio')


with gr.Blocks() as _main:
    btn = gr.Button("Login")
    _js_redirect = """
    () => {
        url = '/login' + window.location.search;
        window.open(url, '_blank');
    }
    """
    btn.click(None, js=_js_redirect)


app = gr.mount_gradio_app(app, _main, path="/main")


def greet(request: gr.Request):
    return f"Welcome, {request.username}"


with gr.Blocks() as _gradio:
    m = gr.Markdown("Welcome to Gradio!")
    _url = 'http://127.0.0.1:3000/'
    iframe = f'<iframe src={_url} style="border:0;height:900px;width:100%;allow:microphone;">'
    gr.HTML(iframe)
    gr.Button("Logout", link="/logout")
    _gradio.load(greet, None, m)


app = gr.mount_gradio_app(app, _gradio, path="/gradio", auth_dependency=get_user)


if __name__ == '__main__':
    uvicorn.run(app, host='127.0.0.1', port=8000)

When open http://127.0.0.1:8000/main/ in browser, click the login button, it shows:

But after clicking the Allow button, it fails to open /gradio web content, but shows:

{"detail":"Not authenticated"}

I tryed my best to debug, but have no clue why it doesn’t work as expected. The error is like below:

(venv) PS E:\_Ai\free> python ttt1.py
INFO:     Started server process [18124]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
get_user.request.session {'_state_custom_vSz7iDKyXolplT9pBrDc1vteAuRcVa': {'data': {'redirect_uri': 'http://127.0.0.1:8000/auth', 'nonce': 'ObhMxyg6xrt0IpachzuL', 'url': 'https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize?response_type=code&client_id=1af911c5c4&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fauth&scope=openid&state=vSz7iDKyXolplT9pBrDc1vteAuRcVa&nonce=ObhMxyg6xrt0IpachzuL'}, 'exp': 1711996631.7465434}, '_state_custom_Uu6NEwh69WCbKCzhmZ8A48wHutsW4V': {'data': {'redirect_uri': 'http://127.0.0.1:8000/auth', 'nonce': 'EvzMzhJGwJwBXeP8d2KS', 'url': 'https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize?response_type=code&client_id=1af911c5c4&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fauth&scope=openid&state=Uu6NEwh69WCbKCzhmZ8A48wHutsW4V&nonce=EvzMzhJGwJwBXeP8d2KS'}, 'exp': 1711996662.8727467}}
INFO:     127.0.0.1:58530 - "GET / HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:58530 - "GET /main/ HTTP/1.1" 200 OK
INFO:     127.0.0.1:58530 - "GET /main/assets/index-138adf03.css HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /main/assets/index-10ead756.js HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /main/assets/svelte/svelte.js HTTP/1.1" 200 OK
INFO:     127.0.0.1:58530 - "GET /main/assets/Index-7fdff9d2.js HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /main/assets/Index-b2fdba73.css HTTP/1.1" 200 OK
INFO:     127.0.0.1:58530 - "GET /assets/index-138adf03.css HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:58530 - "GET /main/info HTTP/1.1" 200 OK
INFO:     127.0.0.1:58530 - "GET /main/theme.css HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /main/assets/Blocks-e8e682ce.js HTTP/1.1" 200 OK
INFO:     127.0.0.1:58536 - "GET /main/assets/Button-2a9911a9.css HTTP/1.1" 200 OK
INFO:     127.0.0.1:58537 - "GET /main/assets/Blocks-2b5ac0cf.css HTTP/1.1" 200 OK
INFO:     127.0.0.1:58538 - "GET /main/assets/Button-bd009e9a.js HTTP/1.1" 200 OK
INFO:     127.0.0.1:58538 - "GET /main/assets/Index-37392b60.js HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /main/assets/Index-2abed479.css HTTP/1.1" 200 OK
INFO:     127.0.0.1:58537 - "GET /main/assets/Index-24a33ce1.js HTTP/1.1" 200 OK
INFO:     127.0.0.1:58537 - "GET /main/assets/api-logo-5346f193.svg HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /main/assets/logo-3707f936.svg HTTP/1.1" 200 OK
/login {'_state_custom_vSz7iDKyXolplT9pBrDc1vteAuRcVa': {'data': {'redirect_uri': 'http://127.0.0.1:8000/auth', 'nonce': 'ObhMxyg6xrt0IpachzuL', 'url': 'https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize?response_type=code&client_id=1af911c5c4&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fauth&scope=openid&state=vSz7iDKyXolplT9pBrDc1vteAuRcVa&nonce=ObhMxyg6xrt0IpachzuL'}, 'exp': 1711996631.7465434}, '_state_custom_Uu6NEwh69WCbKCzhmZ8A48wHutsW4V': {'data': {'redirect_uri': 'http://127.0.0.1:8000/auth', 'nonce': 'EvzMzhJGwJwBXeP8d2KS', 'url': 'https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize?response_type=code&client_id=1af911c5c4&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fauth&scope=openid&state=Uu6NEwh69WCbKCzhmZ8A48wHutsW4V&nonce=EvzMzhJGwJwBXeP8d2KS'}, 'exp': 1711996662.8727467}}
/login Redirecting to http://127.0.0.1:8000/auth
INFO:     127.0.0.1:58531 - "GET /login HTTP/1.1" 302 Found
/auth {'_state_custom_vSz7iDKyXolplT9pBrDc1vteAuRcVa': {'data': {'redirect_uri': 'http://127.0.0.1:8000/auth', 'nonce': 'ObhMxyg6xrt0IpachzuL', 'url': 'https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize?response_type=code&client_id=1af911c5c4&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fauth&scope=openid&state=vSz7iDKyXolplT9pBrDc1vteAuRcVa&nonce=ObhMxyg6xrt0IpachzuL'}, 'exp': 1711996631.7465434}, '_state_custom_Uu6NEwh69WCbKCzhmZ8A48wHutsW4V': {'data': {'redirect_uri': 'http://127.0.0.1:8000/auth', 'nonce': 'EvzMzhJGwJwBXeP8d2KS', 'url': 'https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize?response_type=code&client_id=1af911c5c4&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fauth&scope=openid&state=Uu6NEwh69WCbKCzhmZ8A48wHutsW4V&nonce=EvzMzhJGwJwBXeP8d2KS'}, 'exp': 1711996662.8727467}, '_state_custom_jW0RNlW6m6tJ407aWTvBHoSUaIsUlG': {'data': {'redirect_uri': 'http://127.0.0.1:8000/auth', 'nonce': 'DZYLepwFdp7KOaq8U2GG', 'url': 'https://yiouyou.frappe.cloud/api/method/frappe.integrations.oauth2.authorize?response_type=code&client_id=1af911c5c4&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fauth&scope=openid&state=jW0RNlW6m6tJ407aWTvBHoSUaIsUlG&nonce=DZYLepwFdp7KOaq8U2GG'}, 'exp': 1712037232.0765035}}
access_token {'exception': 'frappe.exceptions.AuthenticationError', 'exc_type': 'AuthenticationError', 'exc': '["Traceback (most recent call last):\\n  File \\"apps/frappe/frappe/app.py\\", line 97, in application\\n    validate_auth()\\n  File \\"apps/frappe/frappe/auth.py\\", line 587, in validate_auth\\n    validate_auth_via_api_keys(authorization_header)\\n  File \\"apps/frappe/frappe/auth.py\\", line 650, in validate_auth_via_api_keys\\n    validate_api_key_secret(api_key, api_secret, authorization_source)\\n  File \\"apps/frappe/frappe/auth.py\\", line 668, in validate_api_key_secret\\n    raise frappe.AuthenticationError\\nfrappe.exceptions.AuthenticationError\\n"]'}
Error getting access token <class 'authlib.integrations.base_client.errors.OAuthError'>
INFO:     127.0.0.1:58531 - "GET /auth?code=fEea0oOPffpwnPSFFhKiuLLclwn58q&state=jW0RNlW6m6tJ407aWTvBHoSUaIsUlG HTTP/1.1" 307 Temporary Redirect
get_user.request.session {}
INFO:     127.0.0.1:58531 - "GET / HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:58531 - "GET /main/ HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /assets/index-138adf03.css HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:58531 - "GET /main/info HTTP/1.1" 200 OK
INFO:     127.0.0.1:58531 - "GET /main/theme.css HTTP/1.1" 200 OK

Among the log, the access_token is wrong with AuthenticationError:

access_token {
'exception': 'frappe.exceptions.AuthenticationError',
'exc_type': 'AuthenticationError',
'exc': '["Traceback (most recent call last):\\n  File \\"apps/frappe/frappe/app.py\\", line 97, in application\\n    validate_auth()\\n  File \\"apps/frappe/frappe/auth.py\\", line 587, in validate_auth\\n    validate_auth_via_api_keys(authorization_header)\\n  File \\"apps/frappe/frappe/auth.py\\", line 650, in validate_auth_via_api_keys\\n    validate_api_key_secret(api_key, api_secret, authorization_source)\\n  File \\"apps/frappe/frappe/auth.py\\", line 668, in validate_api_key_secret\\n    raise frappe.AuthenticationError\\nfrappe.exceptions.AuthenticationError\\n"]'}
Error getting access token <class 'authlib.integrations.base_client.errors.OAuthError'>

Anyone can help me with the problem, I’d like even to pay for the bedug solution.

Thanks a lot,
Zack

I am facing the same issue in frappe v15.

You’ve to set redirect uri from the one that you set in Frappe OAuth Client doctype.