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:
-
Detects the existing sid cookie
-
Auto-approves the authorization request
-
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