How to Retrieve Redirect URL Parameters in Python aiohttp
When integrating OAuth2 authorization flows in Python using aiohttp, a common issue developers face is capturing the authorization code and state query parameters from a redirect URI. Frequently, developers find that response.url does not match the expected redirect_uri or seems to redirect to an unexpected page (such as an onboarding or login screen).
In this guide, we'll explore why this happens and how to successfully extract URL parameters during HTTP redirection using aiohttp.
Understanding How aiohttp Handles Redirects
By default, aiohttp.ClientSession.get() automatically follows redirects (HTTP status codes 301, 302, 303, 307, and 308). When auto-redirection occurs:
response.urlrepresents the final URL reached at the end of the redirect chain.- Intermediate requests are stored in the
response.historytuple. - If an authorization endpoint requires user interaction (such as entering credentials or approving scopes), it will return an HTTP
200 OKrendering an HTML page (like/onboardingor/login) instead of redirecting immediately.
Solution 1: Disable Auto-Redirects and Read the Location Header (Recommended)
OAuth2 authorization endpoints return a 302 Found status pointing to your redirect_uri with the code and state parameters appended. Rather than following this redirect, you can prevent it using allow_redirects=False and extract the parameters directly from the Location header.
Here is how to implement this pattern cleanly:
from yarl import URL
import aiohttp
async def get_authorization_code(self):
params = {
"response_type": "code",
"client_id": self.client_id,
"redirect_uri": "https://example.com",
}
async with self.session.get(
"https://api.example.com/oauth/authorize",
params=params,
allow_redirects=False
) as response:
# Check if the server responded with a redirect (301, 302, 303, 307)
if response.status in (301, 302, 303, 307, 308):
location_header = response.headers.get("Location")
if location_header:
redirect_url = URL(location_header)
code = redirect_url.query.get("code")
state = redirect_url.query.get("state")
return code
else:
# If status is 200, the user likely needs to authenticate via a browser session
print(f"Expected redirect, got status {response.status}")
return None
Solution 2: Inspect response.history for Intermediate URLs
If you prefer to let aiohttp follow redirects to the end, you can inspect the intermediate redirection chain via response.history. Each item in response.history is a ClientResponse instance representing an earlier step in the redirect trail.
async def get_access_token(self):
params = {
"response_type": "code",
"client_id": self.client_id,
"redirect_uri": "https://example.com"
}
async with self.session.get("https://api.example.com/oauth/authorize", params=params) as response:
# Check the history list for the redirect step containing your redirect_uri
for redirect in response.history:
if str(redirect.url).startswith("https://example.com"):
code = redirect.url.query.get("code")
return code
# Check final URL as a fallback
if "code" in response.url.query:
return response.url.query.get("code")
return None
Why Are You Being Redirected to /onboarding or /login?
If your response URL is https://api.web/onboarding with a 200 OK status, the server is indicating that the client is not yet authorized or authenticated:
- Active Session Required: The OAuth2 authorization code flow is designed for web browsers where users authenticate manually. If you are sending a headless HTTP request without existing session cookies or bearer credentials, the API will serve the login/onboarding page.
- Use the Client Credentials Flow: If you are authenticating a machine-to-machine backend service, switch to the Client Credentials Grant (
grant_type=client_credentials), which returns a token directly in a JSON payload without browser redirects.
Summary
To capture redirect parameters in aiohttp, set allow_redirects=False and parse the Location response header using yarl.URL, or iterate through response.history. If the server does not redirect, verify that the session is authenticated and supports programmatic authorization.