User is not authorized for this request. error received with ServiceAPI

Vote:
 

I'm using EPIServer Service APIs to import products/catalog from xml file through a scheduled job. 

The version is close/latest to the version I'm using in my project. https://world.episerver.com/documentation/class-libraries/rest-apis/service-api/

  <package id="EPiServer.ServiceApi" version="5.4.5" targetFramework="net47" />
  <package id="EPiServer.ServiceApi.Commerce" version="5.4.5" targetFramework="net47" />

And the commerce version is 

<package id="EPiServer.Commerce" version="13.12.0" targetFramework="net47" />

Implementation 

I've got a scheduled job that reads an XML through asset pane and imports the entries / catalog from that file. This file is using Service APIs to import an entry and it's associations with following code base, in following order - 

import the entries -

                var result = Post("/episerverapi/commerce/entries",
               new StringContent(json, Encoding.UTF8, "application/json"));

Import the prices - 

  var pricePostMultiple = Post($"/episerverapi/commerce/entries/{entry.Code}/prices/multiple",
                        new StringContent(JsonConvert.SerializeObject(priceList), Encoding.UTF8,
                            "application/json")).Result;

Node entry relations - 

 var nodeEntryRelations = Post(
                            $"/episerverapi/commerce/entries/{entry.Code}/nodeentryrelations",
                            new StringContent(entryRelation, Encoding.UTF8, "application/json"));

Catalog Association - 

  var entryAssociation = Post($"/episerverapi/commerce/entries/{entry.Code}/associations",
                            new StringContent(entryRelation, Encoding.UTF8, "application/json"));

Issue 

We've been getting intermittent response from the prod server where the each of above APIs would fail at some point with the response "User is not authorized for this request". Though the user which this API client is using exists with all the required credentials and has enough permissions for functions to read/write the catalog.

The API is making connection to the Service API through OAuth2Client. Here is the complete file -

public abstract class BaseActor
    {
        public abstract string Execute();
        private readonly string _baseUrl;
        private readonly OAuth2Client _oAuth2Client;

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseActor"/> class.
        /// </summary>
        protected BaseActor()
        {
            _baseUrl = SiteDefinition.Current.SiteUrl.AbsoluteUri;
            _oAuth2Client = new OAuth2Client(new Uri(_baseUrl + "episerverapi/token"));
        }

        private async Task<HttpClient> GetHttpClient()
        {
            try
            {
                var userName = ConfigurationManager.AppSettings["username"];
                var password = ConfigurationManager.AppSettings["password"];
                var token = await _oAuth2Client.RequestResourceOwnerPasswordAsync(userName, password);
                if (token.IsError)
                {
                    return null;
                }
                var client = new HttpClient()
                {
                    BaseAddress = new Uri(_baseUrl)
                };
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
                return client;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        /// <summary>
        /// Posts the specified URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="content">The content.</param>
        /// <returns></returns>
        protected async Task<HttpResponseMessage> Post(string url, HttpContent content)
        {
            var client = await GetHttpClient();
            return client?.PostAsync(url, content).Result;
        }

        /// <summary>
        /// Puts the specified URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="content">The content.</param>
        /// <returns></returns>
        protected async Task<HttpResponseMessage> Put(string url, HttpContent content)
        {
            var client = await GetHttpClient();
            return client?.PutAsync(url, content).Result;
        }

        /// <summary>
        /// Gets the specified URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <returns></returns>
        protected async Task<HttpResponseMessage> Get(string url)
        {
            var client = await GetHttpClient();
            return client?.GetAsync(url).Result;
        }

        /// <summary>
        /// Gets the specified URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="completionOption">The completion option.</param>
        /// <returns></returns>
        protected async Task<HttpResponseMessage> Get(string url, HttpCompletionOption completionOption)
        {
            var client = await GetHttpClient();
            return client?.GetAsync(url, completionOption).Result;
        }

        /// <summary>
        /// Puts the specified URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <returns></returns>
        protected async Task<HttpResponseMessage> Delete(string url)
        {
            var client = await GetHttpClient();
            return client?.DeleteAsync(url).GetAwaiter().GetResult();
        }
    }

The issue is only occuring at production which is on-premise. Does it have to do anything with the permissions those need to be allowed? I've no clue why this is failing or even where to look for if this is a permission issue. 

Thanks for your help.

#254135
Apr 30, 2021 11:25
Vote:
 

While I don't have a good answer for your question, I'd like to point out that creating HttpClient for each request is considered bad practice and should be avoided. Better to create one instance and reuse

#254141
Apr 30, 2021 14:38
Vote:
 

Thanks Quan. 

Do you think this might happen due to parallel requests using separate tokens over async call and might not get authorized? I'll try to make it into 1 call though to see if it works. In some random cases I get authorized and the call completes with appropriate token.

#254256
May 03, 2021 12:29
Vote:
 

Could be. I'm not an Oauth expert (I should be), but it is possible that if you have parallel requests, the later token might invalidate the early one. It might also be a good idea to reuse the bearer token until it's expired (again, not an expert)

#254257
May 03, 2021 12:42
Vote:
 

Thanks Quan. I'll give it a try. Though on my local environment I never had this problem. Do you have any example how to reuse the token?

#254483
May 07, 2021 9:30
Praful Jangid - May 10, 2021 12:08
Hey Manoj, any luck so far?
Manoj Kumawat - May 10, 2021 12:48
I haven't yet Praful, Have you got any example on how to reuse the same authentication flow for subsequent/parallel calls.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.