Deprecating Garth #222
Replies: 12 comments 4 replies
-
|
@matin Thank you for your time building this! The community appreciates it! So many projects, including mine saw daylight because of your effort! We will keep trying. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you for all you've done. Here's hoping someone with time and skills needed can pick up the mantle! :) |
Beta Was this translation helpful? Give feedback.
-
|
Thank you a lot for the time you spent on this project. Sorry to see another platform doing changes which break connections to third part services and have no API to work with :') |
Beta Was this translation helpful? Give feedback.
-
|
Respect your decision @matin . You work on garth/Garmin Connect has been phenomenal. I wish I could open access to our system for users but Garmin Connect is just subset of our system and I really don't want it used just for Garmin. |
Beta Was this translation helpful? Give feedback.
-
|
For anyone still looking for a working solution: I built an MCP server that bypasses the Cloudflare TLS fingerprinting by routing API calls through a headless Playwright browser. 27 tools covering activities, daily health, sleep, HRV, body battery, weight, personal records, fitness stats, and FIT file downloads. Works with Claude Code and any MCP client. Auth uses browser cookies captured via Playwright — no garth/OAuth dependency. |
Beta Was this translation helpful? Give feedback.
-
|
I made a post on the Garmin forums about this issue. I'm not optimistic that it will have any effect, but I urge others who relied on the Connect API to weigh in: https://forums.garmin.com/apps-software/mobile-apps-web/f/garmin-connect-web/433892/tls-fingerprinting-blocks-third-party-clients |
Beta Was this translation helpful? Give feedback.
-
|
Thanks for everything, matin Built something similar for a specific use case - syncing Eufy smart scale data to Garmin Connect. Same approach: Playwright for the initial browser login, capture OAuth2 tokens, then refresh them automatically going forward! The Garmin auth piece is reusable if anyone wants to build on it - it handles the DI token exchange and auto-refresh. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you for all your time and effort! Sad to see this go away. |
Beta Was this translation helpful? Give feedback.
-
|
@matin The root cause is that Garmin's Cloudflare setup blocks garth's default mobile User-Agent (GCM-iOS-5.7.2.1). Overriding it with a browser User-Agent before calling login() is enough to get past the block, no Playwright needed: This works for both regular login and MFA flows. Token refresh via garth.resume() also works fine after this. It's a cat-and-mouse situation (Garmin could tighten TLS fingerprinting beyond just User-Agent), but for now it's the simplest fix and has been reliable for me in production. If garth were to adopt this as a default (or make the User-Agent configurable), it would likely unblock the majority of users hitting the auth breakage from issue #217. I'm using this approach in garmin-health-data, a Python CLI that extracts Garmin Connect data into a SQLite database. It depends on garminconnect (and by extension garth), so keeping garth alive matters to me. Happy to contribute a PR if there's interest. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks all for the comments ! Based on the comments above, here's my quick boilerplate solution using playwright. Garth is still useful once the oauth tokens are fetch. As a quick workaround, here's the solution I did with the help of claude code. steps
I'm using garth to sync garmin data both for me wife and myself, therefore the user. working from the dir file {
"username": "replaceMe",
"password": "replaceMe"
}file """
Browser-based Garmin login.
Garmin's SSO requires JS-set cookies that a plain HTTP client cannot replicate.
This script opens a real Chromium browser, logs in, extracts the CAS ticket,
and exchanges it for OAuth tokens via garth. Tokens are saved to
sources/garmin/tokens-{user}/ and reused automatically on future runs.
Usage:
uv run python sources/garmin/playwright_login.py --user <user>
"""
import argparse
import json
import re
from pathlib import Path
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright
_DIR = Path(__file__).parent
_SSO = "https://sso.garmin.com/sso"
_SSO_EMBED = f"{_SSO}/embed"
_SIGNIN_URL = f"{_SSO}/signin?" + urlencode({
"id": "gauth-widget",
"embedWidget": "true",
"gauthHost": _SSO,
"service": _SSO_EMBED,
"source": _SSO_EMBED,
"redirectAfterAccountLoginUrl": _SSO_EMBED,
"redirectAfterAccountCreationUrl": _SSO_EMBED,
})
def login(user: str) -> None:
creds = json.loads((_DIR / f"credentials-{user}.json").read_text())
tokenstore = _DIR / f"tokens-{user}"
tokenstore.mkdir(exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_context(user_agent=(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
)).new_page()
page.goto(_SIGNIN_URL)
page.wait_for_load_state("networkidle")
page.fill('input[name="username"]', creds["username"])
page.fill('input[name="password"]', creds["password"])
page.click("#login-btn-signin")
page.wait_for_url("**/embed?ticket=**", timeout=60_000)
ticket_url = page.url
browser.close()
m = re.search(r'[?&]ticket=(ST-[^&\s]+)', ticket_url)
if not m:
raise RuntimeError(f"No CAS ticket found in URL: {ticket_url}")
ticket = m.group(1)
from garth.http import Client
from garth.sso import exchange, get_oauth1_token
client = Client()
oauth1 = get_oauth1_token(ticket, client)
oauth2 = exchange(oauth1, client)
client.configure(oauth1_token=oauth1, oauth2_token=oauth2)
client.dump(str(tokenstore))
print(f"Tokens saved to {tokenstore}/")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--user", required=True)
login(parser.parse_args().user)the script above saves the oauth tokens in dir then garth can be used to authenticate against the garmin API with from pathlib import Path
from garth.exc import GarthHTTPError
from garminconnect import (
Garmin,
GarminConnectAuthenticationError,
GarminConnectConnectionError,
)
_DIR = Path(__file__).parent
def auth(user: str) -> Garmin:
"""Authenticate with stored tokens.
Tokens must be obtained first by running:
uv run python sources/garmin/playwright_login.py --user <user>
"""
tokenstore = str(_DIR / f"tokens-{user}/")
try:
garmin = Garmin()
garmin.login(tokenstore)
garmin.garth.dump(tokenstore)
return garmin
except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError, GarminConnectConnectionError) as e:
raise RuntimeError(
f"No valid tokens for user '{user}'. "
f"Run: uv run python sources/garmin/playwright_login.py --user {user}"
) from ewith the help of claude, I believe that anyone can quickly make the above work for themselves while Garmin hopefully listen to their customers |
Beta Was this translation helpful? Give feedback.
-
|
For anyone looking for a working alternative: garmin-health-data authenticates reliably without garth or any browser tooling. It uses |
Beta Was this translation helpful? Give feedback.
-
|
Hi. I fixed the 2fa issue following the advice on this page. I just added and ran the following in the project folder and put the login info and code recieved via email. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I started Garth on July 5, 2023 as a side project to allow me to analyze my personal Garmin data. The existing libraries, at the time, didn't work reliably for me when running code on Google Colab and with MFA enabled, which required a long-lived token.
It took me until August 3, 2023 to figure out how to use the mobile auth and API to have a stable library. The approach continued to work with almost no issues for over 2.5 years. Garth reached 350k+ downloads per month on PyPI and has been translated into multiple other programming languages.
Garmin recently changed their auth flow, breaking the mobile auth approach that Garth and other libraries using Garth depend on (see #217 and cyberjunky/python-garminconnect#332). Unfortunately, I'm not in a position where I can dedicate the time to adapt Garth to these changes.
I'll keep the repo up and release a final version with a deprecation warning pointing to this discussion.
Anyone is welcome to fork Garth as a starting point for a new library.
Beta Was this translation helpful? Give feedback.
All reactions