Self hosted server error on authentication in IUserAuthenticationService

Hi,

I’m trying to trial your self-hosted words-cloud instance but when submitting a request (.NET nuget package) the server throws an error when the library calls /connect/token

Stacktrace:

fail: Aspose.Words.Cloud.WebApp.Filters.CoreResultExceptionFilter[0]
      Unhandled Exception
      System.InvalidOperationException: Unable to resolve service for type 'Aspose.Words.Cloud.WebApp.Auth.IUserAuthenticationService' while attempting to activate 'Aspose.Words.Cloud.WebApp.Auth.AuthController'.
         at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ThrowHelperUnableToResolveService(Type type, Type requiredBy)
         at lambda_method6(Closure, IServiceProvider, Object[])
         at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

Docker run (Windows)

docker run --rm -i -p 45698:80 -e "LicensePublicKey=MyPublicKey" -e "LicensePrivateKey=MyPrivateKey" -e "Logging__LogLevel__Default=Information" -v "C:\Development\Docker\Share\Fonts:/fonts" -v "C:\Development\Docker\Share\Data:/data" -e "ClientId=MyClientId" -e "ClientSecret=MyClientSecret" aspose/words-cloud

Example call to trigger the error

 public async Task Run()
    {
        try
        {
            var config = new Configuration
            {
                ApiBaseUrl = "http://localhost:45698",
                ClientId = "MyClientId",
                ClientSecret = "MyClientSecret",
            };
            var wordsApi = new WordsApi(config);
            var fileName = @"C:\!tmp\test.docx";

            using var filestream = File.OpenRead(fileName);
            var uploadFileRequest = new UploadFileRequest(filestream, fileName);
            await wordsApi.UploadFile(uploadFileRequest);

			//fails here on the first request
			//...

        }
        catch (Exception ex)
        {
            var err = ex.Message;
        }
    }

Thanks for sharing the code, I will check it and share the results

Sorry for the late response.
I did multiple tests and couldn’t reproduce that issue on my side.
But I saw that you mentioned that you run container on Windows, please check that you docker is in linux containers mode, as self-hosted version is available only for linux

Hi,

Yeah we’re running in linux-container mode on Windows.

I think I’ve figured it out.

Looking at your code in the WebApp project it seems you only register a service for IUserAuthenticationService if “User” is specified in the config

 bool isAuthEnabled = !string.IsNullOrEmpty(this.Configuration.GetValue<string>("User"));
      if (isAuthEnabled)
      {
        JwtBearerExtensions.AddJwtBearer(services.AddAuthentication((Action<AuthenticationOptions>) (x =>
        {
          x.DefaultAuthenticateScheme = "Bearer";
          x.DefaultChallengeScheme = "Bearer";
        })), (Action<JwtBearerOptions>) (x =>
        {
          x.RequireHttpsMetadata = false;
          x.SaveToken = true;
          x.TokenValidationParameters = new TokenValidationParameters()
          {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = (SecurityKey) new SymmetricSecurityKey(UserAuthenticationService.TokenSecret),
            ValidateIssuer = false,
            ValidateAudience = false
          };
        }));
        services.AddScoped<IUserAuthenticationService, UserAuthenticationService>();
      }

If no User config is specified there doesn’t seem to be a default service registered so when AuthController tries to initialise it fails because it can’t find a service in DI to inject to the constructor. That’s the “Unable to resolve service for type” exception from the OP.

[ApiController]
  [Route("connect/token")]
  [Route("v4.0/words/connect/token")]
  public class AuthController : ControllerBase
  {
    private const string AuthorizationHeaderName = "Authorization";
    private const string BasicSchemeName = "Basic";
    private readonly IUserAuthenticationService authenticationService;

    public AuthController(IUserAuthenticationService authenticationService)
    {
      this.authenticationService = authenticationService;
    }

So “User” is a required parameter but your example for setting up the container at aspose/words-cloud - Docker Image omits them. My example in the OP also omits them so I’m not sure how you’ve managed to not hit the error. Are we running the same version of code?

The tutorial at How to Run Docker Container|Aspose Words Cloud Docs is also misleading and doesn’t mention that User is a required field (actual it mentions IF it is set which implies it isn’t required).

If it is a required field I’d recommend throwing an exception in your StartUp in WebApp letting the user know the value isn’t set and needs to be. If it isn’t required then the code needs to be able to handle no interface being registered or a no auth service registered.

It might also be worth mentioning that in your library ClientId maps to User and ClientSecret maps to Password. This wasn’t immediately obvious (to me at least).

So for others in the future the docker command line that works is

docker run --rm -d -p 45698:80 -e "LicensePublicKey=MyPublicKey" -e "LicensePrivateKey=MyPrivateKey" -e "User=testUser" -e "Password=testPassword" -e "Logging__LogLevel__Default=Information" -v "C:\Development\Docker\Share\Fonts:/fonts" -v "C:\Development\Docker\Share\Data:/data" aspose/words-cloud

And example call

try
{
	var config = new Configuration
	{
		ApiBaseUrl = "http://localhost:45698",
		ClientId = "testUser",
		ClientSecret = "testPassword",
	};
	var wordsApi = new WordsApi(config);

	var fileName = @"C:\!tmp\testFile.docx";
	using var filestream = File.OpenRead(fileName);
	var uploadFileRequest = new UploadFileRequest(filestream, fileName);
	await wordsApi.UploadFile(uploadFileRequest);

	var msg = "w00t, I made it!";

}
catch (Exception ex)
{
	var err = ex.Message;
}

I see, I share that information with the documentation team to put information more accurate