GarminRunerz.Connect.Auth
0.5.0
dotnet add package GarminRunerz.Connect.Auth --version 0.5.0
NuGet\Install-Package GarminRunerz.Connect.Auth -Version 0.5.0
<PackageReference Include="GarminRunerz.Connect.Auth" Version="0.5.0" />
<PackageVersion Include="GarminRunerz.Connect.Auth" Version="0.5.0" />
<PackageReference Include="GarminRunerz.Connect.Auth" />
paket add GarminRunerz.Connect.Auth --version 0.5.0
#r "nuget: GarminRunerz.Connect.Auth, 0.5.0"
#:package GarminRunerz.Connect.Auth@0.5.0
#addin nuget:?package=GarminRunerz.Connect.Auth&version=0.5.0
#tool nuget:?package=GarminRunerz.Connect.Auth&version=0.5.0
GarminRunerz.Connect.Auth
Garmin Connect authentication for unattended, multi-user hosts.
The unofficial Garmin API needs a password to log in, but only once: the login yields a long-lived (~1 year) OAuth1 token, and short-lived OAuth2 access tokens can be minted from that OAuth1 token without the password. This package exposes that split so a hosted service can store the OAuth1 token (encrypted) and act on a user's behalf without ever persisting their password.
It vendors the MIT-licensed SSO flow from sealbro/dotnet.garmin.connect (see THIRD-PARTY-NOTICES.txt).
⚠️ Relies on Garmin's private web API — intended for personal / small-group automation. See the repository README for the security and terms-of-service considerations.
The model in one line
Log in once (password) → get the OAuth1 token → store it encrypted → refresh OAuth2 from it (no password) for every subsequent action. The password is never stored; when the OAuth1 token eventually expires (~1 year) or is revoked, the user logs in again.
Dependency injection
services.AddGarminConnectAuth();
Registers IGarminAuthClient as a singleton (it holds pending MFA logins in memory between the begin- and complete-MFA requests, so the same instance must serve both) and a default in-memory IOAuth1TokenStore. For a real host, register your own encrypted store — it wins over the default:
services.AddGarminConnectAuth();
services.AddSingleton<IOAuth1TokenStore, MyDataProtectionOAuth1TokenStore>();
Usage
using GarminRunerz.Connect.Auth;
using var auth = new GarminAuthClient();
// One-time: log in with the password, get the long-lived OAuth1 token.
GarminOAuth1Token oauth1 = await auth.LoginAsync(email, password);
// Store `oauth1` encrypted at rest. The password is not retained anywhere.
Refresh — mint OAuth2 access tokens without the password
// Later (even in a different process), load the stored OAuth1 token and mint a
// short-lived OAuth2 access token — no password involved.
GarminOAuth2Token oauth2 = await auth.RefreshOAuth2Async(oauth1);
// Use it to call the Garmin Connect API:
// request.Headers.Authorization = AuthenticationHeaderValue.Parse(oauth2.AuthorizationHeaderValue);
if (oauth2.ExpiresWithin(TimeSpan.FromMinutes(5)))
{
// proactively refresh before it lapses (OAuth2 lasts ~1 hour)
}
When the stored OAuth1 token itself has expired (~1 year) or been revoked, RefreshOAuth2Async throws GarminReconnectRequiredException — the signal to have the user log in again.
MFA accounts (two-step)
For accounts with multi-factor authentication, use the two-step flow — it maps cleanly onto two web requests (start login → prompt for code → complete):
var login = await auth.BeginLoginAsync(email, password);
GarminOAuth1Token oauth1;
if (login.MfaRequired)
{
// Prompt the user for the code Garmin sent, then finish on the SAME client instance.
oauth1 = await auth.CompleteMfaAsync(login.MfaHandle!, userSuppliedCode);
}
else
{
oauth1 = login.OAuth1Token!;
}
- The pending login is held in memory on the client instance under a TTL (5 minutes) —
BeginLoginAsyncandCompleteMfaAsyncmust run on the sameGarminAuthClient(register it as a singleton in a web app). After the TTL,CompleteMfaAsyncthrowsGarminMfaSessionExpiredException. - A rejected code throws
GarminMfaExceptionand consumes the session — restart withBeginLoginAsync.
Storing the OAuth1 token
IOAuth1TokenStore is the seam for keeping each user's OAuth1 token between requests and processes, keyed by a caller-defined user key:
IOAuth1TokenStore store = new InMemoryOAuth1TokenStore(); // dev/tests; swap for an encrypted one
// After a successful login/MFA:
await store.SetAsync(appUserId, oauth1);
// Later, on demand:
var stored = await store.GetAsync(appUserId);
if (stored is null) { /* not connected — start a login */ }
else
{
var oauth2 = await auth.RefreshOAuth2Async(stored);
// ... use oauth2 ...
}
InMemoryOAuth1TokenStore ships for development and single-process use. A production host implements IOAuth1TokenStore over encrypted storage (e.g. ASP.NET Core Data Protection) — the OAuth1 token is a bearer secret. Only the OAuth1 token is ever stored; passwords are never persisted.
Notes
GarminOAuth1Tokenis a bearer secret — store it encrypted, never log it. ItsToString()is redacted.LoginAsyncremains a convenience for non-MFA accounts; it throwsGarminMfaRequiredExceptionif the account needs MFA (use the two-step flow above instead).- HttpClient: the SSO flow does its own redirect and cookie handling. The parameterless
GarminAuthClient()configures this correctly; if you pass your ownHttpClient, its handler must setAllowAutoRedirect = falseandUseCookies = false.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
NuGet packages (1)
Showing the top 1 NuGet packages that depend on GarminRunerz.Connect.Auth:
| Package | Downloads |
|---|---|
|
GarminRunerz.Connect
Uploads GarminRunerz workouts to a Garmin Connect account through the unofficial Garmin Connect web API. Supports creating, updating, scheduling and deleting workouts. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.5.0 | 168 | 8/19/2026 |