Search code examples
c#apple-news

How do I connect to the Apple News API from c#?


I'm trying to connect to Apple's News API. When generating the signature the results all appear the same from each of the different examples. However, I keep getting this error:

 {"errors":[{"code":"WRONG_SIGNATURE"}]}

Here is my c# code to generate the auth header.

public class Security
{
  public static string AuthHeader(string method, string url, object content=null)
  {
    var apiKeyId = "<YOUR KEY HERE...>"; //we get this value from our settings file.
    var apiKeySecret = "<YOUR SECRET HERE...>"; //we get this value from our settings file.
    if ( string.IsNullOrEmpty(apiKeyId) || string.IsNullOrEmpty(apiKeySecret)) return string.Empty;
    var encoding = new ASCIIEncoding();
    var dt = DateTime.Now.ToString(Constants.DateFormat);
    var canonicalRequest = string.Format("{0}{1}{2}", method, url, dt);
    var key = Convert.FromBase64String(apiKeySecret);
    var hmac = new HMACSHA256(key);
    var hashed = hmac.ComputeHash(encoding.GetBytes(canonicalRequest));
    var signature = Convert.ToBase64String(hashed);
    var authorizaton = string.Format(@"HHMAC; key={0}; signature={1}; date={2}", apiKeyId, signature, dt);
    return authorizaton;
  }
}

Short version of Constants class

public static class Constants
{
  public static readonly string ChannelId = "<YOUR CHANNEL ID HERE...>"; //again from our settings file
  public static readonly string BaseUrl = "https://news-api.apple.com";
  public static readonly string DateFormat = "yyyy-MM-ddTHH:mm:ssK";    
}

Short version of Actions class (SendCommand is the method that performs the request)

public class Actions
{
  public static string SendCommand(string action, string method)
  {
    var url = $"{Constants.BaseUrl}{action}";      
    var authheader = Security.AuthHeader(method, url, null);
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = method;
    request.Timeout = 1000;
    request.Headers.Add("Authorization", authheader);
    request.Accept = "application/json";
    request.ContentType = "application/json";
    var output = string.Empty;
    try
    {
      using (var response = request.GetResponse())
      {
        using (var reader = new StreamReader(response.GetResponseStream()))
          output = reader.ReadToEnd();
      }
    }
    catch (WebException e)
    {
      using (var reader = new StreamReader(e.Response.GetResponseStream()))
      {
        output = reader.ReadToEnd();
      }
    }          
    return output;
  }

  public static string ReadChannel()
  {
    var action = $"/channels/{Constants.ChannelId}";
    const string method = "GET";
    return SendCommand(action, method);
  }
}

I'm using the ReadChannel method for testing.

I've also tried examples in php and ruby with no luck.

Any ideas how to do this correctly?


Solution

  • Pasted the authorization string generated from the original code on this post into fiddler and I was able to get a successful response from the Apple News API. It seems like HttpWebRequest isn't including the Authorization header correctly and submitting the same request with the property PreAuthenticate = true corrects this issue (HttpWebRequest.PreAuthenticate). Also, with a GET request the ContentType needs to be omitted so I've added a conditional statement to account for this too.

    public class Actions
    {
      public static string SendCommand(string action, string method)
      {
        var url = $"{Constants.BaseUrl}{action}";      
        var authheader = Security.AuthHeader(method, url, null);
        var request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = method;
        request.Timeout = 1000;
        request.PreAuthenticate = true;
        request.Headers.Add("Authorization", authheader);
        request.Accept = "application/json";
        if(method.Equals("post", StringComparison.InvariantCultureIgnoreCase)) 
           request.ContentType = "application/json";
         var output = string.Empty;
        try
        {
          using (var response = request.GetResponse())
          {
            using (var reader = new StreamReader(response.GetResponseStream()))
              output = reader.ReadToEnd();
          }
        }
        catch (WebException e)
        {
          using (var reader = new StreamReader(e.Response.GetResponseStream()))
          {
            output = reader.ReadToEnd();
          }
        }          
        return output;
      }
    
      public static string ReadChannel()
      {
        var action = $"/channels/{Constants.ChannelId}";
        const string method = "GET";
        return SendCommand(action, method);
      }
    }