mrkeyoor.com_
Thu 06 Aug 01:02 UTC
PyPISecurityupdated 05 Aug 2026

requests-oauthlib

requests-oauthlib glues the oauthlib protocol library onto requests so you can talk to OAuth-protected APIs with the requests interface you already know. OAuth1Session and OAuth2Session are subclasses of requests.Session: they sign OAuth 1 requests, run the OAuth 2 authorization code dance (authorization_url, then fetch_token), attach bearer tokens to every call, and can refresh expired tokens automatically. It covers the main OAuth 2 grants (authorization code with PKCE, client credentials, legacy password) plus full OAuth 1. It exists because most Python SDKs for Twitter-era and Google-era APIs needed exactly this and nothing more.

Verdict

Still the shortest path from requests to an OAuth-protected API, and its 84M weekly downloads are baked into countless SDKs, but the project moves slowly and OAuth keeps moving. Fine to keep in existing code; for new projects Authlib is the safer bet.

API stability5/5OAuth1Session and OAuth2Session have kept the same shapes for a decade; 2.0.0 in 2024 was the first major in years and mainly dropped old Python versions and added PKCE rather than breaking callers.
Docs4/5The Read the Docs site walks through OAuth 1, OAuth 2, and real provider examples (Google, GitHub, LinkedIn) step by step, though some provider recipes are dated and the compliance-fix section assumes protocol knowledge.
Maintenance2/5Last push June 2025, releases are years apart, and 128 issues and PRs are open; it is community-maintained under the requests org with no sponsor, so expect stability rather than progress.
Ecosystem5/5Roughly 84M weekly downloads because major SDKs (kubernetes, tweepy, many API wrappers) depend on it; nearly any OAuth question about it has an existing answer somewhere.

Use it if

  • You call an OAuth 2 API from requests and want the authorization code flow (authorization_url, state check, fetch_token) handled in a few lines
  • You need automatic token refresh on long-lived integrations, with a callback to persist the new token when it rotates
  • You still have to integrate an OAuth 1.0a API (some financial, government, and legacy platforms), where this and oauthlib remain the standard Python pair
  • You do machine-to-machine calls with the client credentials grant and just want a session object that stays authenticated
Skip it if

Setup reality

pip install requests-oauthlib is pure Python and pulls just oauthlib and requests. The friction is all protocol bookkeeping: local testing over http fails with InsecureTransportError until you export OAUTHLIB_INSECURE_TRANSPORT=1, providers that return slightly nonstandard token responses need workarounds from the documented compliance-fixes module, and auto-refresh silently does nothing unless you pass auto_refresh_url, auto_refresh_kwargs, and a token_updater callback together. Mismatched scopes between request and response raise warnings you have to read the docs to interpret, and OAuth 1 signatures fail on clock skew with errors that never mention clocks.

Patterns

OAuth 2 authorization code flowauth-code-flow

from requests_oauthlib import OAuth2Session

oauth = OAuth2Session(
    client_id,
    redirect_uri="https://myapp.example/callback",
    scope=["read", "write"],
)
authorization_url, state = oauth.authorization_url(
    "https://provider.example/oauth/authorize"
)
print("Visit:", authorization_url)

# after the user is redirected back:
token = oauth.fetch_token(
    "https://provider.example/oauth/token",
    client_secret=client_secret,
    authorization_response=callback_url,
)

Store state between the two steps and pass it when rebuilding the session in a web app, or the CSRF check fails with MismatchingStateError.

Call an API with an existing tokencall-api-with-token

from requests_oauthlib import OAuth2Session

token = {
    "access_token": "abc123",
    "token_type": "Bearer",
}
oauth = OAuth2Session(client_id, token=token)
resp = oauth.get("https://api.example.com/v1/me")
resp.raise_for_status()

The session is a requests.Session, so headers, timeouts, and adapters work as usual; the bearer header is added on every request.

Refresh tokens automaticallyauto-refresh-token

def save_token(token):
    db.store(user_id, token)

oauth = OAuth2Session(
    client_id,
    token=stored_token,
    auto_refresh_url="https://provider.example/oauth/token",
    auto_refresh_kwargs={
        "client_id": client_id,
        "client_secret": client_secret,
    },
    token_updater=save_token,
)
resp = oauth.get("https://api.example.com/v1/data")

All three arguments are required; with a refresh URL but no token_updater the session raises TokenUpdated instead of continuing.

Machine-to-machine client credentials grantclient-credentials

from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

client = BackendApplicationClient(client_id=client_id)
oauth = OAuth2Session(client=client)
token = oauth.fetch_token(
    token_url="https://provider.example/oauth/token",
    client_id=client_id,
    client_secret=client_secret,
)

There is no user or redirect in this grant; the client object choice is what switches OAuth2Session between grant types.

Authorization code with PKCEpkce-flow

from requests_oauthlib import OAuth2Session

oauth = OAuth2Session(
    client_id,
    redirect_uri="https://myapp.example/callback",
    pkce="S256",
)
authorization_url, state = oauth.authorization_url(auth_base_url)
# ... user authorizes ...
token = oauth.fetch_token(
    token_url,
    authorization_response=callback_url,
    include_client_id=True,
)

The pkce parameter arrived in 2.0.0; the session generates and stores the code verifier itself, so upgrade before copying this.

Legacy password grantpassword-grant

from oauthlib.oauth2 import LegacyApplicationClient
from requests_oauthlib import OAuth2Session

oauth = OAuth2Session(client=LegacyApplicationClient(client_id=client_id))
token = oauth.fetch_token(
    token_url="https://provider.example/oauth/token",
    username="user@example.com",
    password="secret",
    client_id=client_id,
    client_secret=client_secret,
)

This grant is deprecated by the OAuth 2 security best practices; use it only against internal or legacy providers that offer nothing else.

Call an OAuth 1 APIoauth1-session

from requests_oauthlib import OAuth1Session

session = OAuth1Session(
    client_key,
    client_secret=client_secret,
    resource_owner_key=access_token,
    resource_owner_secret=access_token_secret,
)
resp = session.get("https://api.provider.example/1.1/account.json")

Every request is HMAC-signed; if the provider rejects signatures, check your server clock first because skew breaks the timestamp check.

Full OAuth 1 token danceoauth1-obtain-tokens

from requests_oauthlib import OAuth1Session

session = OAuth1Session(client_key, client_secret=client_secret,
                        callback_uri="https://myapp.example/callback")
session.fetch_request_token("https://provider.example/oauth/request_token")
url = session.authorization_url("https://provider.example/oauth/authorize")
print("Visit:", url)
# after redirect back:
session.parse_authorization_response(callback_url)
tokens = session.fetch_access_token("https://provider.example/oauth/access_token")

The session carries the request token internally between steps, so in a web app you must persist and rebuild it across the redirect.

Use OAuth 1 as a plain requests auth objectoauth1-as-auth

import requests
from requests_oauthlib import OAuth1

auth = OAuth1(client_key, client_secret,
              resource_owner_key, resource_owner_secret)
resp = requests.get("https://api.provider.example/resource", auth=auth)

OAuth1 works anywhere requests accepts auth=, which is handy when you already have a configured session or use requests.get directly.

Allow http:// during local developmentlocal-http-testing

import os

# oauthlib refuses plain http by default
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

# now http://localhost callbacks work in fetch_token()

Set this only in development; in production keep https so InsecureTransportError keeps protecting real tokens.

Tolerate providers that change scopesrelax-scope-check

import os

# provider returns different scopes than requested
os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] = "1"

token = oauth.fetch_token(token_url, client_secret=client_secret,
                          authorization_response=callback_url)

Facebook and a few other providers rewrite scopes in the token response; without this flag oauthlib raises a scope-changed warning or error.

Apply a compliance fix for a quirky providerfix-nonstandard-provider

from requests_oauthlib import OAuth2Session
from requests_oauthlib.compliance_fixes import facebook_compliance_fix

oauth = OAuth2Session(client_id, redirect_uri=redirect_uri)
oauth = facebook_compliance_fix(oauth)
token = oauth.fetch_token(token_url, client_secret=client_secret,
                          authorization_response=callback_url)

The compliance_fixes module patches known nonstandard token responses; check it before writing your own hook for a misbehaving provider.

Alternatives

PackageRegistryPick it when
authlibPyPIYou want an actively maintained, spec-complete OAuth and OIDC client with requests and httpx integrations
google-authPyPIYou authenticate against Google APIs, where the official library handles service accounts and token caching
msalPyPIYou authenticate against Microsoft Entra or Office 365; it manages that ecosystem's token flows directly
oauthlibPyPIYou are building a provider or need raw protocol signing without the requests session layer