2
votes

I have developed a set of ServiceStack web services which have been working well for some months now primarily from a WPF application which uses the ServiceStack client libraries.

Due to some other issues arising with the services with other developers, I decided to look at the requests and the responses between the existing WPF application and the services using Fiddler.

I noticed that on every request to a web service method, ith shows two requests, the first returns response 401 and the second returns 200.

All of our web service methods are set up to use the "Any" functionality allowing any verb from the client - i.e.-

[Authenticate]
public object Any(DriverQuery request)
{
    var driver = DriverRepository.GetDriver<DriverEntity>(request.DriverUserId);
    return new DriverResponse { Driver = driver };
}

The client just uses the JsonServiceClient.Send method - i.e.

var response = client.Send(new DriverQuery { DriverUserId = driverUserId });

We are using basic authentication.

I am concerned as to why this is happening and if this is normal or not, and if not, what am I doing wrong?

Additional info as requested

Request which returns the 401 response:-

POST http://www.*******.uk/Api/jsv/reply/ClientsQuery HTTP/1.1
Accept: application/jsv
LoggedOnUserNameHeader: ******
User-Agent: ServiceStack .NET Client 4.038
Accept-Encoding: gzip,deflate
Content-Type: application/jsv
Host: dev.carbenefitsolutionstest.uk
Content-Length: 2
Expect: 100-continue

{}

The 401 Response

HTTP/1.1 401 Unauthorized
Cache-Control: private
Vary: Accept
Server: Microsoft-IIS/8.5
WWW-Authenticate: basic realm="/auth/basic"
X-Powered-By: ServiceStack/4.038 Win32NT/.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Access-Control-Allow-Origin, Authorization, Content-Type
X-AspNet-Version: 4.0.30319
Set-Cookie: ss-id=5JfAHCT3axaUqDA9eQTA; path=/; HttpOnly
Set-Cookie: ss-pid=yEGg9GfCSbwLZKL4RYsi; expires=Wed, 14-Mar-2035 08:47:48 GMT; path=/; HttpOnly
X-Powered-By: ASP.NET
Date: Sat, 14 Mar 2015 08:47:48 GMT
Content-Length: 0

Request which returns the 200 Response:-

POST http://www.*******.uk/Api/jsv/reply/ClientsQuery HTTP/1.1
Accept: application/jsv
LoggedOnUserNameHeader: *******
User-Agent: ServiceStack .NET Client 4.038
Accept-Encoding: gzip,deflate
Content-Type: application/jsv
Host: dev.carbenefitsolutionstest.uk
Cookie: ss-id=5JfAHCT3axaUqDA9eQTA; ss-pid=yEGg9GfCSbwLZKL4RYsi
Authorization: Basic d2Vic2VydmljZTpzdDNybDFuZw==
Content-Length: 2
Expect: 100-continue

{}

The 200 Response

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/jsv
Vary: Accept
Server: Microsoft-IIS/8.5
X-Powered-By: ServiceStack/4.038 Win32NT/.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Access-Control-Allow-Origin, Authorization, Content-Type
X-AspNet-Version: 4.0.30319
Set-Cookie: X-UAId=1; expires=Wed, 14-Mar-2035 08:47:48 GMT; path=/; HttpOnly
X-Powered-By: ASP.NET
Date: Sat, 14 Mar 2015 08:47:48 GMT
Content-Length: 78618

I have created a base class derived from the ServiceStack JsvServiceClient, which handles providing the authentication information. The code for this is shown below.

public abstract class BaseServiceClient : JsvServiceClient
{
    protected BaseServiceClient(string baseUri, TimeSpan serviceTimeout, string currentUserName)
        : base(baseUri)
    {
        Headers.Add(CustomHeaders.LoggedOnUserNameHeader, currentUserName);
        UserName = WebServiceSecurity.GetMasterUsername();
        Password = WebServiceSecurity.GetMasterPassword();
        Timeout = serviceTimeout;
    }
}

Service Authentication Configuration Code

Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {new BasicAuthProvider()}));
    var userRepository = new InMemoryAuthRepository();
    container.Register<IUserAuthRepository>(userRepository);

    string hash;
    string salt;

    new SaltedHash().GetHashAndSaltString(WebServiceSecurity.GetMasterPassword(), out hash, out salt);
userRepository.CreateUserAuth(new UserAuth
{
    Id = 1,
    UserName = *UserName*,
    PasswordHash = ******,
    Salt = ******,
    Roles = new List<string> { *Role* },
}, *Password*);

It is important to note that we only make one call to the web service method and both of these requests result as part of this single call

If anyone could assist or confirm that this is normal behavior, it would be very much appreciated.

Many thanks in advance

Simon

1
Please update your question to include the raw HTTP Request / Responses from Fiddler as well as your AuthFeature registration that shows how you've registered BasicAuth. Also the code you used to configure the JsonServiceClient (with any passwords omitted).mythz
Hi - Apologies - Additional information as requested. Many thanks. SimonSimon Martin-Lemmon

1 Answers

3
votes

The first request fails because it's sending a non-Authenticated request to a protected Service, so the Service is correctly responding with a 401 UnAuthenticated to indicate this. The error response also indicates that the Service supports HTTP Basic Auth with the WWW-Authenticate: basic realm="/auth/basic" response header.

When the subsequent request is made it includes the Basic Auth info which can be seen in the HTTP Header Authorization: Basic d2Vic2VydmljZTpzdDNybDFuZw== which as it holds a valid UserName/Password, then succeeds.

You can skip the initial UnAuthenticated Request and Auth Challenge Response by always sending the Basic Auth Info with every request.

You can set the AlwaysSendBasicAuthHeader property to instruct ServiceStack's ServiceClients to always send the Basic Auth Info with every request, e.g:

client.AlwaysSendBasicAuthHeader = true;

You can find other Authentication examples using the ServiceStack ServiceClients in the ServiceClients AuthTests.cs.