0

I'm a rank noob at this, so excuse my ignorance. I've got an MVC web application to login, get the access and refresh tokens, and tenant list OK. I can even get it to refresh the refresh token. No problems.

When I try to run the GetInvoices endpoint either directly or via the sdk, I get 403 (skd) or 401 from the direct api call.

From the latest run with direct call I get this response

{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: 
System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Server: nginx
  Strict-Transport-Security: max-age=31536000
  WWW-Authenticate: OAuth Realm="api.xero.com"
  Cache-Control: no-store, no-cache, max-age=0
  Pragma: no-cache
  Date: Wed, 21 Jul 2021 11:19:56 GMT
  Connection: close
  X-Client-TLS-ver: tls1.2
  Content-Type: text/html; charset=utf-8
  Content-Length: 95
  Expires: Wed, 21 Jul 2021 11:19:56 GMT
}, Trailing Headers:
{
}}

I know that the access token and tenant id used in the GetInvoices step are correct because I checked them against the values pulled in from the auth steps character by character.

The app is being run in Visual Studio 2019, using the self-signed development SSL certificate.

Why is it rejecting the request?

my controllers have the following

private static readonly string Scopes = "openid offline_access profile email accounting.transactions accounting.contacts accounting.attachments";

        private static readonly string Scopes = "openid offline_access profile email accounting.transactions accounting.contacts accounting.attachments";

            string[] tenant = (string[])TempData.Peek("tenant");
        var client = new HttpClient();

        var formContent = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("summaryOnly", "true"),
        });

        client.DefaultRequestHeaders.Add("Authorization", (string)TempData.Peek("accessToken"));

        client.DefaultRequestHeaders.Add("Xero-Tenant-Id", tenant[0]);

        client.DefaultRequestHeaders.Add("Accept", "application/json");

        var response = await client.PostAsync("https://api.xero.com/api.xro/2.0/Invoices", formContent);
kevin rowe
  • 58
  • 3
  • If you're trying to retrieve invoices, why are you calling `PostAsync`? Shouldn't that be a `get`? The only difference between your scope and mine is that I don't have `accounting.attachements`, but I do have `accounting.settings`. Most recently when I've had a similar problem it's been down to the Tenant ID. – droopsnoot Jul 21 '21 at 17:10

2 Answers2

2

The SDK should handle this for you in the helper methods for the client and OAuth flow but i've included what looks like is missing from just a raw API call below.

Core API call - looks like you need to prefix the token with the Bearer string.

Authorization: "Bearer " + access_token

If you are wanting to use the SDK note that there is a sub Nuget package for OAuth helpers that will help you obtain an access token which you need to pass to core api calls.

https://github.com/XeroAPI/Xero-NetStandard/tree/master/Xero.NetStandard.OAuth2Client

SerKnight
  • 2,502
  • 1
  • 16
  • 18
  • When I added "Bearer " to direct call I get forbidden 403. Response. `{StatusCode: 403, ReasonPhrase: 'Forbidden', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers: { Server: nginx Xero-Correlation-Id: c750854a-6a79-4973-8c01-605012d1f57d X-AppMinLimit-Remaining: 9998 Cache-Control: no-store, no-cache, max-age=0 Pragma: no-cache Date: Thu, 22 Jul 2021 07:31:56 GMT Connection: close X-Client-TLS-ver: tls1.2 Content-Type: application/json Content-Length: 150 Expires: Thu, 22 Jul 2021 07:31:56 GMT } }` – kevin rowe Jul 22 '21 at 07:33
  • I also noticed that the token was being passed as "Xero-Tenant-Id" not "Xero-tenant-id" changing this did not help, nor recreating the app and only linking it to demo company. The app is shown as 'In Trial" in xero. I have not changed the users authority or permissions. – kevin rowe Jul 22 '21 at 07:59
  • That header doesn't seem to be case-sensitive, I send it in all lower case with success. – droopsnoot Jul 22 '21 at 08:06
  • changing it to GetAsync("https://api.xero.com/api.xro/2.0/Invoices?summaryOnly=true"); made no difference. I think you must be right about the tenant ID. I'll look further at that – kevin rowe Jul 22 '21 at 08:45
  • @kevinrowe - I tracked down that log and client ID associated and it says that the API application has been DELETED in our console. I think if you make a new app and swap in the new client id secret you should be good. – SerKnight Jul 22 '21 at 14:58
2

(DOH!) The Tenant returns an Id and a TenantId. I was using the Id.

Thanks to SerKnight and droopsnoot for helping.

I've added code from the OAuth2. The help does not mention to get and cast the return type of RequestAcessTokenAsync.

    XeroOAuth2Token authToken = (XeroOAuth2Token)await client.RequestAccessTokenAsync(authorisationCode);

also to check the state on return, you must set in xconfig. mine reads

    XeroConfiguration xconfig = new()
            {
                ClientId = Global.XeroClientID,
                ClientSecret = Global.XeroClientSecret,
                CallbackUri = new Uri(Global.RedirectURI),
                Scope = Global.XeroScopes,
                State = Global.XeroState
            };

            var client = new XeroClient(xconfig);

            return Redirect(client.BuildLoginUri());
kevin rowe
  • 58
  • 3