JsonServiceClient sending content-type header of text/plain when mode is no-cors

I am having an issue where the javascript/typescript version of the JsonServiceClient is always sending requests with the content-type of text/plain when the clients mode is set to no-cors.

I have tried to override the content-type header in the headers of the client directly and using the RequestFilters with no luck.

If I capture the request in Fiddler, modify the content-type header to application/json and resend the request I can get to my break points in my ServiceStack web service with no problem.

Any assistance would be appreciated.

Can you please include an example that does it with the raw HTTP Request Headers.

Here are the raw details of the request.

POST http://localhost:45001/auth/credentials?format=json HTTP/1.1
Host: localhost:45001
Connection: keep-alive
Content-Length: 49
Origin: http://localhost:45000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: /
Referer: http://localhost:45000/login
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: ss-opt=temp; X-UAId=%7B%22UserId%22%3A1%7D; ss-id=kl4AR4Ourft1wMu6qSNs; ss-pid=t0G0wPCnCNgfRLA7EBzt

{“UserName”:“a username”,“Password”:“a password”}

And here is the response:

HTTP/1.1 400 ValidationException
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Kestrel
Set-Cookie: ss-id=AvmgNKupytdmZoMdFLu0; path=/; samesite=lax; httponly
Set-Cookie: ss-pid=WwSXEawkMSnJsUarzMOe; expires=Thu, 24 Jun 2038 07:26:47 GMT; path=/; samesite=lax; httponly
Set-Cookie: ss-opt=temp; expires=Thu, 24 Jun 2038 07:26:47 GMT; path=/; samesite=lax; httponly
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Allow, Authorization, Origin
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: http://localhost:45000
X-Powered-By: ServiceStack/5.02 NETStandard/.NET
X-SourceFiles: =?UTF-8?B?QzpcRGV2ZWxvcG1lbnRcTWljcm9tbmlcbWljcm9tbmkubmV3XE1pY3JvbW5pLkNvcmUuU2VydmljZVxhdXRoXGNyZWRlbnRpYWxz?=
X-Powered-By: ASP.NET
Date: Sun, 24 Jun 2018 07:26:47 GMT

400
{“responseStatus”:{“errorCode”:“ValidationException”,“message”:“Validation failed: \r\n – ‘User Name’ should not be empty.\r\n – ‘Password’ should not be empty.”,“stackTrace”:"[Authenticate: 24/06/2018 7:26:47 AM]:\n[REQUEST: {provider:credentials}]\nServiceStack.FluentValidation.ValidationException: Validation failed: \r\n – ‘User Name’ should not be empty.\r\n – ‘Password’ should not be empty.\r\n at ServiceStack.FluentValidation.DefaultValidatorExtensions.ValidateAndThrow[T](IValidator1 validator, T instance, String ruleSet)\r\n at ServiceStack.Auth.CredentialsAuthProvider.Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)\r\n at ServiceStack.Auth.AuthenticateService.Post(Authenticate request)\r\n at ServiceStack.Host.ServiceRunner1.d__13.MoveNext()",“errors”:[{“errorCode”:“NotEmpty”,“fieldName”:“UserName”,“message”:"‘User Name’ should not be empty.",“meta”:{“PropertyName”:“User Name”}},{“errorCode”:“NotEmpty”,“fieldName”:“Password”,“message”:"‘Pa
44
ssword’ should not be empty.",“meta”:{“PropertyName”:“Password”}}]}}

Can you please also include the source code that sends this request.

The client-side code is as simple as this:

import { JsonServiceClient} from '@servicestack/client';

var client = new JsonServiceClient('http://localhost:45001');
client.mode = "no-cors";

var authenticate = new requests.security.AuthenticateRequest();
authenticate.UserName = username;
authenticate.Password = password;

client.postToUrl("/auth/credentials?format=json", authenticate).then((response => {
    this.doGetCurrentUser().then((user) => {
        userState.setUser(user);
    }).catch((error) => {
        this.showError("There was a problem authenticating your details");
    });
})).catch((error) => {
    this.showError("There was a problem authenticating your details");
});

and the server-side configuration in Startup.cs is:

public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration) => Configuration = configuration;

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseCors(builder =>
            builder.WithOrigins("http://localhost:45000").AllowAnyHeader().AllowAnyMethod().AllowCredentials()
        );

        app.UseServiceStack(new AppHost
        {
            AppSettings = new NetCoreAppSettings(Configuration)
        });
    }
}
public class AppHost : AppHostBase
{
    public AppHost() : base("Micromni.Core.Service", typeof(Services).Assembly) { }

    // Configure your AppHost with the necessary configuration and dependencies your App needs
    public override void Configure(Container container)
    {
        Plugins.Add(new TemplatePagesFeature()); // enable server-side rendering, see: http://templates.servicestack.net
        Plugins.Add(new CorsFeature(
            allowCredentials: true,
            allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
            allowedHeaders: "Content-Type, Allow, Authorization, Origin",
            allowOriginWhitelist: new[] {
                "http://localhost:45000"
            }));

        SetConfig(new HostConfig
        {
            AddRedirectParamsToQueryString = true,
            DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), false)
        });

        ConfigureAuth(container);
    }

    private void ConfigureAuth(Container container)
    {
        Plugins.Add(new AuthFeature(
            () => new AuthUserSession(),
            new IAuthProvider[] {
                new JwtAuthProvider() { HashAlgorithm = "RS256", PrivateKeyXml = AppSettings.GetString("PrivateKeyXml"), RequireSecureConnection = false },
                //new JwtAuthProvider() { AuthKey = AesUtils.CreateKey(), RequireSecureConnection = false },
                new CustomCredentialsAuthProvider(this, AppSettings)
            }));

        container.Register<ICacheClient>(new MemoryCacheClient());
        var userRep = new InMemoryAuthRepository();
        container.Register<IUserAuthRepository>(userRep);
    }
}

Like I said if I intercept the request with Fiddler and change the Content-Type back to application/json the server receives the request and hits my breakpoint with no problem.

I am using “@servicestack/client”: “^1.0.14”, on my client site and ServiceStack (5.0.2) on the web service.

Why are you using your own custom DTO and custom route instead of using the built-in Authenticate Request DTO?

Note specifying a no-cors Request Mode disables access to HTTP Headers which would be preventing sending the application/json Content-Type.

But if you access the Service as normal it will send the Request DTO with the pre-defined route which specifies the JSON endpoint. So can you try just sending the normal Authenticate DTO, e.g:

var request = Authenticate();
request.provider = "credentials";
request.userName = userName;
request.password = password;

client.post(request).then(response => { 
  //..
})

Also if you’re using TypeScript you can use the much nicer async/await syntax, e.g:

var response = await client.post(request);