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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GarminRunerz.Connect.Auth" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GarminRunerz.Connect.Auth" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="GarminRunerz.Connect.Auth" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GarminRunerz.Connect.Auth --version 0.5.0
                    
#r "nuget: GarminRunerz.Connect.Auth, 0.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GarminRunerz.Connect.Auth@0.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GarminRunerz.Connect.Auth&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=GarminRunerz.Connect.Auth&version=0.5.0
                    
Install as a Cake Tool

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 tokenstore it encryptedrefresh 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) — BeginLoginAsync and CompleteMfaAsync must run on the same GarminAuthClient (register it as a singleton in a web app). After the TTL, CompleteMfaAsync throws GarminMfaSessionExpiredException.
  • A rejected code throws GarminMfaException and consumes the session — restart with BeginLoginAsync.

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

  • GarminOAuth1Token is a bearer secret — store it encrypted, never log it. Its ToString() is redacted.
  • LoginAsync remains a convenience for non-MFA accounts; it throws GarminMfaRequiredException if 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 own HttpClient, its handler must set AllowAutoRedirect = false and UseCookies = false.
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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