Playwright-based browser authentication bridge for HTTP APIs with complex auth flows
Project description
web-auth-bridge
Browser-assisted authentication for HTTP APIs that fight back.
Many interesting APIs can't be reached with plain HTTP: they hide behind
SSO, SAML, Cloudflare challenges, or stateful web portals with no JSON
surface. web-auth-bridge uses Playwright to handle the login once,
extracts the resulting cookies and tokens as plain data, and hands them
to your consumer code so every subsequent call is a fast httpx request.
What it does
- Logs in with a real browser (Playwright / Chromium) so WAFs, JS challenges, and SAML redirects all just work.
- Extracts cookies and tokens into a typed
AuthResult— no more hairy JSON digging. - Caches auth on disk at
~/.config/<app>/auth_cache.jsonso every run after the first is cache-warm. - Issues parallel authenticated browsers when a site has no clean HTTP API (classic ASP.NET portals, etc.).
- Runs headless or headed, with credentials from a file or typed by the user into a visible browser.
Install
pip install web-auth-bridge
playwright install chromium
For sites that reject Python's default TLS fingerprint (e.g. Garmin's DI OAuth2 exchange):
pip install "web-auth-bridge[tls-impersonation]"
Quick start
import asyncio
from web_auth_bridge import WebAuthBridge, AuthResult, Credentials
class ExampleCallback:
async def authenticate(self, page, credentials):
await page.goto("https://example.com/login")
await page.fill('input[name="email"]', credentials.username)
await page.fill('input[name="password"]', credentials.password)
async with page.expect_navigation():
await page.click('button[type="submit"]')
return AuthResult() # cookies auto-extracted from the context
async def is_authenticated(self, result):
return not result.is_expired
async def main():
bridge = WebAuthBridge(
app_name="example",
auth_callback=ExampleCallback(),
credentials=Credentials(username="me@example.com", password="..."),
)
await bridge.ensure_authenticated()
async with bridge.http_client() as client:
resp = await client.get("https://example.com/api/me")
print(resp.json())
asyncio.run(main())
The first run opens Chromium; the second (within cookie lifetime) doesn't.
When to use it
Use web-auth-bridge when you need to script against a site whose auth
flow you can't or don't want to reimplement by hand:
- Cloudflare / other WAFs that challenge non-browser clients.
- SAML/OAuth portals where the app doesn't expose a token endpoint.
- Company intranets that require MFA or interactive SSO.
- Scraping portals with stateful sessions and no API.
- CLI tools for personal/private APIs where reimplementing the auth dance is more work than the tool is worth.
Don't use it when you already have a clean API token — plain httpx is
fine there.
Modes
| Mode | Credentials from | Browser visible? | Typical use case |
|---|---|---|---|
| Headless + stored creds | .env / file |
No | Fully automated CLIs and agents |
| Headed + stored creds | .env / file |
Yes | Debugging; sites that require a visible window |
| Headed + manual entry | User types into page | Yes | Sensitive credentials never written to disk |
| Headless + no creds | Previously-cached | No | Every post-first run |
Parallel headless browsers
await bridge.ensure_authenticated()
async with bridge.context_pool(size=4) as pool:
pages = [await ctx.new_page() for ctx in pool]
# All four pages start already authenticated.
...
Useful for portals where every read is a full page render.
Docs
- Architecture — components, flows, and diagrams
- Design notes — decisions, alternatives, constraints
- Examples — minimal snippets for common flows
- Testing — how to run unit, integration, and live-site tests
- Garmin example — end-to-end Cloudflare-guarded SSO + OAuth2 Bearer token flow
Development
git clone https://github.com/mikejhill/web-auth-bridge
cd web-auth-bridge
uv sync --all-extras
uv run playwright install chromium
uv run poe test # unit + integration
uv run poe cov # with coverage report
uv run poe lint # ruff check
Contributions welcome. Please use Conventional Commits — the release workflow derives versions from commit messages.
License
MIT © Mike Hill
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file web_auth_bridge-0.1.0.tar.gz.
File metadata
- Download URL: web_auth_bridge-0.1.0.tar.gz
- Upload date:
- Size: 25.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d8da3014d1381cbc6c6893c3b14571c90e9843f78c13980cafe7278cd60110e
|
|
| MD5 |
98b49b84a5e29ad4422342484e886cfd
|
|
| BLAKE2b-256 |
05bba1e2066bb3681ea8aefb319114a42fe4f8d9627bd693d7cfc2d34ebf19f2
|
Provenance
The following attestation bundles were made for web_auth_bridge-0.1.0.tar.gz:
Publisher:
release.yml on mikejhill/web-auth-bridge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
web_auth_bridge-0.1.0.tar.gz -
Subject digest:
1d8da3014d1381cbc6c6893c3b14571c90e9843f78c13980cafe7278cd60110e - Sigstore transparency entry: 1322734732
- Sigstore integration time:
-
Permalink:
mikejhill/web-auth-bridge@cca6a68b0e9ec44197d0d5b36baddeb1c64c7d1d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mikejhill
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@cca6a68b0e9ec44197d0d5b36baddeb1c64c7d1d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file web_auth_bridge-0.1.0-py3-none-any.whl.
File metadata
- Download URL: web_auth_bridge-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04b53df12ba6b391309757ca314726344160e649f8d35aea77830aa928c9f748
|
|
| MD5 |
fc14ddb1f331058f37526598f41e2647
|
|
| BLAKE2b-256 |
e99ad6ffe94f153b9271c522d47a73e4e80eecb39645d76086877ec1ea3e3aa1
|
Provenance
The following attestation bundles were made for web_auth_bridge-0.1.0-py3-none-any.whl:
Publisher:
release.yml on mikejhill/web-auth-bridge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
web_auth_bridge-0.1.0-py3-none-any.whl -
Subject digest:
04b53df12ba6b391309757ca314726344160e649f8d35aea77830aa928c9f748 - Sigstore transparency entry: 1322734792
- Sigstore integration time:
-
Permalink:
mikejhill/web-auth-bridge@cca6a68b0e9ec44197d0d5b36baddeb1c64c7d1d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mikejhill
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@cca6a68b0e9ec44197d0d5b36baddeb1c64c7d1d -
Trigger Event:
workflow_dispatch
-
Statement type: