philosowaffle/peloton-to-garmin

[Feature] Translate Garth to C# and use in P2G

philosowaffle opened this issue ยท 11 comments

https://github.com/matin/garth

Seems someone has figured out how to authenticate with Garmin using proper OAuth tokens instead of storing u/p. See if I can implement this in C# and use for P2G.

Will use oauth library: https://github.com/DuendeSoftware/Duende.AccessTokenManagement or IdentityModel? These may not support OAuth1 which could be deal breaker.

Copy rest of impl from garth: https://github.com/matin/garth/blob/main/garth/sso.py

Maybe you will be interested. It's dirty code but it's works. However, without MFA support yet, (But I want to update my asap so it was not priority for me)

https://github.com/lswiderski/GarminWeightScaleUploader/blob/master/Libs/garmin-connect-client/GarminConnectClient.Lib/Services/Client.cs

OAuth.DotNetCore used as OAuth1 Client

Awesome thanks I'll take a look! I've got most of the flow stubbed out on my end, just about to dig into seeing if it actually works + figuring out the OAuth portion. I was a bit worried about OAuth.DotNetCore no longer being actively maintained, but if its still the best option I'll use it.

@philosowaffle First I tried with Duende.AccessTokenManagement but without success and and after tried few more. It was the first that worked. Garmin still use OAuth1 and not so many libraries of this standard are still maintaned :/

Ah perfect, sounds like you have saved me a lot of headache then :)

@lswiderski I appear to have everything working right up until the final step, exchanging the oauth1 for an oauth2 token. I'm getting a 500 response from POST https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0. Did you happen to run into a similar error while you were implementing? Trying to figure out what I may have messed up on my request.

@philosowaffle
Did you set up Content-type to 'application/x-www-form-urlencoded'? Without that I got 500 too.
Other cookies are not important for that exchange. It works well in Postman too with authorization type OAuth 1.0

This snippet works for me:

 public async Task<OAuth2Token> GetOAuth2Token(string accessToken, string tokenSecret)
 {
     
     OAuthRequest oauthClient2 = OAuthRequest.ForProtectedResource("POST", CONSUMER_KEY, CONSUMER_SECRET, accessToken, tokenSecret);

     oauthClient2.RequestUrl = $"https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0";
     string auth2 = oauthClient2.GetAuthorizationHeader();

     HttpWebRequest request2 = (HttpWebRequest)WebRequest.Create(oauthClient2.RequestUrl);

     request2.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
     request2.Headers.Add("Authorization", auth2);
     request2.Headers.Add("User-Agent", USER_AGENT);
     request2.Method = "POST";
     
     var oauthContent2 = "";
     OAuth2Token token = null;
     using (var oAuthResponse = (HttpWebResponse)request2.GetResponse())
     {

         using (var responseStream = oAuthResponse.GetResponseStream())
         using (var reader = new StreamReader(responseStream))
             oauthContent2 = reader.ReadToEnd();
         token = DeserializeData<OAuth2Token>(oauthContent2);

     }

     return token;
 }

Yup, I think you're right, I'm missing that header. Thank you!

matin commented

@philosowaffle @lswiderski feel free to ping me if you need clarifications on the SSO logic.