2017-07-26 7 views
0

J'essaye d'avoir accès à mes données d'échelles Withings/Nokia via oauth (.net core C#).Withings API - Signature invalide

Les instructions se trouvent à l'adresse: https://oauth.withings.com/en/api/oauthguide

et guide API ici: https://developer.health.nokia.com/api#step1

I ont atteint la partie 1 - je reçois un jeton et secret auth.

Partie 2 - manuellement J'ai récupéré un code autorisant l'utilisation par mon application de mes données d'échelles d'informations - c'est-à-dire le code d'autorisation suite au rappel (via la page des développeurs API). Je présume que cela ne doit être fait qu'une seule fois pour autoriser l'accès de mon application de manière permanente. J'ai codé en dur cette valeur dans mon code et je l'ai mise à jour si je réautorise l'application.

Maintenant, je suis coincé sur la partie 3 - obtention du jeton d'accès/secret. ERROR = signature invalide

(en utilisant la page ci-dessus, j'ai pu récupérer mes 4 années de données d'échelles, donc je sais que cela devrait fonctionner).

Ma signature de base est identique à la page de test API ci-dessus (hormis le nonce, la signature et l'horodatage).

Mon URL est identique à la page de test API ci-dessus (sauf le nonce et l'horodatage). Le mystère pour moi est pourquoi cela fonctionne pour la partie 1 et non la partie 3. Est-ce le code qui est mauvais ou simplement que le jeton de demande doit être autorisé contre les données de l'application/utilisateurs avant qu'une demande puisse être faite? Mais je n'ai pas besoin de réautoriser avec l'utilisateur à chaque fois?

J'ai à l'origine foiré la partie 1 et ai donné une erreur de signature invalide - c'était clairement un problème avec la signature - mais j'ai revérifié la signature dans la partie 3 et c'est bon.

private const string AUTH_VERSION = "1.0"; 
private const string SIGNATURE_METHOD = "HMAC-SHA1"; 

private const string BASE_URL_REQUEST_AUTH_TOKEN = "https://developer.health.nokia.com/account/request_token"; 
private const string BASE_URL_REQUEST_ACCESS_TOKEN = "https://developer.health.nokia.com/account/access_token"; 

...

Withings w = new Withings(); 
OAuthToken t = await w.GetOAuthToken(); 
string token = t.OAuth_Token; 
string secret = t.OAuth_Token_Secret; 
OAuthAccessToken at = await w.GetOAuthAccess(t); 
string aToken = at.OAuth_Token; 
string aTokenSecret = at.OAuth_Token_Secret; 

...

public async Task<OAuthAccessToken> GetOAuthAccess(OAuthToken authToken) 
     { 
      OAuthAccessToken token = new OAuthAccessToken(); 

      try 
      { 
       string random = GetRandomString(); 
       string timestamp = GetTimestamp(); 

       string baseSignature = GetOAuthAccessSignature(authToken, random, timestamp); 
       string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, authToken.OAuth_Token_Secret); 
       string codeSignature = UrlEncode(hashSignature); 

       string requestUrl = GetOAuthAccessUrl(authToken, codeSignature, random, timestamp); 

       HttpResponseMessage response = await client.GetAsync(requestUrl); 
       string responseBodyAsText = await response.Content.ReadAsStringAsync(); 

       string[] parameters = responseBodyAsText.Split('&'); 
       token.OAuth_Token = parameters[0].Split('=')[1].ToString(); 
       token.OAuth_Token_Secret = parameters[1].Split('=')[1].ToString(); 

      } 
      catch (Exception ex) 
      { 

      } 
      return token; 
     } 

private string GetOAuthAccessSignature(OAuthToken authToken, string random, string timestamp) 
     { 
      var urlDict = new SortedDictionary<string, string> 
      { 
       //{ "oauth_consumer_key", CONSUMER_KEY}, 
       { "oauth_nonce", random}, 
       { "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)}, 
       { "oauth_timestamp", timestamp}, 
       { "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN }, 
       { "oauth_version", AUTH_VERSION} 
      }; 

      StringBuilder sb = new StringBuilder(); 
      sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_ACCESS_TOKEN) + "&oauth_consumer_key%3D" + CONSUMER_KEY); 

      int count = 0; 
      foreach (var urlItem in urlDict) 
      { 
       count++; 
       if (count >= 1) sb.Append(UrlEncode("&")); 
       sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value)); 
      } 

      return sb.ToString(); 
     } 

     private string GetOAuthAccessUrl(OAuthToken authToken, string signature, string random, string timestamp) 
     { 
      var urlDict = new SortedDictionary<string, string> 
      { 
       { "oauth_consumer_key", CONSUMER_KEY}, 
       { "oauth_nonce", random}, 
       { "oauth_signature", signature }, 
       { "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)}, 
       { "oauth_timestamp", timestamp}, 
       { "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN }, 
       { "oauth_version", AUTH_VERSION} 
      }; 

      StringBuilder sb = new StringBuilder(); 
      sb.Append(BASE_URL_REQUEST_ACCESS_TOKEN + "?"); 

      int count = 0; 
      foreach (var urlItem in urlDict) 
      { 
       count++; 
       if (count > 1) sb.Append("&"); 
       sb.Append(urlItem.Key + "=" + urlItem.Value); 
      } 

      return sb.ToString(); 
     } 

Notes:

  • J'ai commandé mes paramètres
  • J'ai un urlencode décent (correcte moi wr ong)
  • J'ai un hash HMAC-SHA1 (me corriger mal)
  • pas intéressé à utiliser les bibliothèques ouvertes - Je veux corriger ce code sans outils tiers

Ci-dessous sont les méthodes d'aide, je suis en utilisant:

private string ComputeHash(string data, string consumerSecret, string tokenSecret = null) 
     { 
      // Construct secret key based on consumer key (and optionally include token secret) 
      string secretKey = consumerSecret + "&"; 
      if (tokenSecret != null) secretKey += tokenSecret; 

      // Initialise with secret key 
      System.Security.Cryptography.HMACSHA1 hmacsha = new System.Security.Cryptography.HMACSHA1(Encoding.ASCII.GetBytes(secretKey)); 
      hmacsha.Initialize(); 

      // Convert data into byte array 
      byte[] dataBuffer = Encoding.ASCII.GetBytes(data); 

      // Computer hash of data byte array 
      byte[] hashBytes = hmacsha.ComputeHash(dataBuffer); 

      // Return the base 64 of the result 
      return Convert.ToBase64String(hashBytes); 
     } 

     // Get random string 
     private string GetRandomString() 
     { 
      return Guid.NewGuid().ToString().Replace("-", ""); 
     } 

     // Get timestamp 
     private string GetTimestamp() 
     { 
      var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 
      return Convert.ToInt64(ts.TotalSeconds).ToString(); 
     } 

     // Url Encode (as Uri.Escape is reported to be not appropriate for this purpose) 
     protected string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~"; 
     protected string UrlEncode(string value) 
     { 
      var result = new StringBuilder(); 
      foreach (var symbol in value) 
      { 
       if (UnreservedChars.IndexOf(symbol) != -1) 
        result.Append(symbol); 
       else 
        result.Append('%' + $"{(int)symbol:X2}"); 
      } 
      return result.ToString(); 
     } 

Merci, Dan.

Répondre

1

Résolu - c'était un problème avec ma compréhension du fonctionnement d'oauth.

Étape 1 - Obtenez un jeton (ce qui vous permet de faire des demandes en fonction de votre demande de compte api)

Étape 2 - Créer une URL (en utilisant token 2 minutes ci-dessus) qui redirige l'utilisateur pour autoriser vos applications Withings api à utiliser le compte d'un utilisateur spécifique. De même est retourné comme vous avez passé - mais maintenant, il sera autorisé à faire la demande à l'étape 3.

Étape 3 - Demande un jeton d'accès - cela vous donnera une chaîne de jeton et secret qui permet à votre accès continu au compte de cet utilisateur (pour votre application de compte api).

Étape 4 - Demande de données - méthode similaire à toutes les étapes précédentes - assez facile. Renvoie une grande chaîne de données. Lisez les documents de l'API que vous pouvez filtrer - ce que je ferai car j'ai environ 4/5 ans de données de poids «intéressantes». Je faisais l'étape 1 et ensuite je faisais l'étape 3 en pensant que le code retourné à l'étape 2 (sans avoir remarqué qu'il était le même que celui entré) pourrait être stocké et utilisé pour l'étape 3 sans avoir à réautoriser .

Vous pouvez réellement (et ce que je l'ai fait) est de suivre l'interface de démonstration de l'API pour générer le jeton d'authentification et secret à l'étape 3 et c'est tout ce que vous devez continuer à demander des données. Vous n'avez besoin qu'une seule fois de l'autorisation de l'utilisateur et stockez l'authentification token/secret de l'étape 3 sur un compte d'utilisateur/un magasin quelconque.

Notez également que vous pouvez appeler des notifications - un nouveau poids déclenche votre site Web pour actualiser automatiquement les données. Super si vous voulez juste vous connecter et voir les dernières données sans avoir le déclenchement manuel d'un rafraîchissement des données/causer un retard supplémentaire. Sachez que l'API dispose d'options de filtrage. Assurez-vous de lire ces options.

est ici un code (de base) qui peut être d'une certaine utilité:

 public async Task<string> GetData_BodyMeasures() 
    { 
     string rawdata = ""; 

     try 
     { 
      string random = GetRandomString(); 
      string timestamp = GetTimestamp(); 

      string baseSignature = GetDataSignature_BodyMeasure(random, timestamp); 
      string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, ACCESS_OAUTH_TOKEN_SECRET); 
      string codeSignature = UrlEncode(hashSignature); 

      string requestUrl = GetData_BodyMeasure_Url(codeSignature, random, timestamp); 

      HttpResponseMessage response = await client.GetAsync(requestUrl); 
      string responseBodyAsText = await response.Content.ReadAsStringAsync(); 

      rawdata = responseBodyAsText; 

     } 
     catch (Exception ex) 
     { 

     } 
     return rawdata; 
    } 




    private string GetDataSignature_BodyMeasure(string random, string timestamp) 
    { 
     var urlDict = new SortedDictionary<string, string> 
     { 
      { "oauth_consumer_key", CONSUMER_KEY}, 
      { "oauth_nonce", random}, 
      { "oauth_signature_method", SIGNATURE_METHOD}, 
      { "oauth_timestamp", timestamp}, 
      { "oauth_token", ACCESS_OAUTH_TOKEN }, 
      { "oauth_version", AUTH_VERSION}, 
      { "userid", USER_ID } 
     }; 

     StringBuilder sb = new StringBuilder(); 
     sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_BODY_MEASURE) + "&action%3Dgetmeas"); 

     int count = 0; 
     foreach (var urlItem in urlDict) 
     { 
      count++; 
      if (count >= 1) sb.Append(UrlEncode("&")); 
      sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value)); 
     } 

     return sb.ToString(); 
    } 


    private string GetData_BodyMeasure_Url(string signature, string random, string timestamp) 
    { 
     var urlDict = new SortedDictionary<string, string> 
     { 
      { "action", "getmeas"}, 
      { "oauth_consumer_key", CONSUMER_KEY}, 
      { "oauth_nonce", random}, 
      { "oauth_signature", signature }, 
      { "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)}, 
      { "oauth_timestamp", timestamp}, 
      { "oauth_token", ACCESS_OAUTH_TOKEN }, 
      { "oauth_version", AUTH_VERSION}, 
      { "userid", USER_ID } 
     }; 

     StringBuilder sb = new StringBuilder(); 
     sb.Append(BASE_URL_REQUEST_BODY_MEASURE + "?"); 

     int count = 0; 
     foreach (var urlItem in urlDict) 
     { 
      count++; 
      if (count >= 1) sb.Append("&"); 
      sb.Append(urlItem.Key + "=" + urlItem.Value); 
     } 

     return sb.ToString(); 
    }