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.
Vote:
 

It seems to have been resolved since I had the HttpClient instance at the constructor. That way it makes 1 instance of HttpClient and the authorize token (internally being generated within HttpClient). 

Thanks Quan.

#254980
May 17, 2021 10:36
Vote:
 

Hello Manoj

I'm working with the support case related this thread.

Is problem still persist?

In your local developement, did you setup with https?

At the moment, I don't have any idea but I suggest a solution:

When Job has error, we will retry one more time: re get token and re-post

#258853
Jul 13, 2021 3:45
Vote:
 

Thanks Cuvu,

We've tried it earlier but the intermittent behavior is there. Could you please list out the must-have configuration settings on production? On local without/with https it works just fine. 

#258855
Jul 13, 2021 8:27
Vote:
 

Hello

You can find documentation here: https://world.episerver.com/documentation/developer-guides/Optimizely-Service-API/installation-and-configuration/

But I think it's not related configuration because as you said error occurs some points.

Could you change logic as I mentioned: when error occurs, job will retry one more time and log bug for that request?

#258856
Jul 13, 2021 10:19
Vote:
 

Cuvu,

We have tried re-running the job iteration when it throws an exception while generating the token request. There is no full stack trace except the message 'User is not authorized for this request' 

What do you think might be the reason behind this error? 

#258857
Jul 13, 2021 10:31
Vote:
 

@Manoj can we see what authentication options you've setup on the Service API side?

#258859
Jul 13, 2021 11:30
Manoj Kumawat - Jul 14, 2021 7:30
Hello Surjit, I'm sending a Bearer token generated with oauth2. Below are the options
https://prnt.sc/1b0erxx
Surjit Bharath - Jul 14, 2021 14:33
The original issues I'm reading is that you get intermittent access issues despite it working most of the time.

I would be checking what the session life time of the token is and what the authentication life time is. Those can be set up the token provider but also changed by the resource provider (service api). Check these, see if they are long enough.

Additionally you may want to enable slidingexpiration. So everytime the token gets used in a request, the 'timeout' gets reset.
Vote:
 

Hello, Manoj

I have checked, 'User is not authorized for this request'  message come from AuthorizePermissionAttribute attribute in Episerver ServiceAPI.

I have attached some screenshot but I haven't figure out yet

This attribute is used in Controller

#258901
Jul 14, 2021 6:13
* 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.