VAITP Dataset

← Back to the dataset

CVE-2026-41425

Authlib Starlette client lacks CSRF protection on its cache feature.

  • CVSS 5.4
  • CWE-352
  • Authentication, Authorization, and Session Management
  • Remote

Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to 1.6.11, there is no CSRF protection on the cache feature in authlib.integrations.starlette_client.OAuth. This vulnerability is fixed in 1.6.11.

CVSS base score
5.4
Published
2026-04-24
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Authentication, Authorization, and Session Management
Subcategory
Cross-Site Request Forgery (CSRF)
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
Authlib
Fixed by upgrading
Yes

Solution

Upgrade Authlib to version 1.6.11 or later.

Vulnerable code sample

import os
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import JSONResponse, RedirectResponse
from starlette.routing import Route
from authlib.integrations.starlette_client import OAuth

# This code represents a vulnerable application using an Authlib version < 1.6.11
# The vulnerability lies within the library's implementation, not this specific user code.
# The user code appears correct, but the underlying library calls are flawed.

# Configuration for a hypothetical OAuth provider
oauth = OAuth()
oauth.register(
    name='my_provider',
    client_id='YOUR_CLIENT_ID',
    client_secret='YOUR_CLIENT_SECRET',
    authorize_url='https://provider.com/oauth/authorize',
    access_token_url='https://provider.com/oauth/token',
    client_kwargs={'scope': 'openid profile email'},
)

async def login(request):
    """
    Initiates the OAuth2 flow.
    """
    redirect_uri = request.url_for('auth')
    # In the vulnerable version of Authlib, the 'state' parameter generated by this
    # call is not properly saved into the session before the redirect.
    return await oauth.my_provider.authorize_redirect(request, redirect_uri)

async def auth(request):
    """
    Callback endpoint for the OAuth provider.
    """
    # VULNERABILITY: In the old version, this method does NOT validate the incoming 'state'
    # parameter from the URL against a value stored in the session, because
    # the 'login' endpoint failed to store it. An attacker can therefore
    # intercept the authorization flow and steal the user's authorization code.
    # The library proceeds without a CSRF check.
    token = await oauth.my_provider.authorize_access_token(request)
    
    # The application now has the token, but the flow was insecure.
    userinfo = token.get('userinfo')
    if userinfo:
        request.session['user'] = dict(userinfo)
    return JSONResponse(request.session.get('user'))

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

routes = [
    Route('/login', endpoint=login),
    Route('/auth', endpoint=auth, name='auth'),
    Route('/logout', endpoint=logout),
]

middleware = [
    Middleware(SessionMiddleware, secret_key="a-very-secret-key-that-is-not-secure")
]

app = Starlette(debug=True, routes=routes, middleware=middleware)

Patched code sample

import os
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import JSONResponse
from starlette.routing import Route
from authlib.integrations.starlette_client import OAuth

# The vulnerability was that Authlib did not use the session to store the
# OAuth state parameter, creating a CSRF risk. The fix involves using
# the session, which requires the SessionMiddleware to be configured.
# This example demonstrates the correct, fixed usage.

# A real secret key should be used in production.
app_secret_key = os.urandom(24)

# 1. Initialize OAuth registry
oauth = OAuth()

# 2. Register a remote application
# NOTE: In a real app, load these from environment variables or a config file.
oauth.register(
    name='google',
    client_id='YOUR_CLIENT_ID',
    client_secret='YOUR_CLIENT_SECRET',
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid email profile'}
)

# 3. Define the login route that initiates the OAuth flow
async def login(request):
    redirect_uri = request.url_for('auth')
    # The fixed version of this method now stores a CSRF 'state' token
    # in the user's session (request.session).
    return await oauth.google.authorize_redirect(request, redirect_uri)

# 4. Define the callback route that handles the response from the provider
async def auth(request):
    # The fixed version of this method retrieves the 'state' from the session
    # and compares it with the 'state' parameter in the callback URL,
    # preventing CSRF attacks.
    try:
        token = await oauth.google.authorize_access_token(request)
    except Exception as e:
        return JSONResponse({'error': str(e)})

    user = await oauth.google.parse_id_token(request, token)
    return JSONResponse({'token': token, 'user': user})

# 5. Create the Starlette application with routes and the crucial SessionMiddleware
routes = [
    Route('/login', endpoint=login),
    Route('/auth', endpoint=auth),
]

# The SessionMiddleware is required for the CSRF fix to work, as the 'state'
# is stored in the session.
middleware = [
    Middleware(SessionMiddleware, secret_key=app_secret_key)
]

app = Starlette(routes=routes, middleware=middleware)

# To run this example:
# 1. Install dependencies: pip install "uvicorn[standard]" "authlib>=1.3.0" "starlette" "requests"
# 2. Replace 'YOUR_CLIENT_ID' and 'YOUR_CLIENT_SECRET' with your credentials.
# 3. Run with uvicorn: uvicorn your_file_name:app --reload
# 4. Access http://127.0.0.1:8000/login in your browser.

Payload

<html>
  <body onload="document.forms[0].submit()">
    <form action="https://vulnerable-app.com/login/google" method="POST">
    </form>
  </body>
</html>

Cite this entry

@misc{vaitp:cve202641425,
  title        = {{Authlib Starlette client lacks CSRF protection on its cache feature.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-41425},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-41425/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::