py-oauth-kit
Simple OAuth 2.0 helpers for Python: build the sign-in URL, exchange the code for a token and fetch the user's details. Works with Django, DRF, Flask, FastAPI or plain Python. The only dependency is httpx.
Supported providers: Amazon, Apple, Discord, Facebook, GitHub, Google, LinkedIn, Microsoft, Reddit, X (Twitter) and Yahoo.
Installation
Requires Python 3.13+.
pip install py-oauth-kit
For Sign in with Apple, install the apple extra:
pip install "py-oauth-kit[apple]"
Usage
Basic flow
Sign-in takes three steps: send the user to the provider, exchange the code the provider sends back for a token, then fetch the user. Every provider works the same way; only the constructor differs (see below).
import secrets
from py_oauth import GitHub, OAuthError
github = GitHub(
client_id="your-client-id",
client_secret="your-client-secret",
redirect_uri="https://example.com/auth/github/callback",
)
# 1. Redirect the user to the provider.
state = secrets.token_urlsafe()
session["oauth_state"] = state # save it, e.g. in the user's session
url = github.get_authorization_url(state)
# 2. The provider redirects back to redirect_uri with ?code=...&state=...
if request.args["state"] != session.pop("oauth_state"):
raise PermissionError("Invalid state")
try:
token = github.get_access_token(request.args["code"])
# 3. Fetch the user.
user = github.fetch_user(token)
except OAuthError as exc:
... # the provider rejected the request, or it failed
print(user.id, user.email, user.email_verified, user.name, user.username)
session and request stand for your framework's session and request objects.
The redirect_uri must be registered with the provider. Each provider section
below links to a setup guide.
Amazon
Setup guide: docs/amazon.md
from py_oauth import Amazon
amazon = Amazon(client_id, client_secret, redirect_uri)
- Amazon has no username, so
nameis"Unknown"when the profile has no name. email_verifiedis alwaysFalse.
Apple
Setup guide: docs/apple.md
from py_oauth import Apple
apple = Apple(
client_id="com.example.signin", # your Services ID
redirect_uri=redirect_uri,
team_id="ABCDE12345",
key_id="FGHIJ67890",
private_key=open("AuthKey_FGHIJ67890.p8").read(),
)
-
Requires a paid Apple Developer Program membership.
-
Needs the
appleextra (see Installation). -
There's no client secret: py-oauth-kit signs one with your
.p8key. -
Apple redirects back with a POST, so your callback must accept POST. Browsers don't send
SameSite=Laxcookies (Django's default) with it, so store the state in aSameSite=None; Securecookie. -
Apple sends the user's name only on their first sign-in, in the
userform field. Pass it on, and save the name then, as later sign-ins don't include it:user = apple.fetch_user(token, user=request.POST.get("user"))
-
Reuse one
Appleinstance across requests: it caches Apple's public keys.
Discord
Setup guide: docs/discord.md
from py_oauth import Discord
discord = Discord(client_id, client_secret, redirect_uri)
Setup guide: docs/facebook.md
from py_oauth import Facebook
facebook = Facebook(client_id, client_secret, redirect_uri)
email_verifiedis alwaysFalse.- The email is
Noneif the user declines to share it.
GitHub
Setup guide: docs/github.md
from py_oauth import GitHub
github = GitHub(client_id, client_secret, redirect_uri)
- Supports OAuth Apps, not GitHub Apps.
- OAuth App tokens don't expire by default (
expires_inisNone); they stay valid until revoked. - The email is the user's primary address. Without the
user:emailscope (requested by default), it falls back to the public profile email, which may beNone.
Setup guide: docs/google.md
from py_oauth import Google
google = Google(client_id, client_secret, redirect_uri)
-
A refresh token is only sent with
access_type=offline, and only on the user's first consent. Addprompt=consentto get one again:url = google.get_authorization_url( state, extra_params={"access_type": "offline", "prompt": "consent"} )
Setup guide: docs/linkedin.md
from py_oauth import LinkedIn
linkedin = LinkedIn(client_id, client_secret, redirect_uri)
- Add the "Sign In with LinkedIn using OpenID Connect" product to your app in the LinkedIn developer portal, or sign-in fails.
Microsoft
Setup guide: docs/microsoft.md
from py_oauth import Microsoft
microsoft = Microsoft(client_id, client_secret, redirect_uri, tenant="common")
tenantsets who can sign in:"common"(default, all accounts),"organizations"(work and school),"consumers"(personal) or your tenant ID. Single-tenant apps must pass their tenant.email_verifiedis alwaysFalse, as Microsoft doesn't verify emails.avatar_urlis alwaysNone.
Setup guide: docs/reddit.md
from py_oauth import Reddit
reddit = Reddit(
client_id,
client_secret,
redirect_uri,
user_agent="web:com.example.app:1.0 (by /u/your_username)",
)
- Doesn't share the user's email, so
emailis alwaysNone. - Requires a
user_agent. Reddit blocks generic ones; use the format<platform>:<app ID>:<version> (by /u/<username>).
X (Twitter)
Setup guide: docs/twitter.md
from py_oauth import Twitter
twitter = Twitter(client_id, client_secret, redirect_uri)
- Requires PKCE (see PKCE under Options).
- To get the email, enable "Request email from users" in your app's settings
in the X developer portal. Without it,
emailisNone.
Yahoo
Setup guide: docs/yahoo.md
from py_oauth import Yahoo
yahoo = Yahoo(client_id, client_secret, redirect_uri)
- Enable the OpenID Connect permissions (Email, Profile) for your app in the Yahoo developer console, or sign-in fails.
What you get back
get_access_token returns an AccessToken:
| Field | Description |
|---|---|
token |
The access token. |
expires_in |
Lifetime in seconds, or None if not given. |
expires_at |
When it expires (UTC), or None. |
refresh_token |
Refresh token, if the provider sent one. |
scopes |
Scopes granted, if the provider reports them. |
raw |
The provider's full response. |
fetch_user returns an Identity:
| Field | Description |
|---|---|
id |
The user's ID at the provider (always a string). |
email |
Email address, or None. |
email_verified |
Whether the provider verified the email. |
name |
Display name (see Names). |
username |
Handle or login, or None. |
avatar_url |
Profile picture URL, or None. |
raw |
The provider's full response. |
To use a stored token later, wrap it: github.fetch_user(AccessToken(token=value)).
Names
name is the user's display name. If the provider has none, it falls back to
their username (e.g. GitHub login), and then to "Unknown". It never falls back
to the email.
Emails
emailcan beNone: the user may have no email, or may not share it.- Only trust
emailto match users to existing accounts whenemail_verifiedisTrue.
Tokens
py-oauth-kit doesn't refresh tokens. If a provider sends a refresh_token, it's
available on the AccessToken, but you refresh it yourself.
Options
Scopes and extra parameters
Each provider requests sensible default scopes. To change them, or to add extra query parameters:
url = google.get_authorization_url(
state,
scopes=["openid", "email"],
extra_params={"prompt": "select_account"},
)
PKCE
PKCE adds extra protection to the sign-in. X (Twitter) requires it; other providers accept it. Generate a verifier, save it with the state, and pass it to both calls:
from py_oauth import Twitter, generate_code_verifier
verifier = generate_code_verifier()
session["oauth_verifier"] = verifier
url = twitter.get_authorization_url(state, code_verifier=verifier)
# On the callback:
token = twitter.get_access_token(code, code_verifier=session.pop("oauth_verifier"))
HTTP client
By default each request opens a new connection. To reuse connections, or set
timeouts or proxies, pass your own httpx.Client:
import httpx
client = httpx.Client(timeout=10)
github = GitHub(client_id, client_secret, redirect_uri, client=client)
Errors
All errors inherit from OAuthError, so you never need to catch httpx errors:
TokenExchangeError: exchanging the code failed (e.g. the code expired).UserFetchError: fetching the user failed (e.g. the token was revoked).
Each error has error, description and status_code from the provider, when
available.
License
MIT
Metadata
Release files for py-oauth-kit 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| py_oauth_kit-1.0.0.tar.gz | 20.2 kB | Without provenance | Details for py_oauth_kit-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| py_oauth_kit-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for py_oauth_kit-1.0.0-py3-none-any.whl |
Total release size: 50.3 kB
Release files / py_oauth_kit-1.0.0.tar.gz
| Download URL | py_oauth_kit-1.0.0.tar.gz |
|---|---|
| Size | 20.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
07b206c9ffacd9ecc4eeb10ff11e89fd405414e231e4f6f53565ab85db0560c1
|
|
BLAKE2b-256 checksum How to use checksums |
49128164794f2126d584a1e69b9a1204597017c5f4a7738164d2db2cd5445010
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / py_oauth_kit-1.0.0-py3-none-any.whl
| Download URL | py_oauth_kit-1.0.0-py3-none-any.whl |
|---|---|
| Size | 30.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7bc75e34ebc392fe74add64feed489d8a160c04acc0722827027ed993c6aa245
|
|
BLAKE2b-256 checksum How to use checksums |
b53fce213d32bf8603d88077ef377b2f7c10b345a8b1aed41e4da0f31a833c6c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Security for py-oauth-kit 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.