OAuth2 is never logging out user from FrontEnd

I’m implementing OAuth2 logout in a frontend application. After calling revoke_token and logging out, when users attempt to log in again, they’re automatically authenticated without entering credentials.

Observed Behavior:

  • After logout, calling logIn() triggers get_token endpoint directly

  • No authorize endpoint call appears in network logs

  • User is authenticated without credential prompt

  • The authorization code appears to be auto-generated/approved

Root Cause Hypothesis:

The Frappe session cookie (sid) persists after token revocation. When the OAuth library redirects to the authorize endpoint, Frappe:

  1. Detects the existing sid cookie

  2. Auto-approves the authorization request

  3. Immediately redirects back with an authorization code

    Login Call

curl 'http://mysite.localhost:8000/api/method/frappe.integrations.oauth2.get_token' \
  -H 'Accept: */*' \
  -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Origin: http://localhost:5173' \
<REDACTED>
  --data-raw 'grant_type=authorization_code&code=ift2APzZoS50zF7d9Rsi8vo0Fj4QUZ&client_id=p51drn667k&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Foauth%2Fcallback&code_verifier=qkyI4BibdH8eSR1T63sBGrvo7ONwVDrOmo1H2cNHOALTOqSeOmRedi7CdNRMWKVjl1XK1Qx2pkWsgtM4i4grMEGwRhobfjIb'

Logout Call

curl 'http://mysite.localhost:8000/api/method/frappe.integrations.oauth2.revoke_token' \
  -H 'Accept: */*' \
  -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Origin: http://localhost:5173' \
  <REDACTED>
  --data-raw 'token=8wNAyjMxmzuuLF7jDX2tBOAdXoBXkR'
@frappe.whitelist(allow_guest=True)
def oauth_full_logout(token=None):
    """
    Complete logout that:
    1. Revokes the provided OAuth token (if given)
    2. Revokes ALL OAuth bearer tokens for the user
    3. Clears ALL sessions for the user (across all devices/browsers)
    4. Clears cookies for current request
    """
    from frappe.sessions import clear_sessions
    from frappe.auth import clear_cookies
    
    user = None
    
    # Get user from token if provided
    if token:
        # Try to find token as access_token first
        if frappe.db.exists("OAuth Bearer Token", token):
            user = frappe.db.get_value("OAuth Bearer Token", token, "user")
            # Revoke this specific token
            frappe.db.set_value("OAuth Bearer Token", token, "status", "Revoked")
        else:
            # Try to find as refresh_token
            bearer_token = frappe.db.get_value("OAuth Bearer Token", {"refresh_token": token}, ["name", "user"], as_dict=True)
            if bearer_token:
                user = bearer_token.user
                # Revoke this specific token
                frappe.db.set_value("OAuth Bearer Token", bearer_token.name, "status", "Revoked")
    
    # If no user from token, try to get from current session
    if not user:
        user = frappe.session.user if frappe.session.user != "Guest" else None
    
    if user and user != "Guest":
        # Revoke ALL OAuth bearer tokens for this user
        frappe.db.sql("""
            UPDATE `tabOAuth Bearer Token`
            SET status = 'Revoked'
            WHERE user = %s AND status = 'Active'
        """, (user,))
        
        # Clear ALL sessions for this user (force=True ignores simultaneous_sessions limit)
        clear_sessions(user=user, keep_current=False, force=True)
        
        # Clear cookies for current request
        clear_cookies()
        
        # If current session belongs to this user, also run logout triggers
        if frappe.session.user == user and hasattr(frappe.local, 'login_manager'):
            frappe.local.login_manager.run_trigger("on_logout")
            if frappe.request:
                frappe.local.login_manager.login_as_guest()
        
        frappe.db.commit()
    
    return {
        "message": "Logged out successfully"
    }

Posting the solution that worked for me. the default revoke_token API was not helpful