Session handling with custom session

I’m developing against a legacy application that uses ASP.NET Membership (no fun). I’ve tried to create custom auth providers and custom sessions to map ServiceStack authentication against the membership. My result is in the following gist:

https://gist.github.com/andrejohansson/4985add5a830753fbaa8

But when I run my unit tests I get random failures when running in parallell, but if I run them one by one, I get no errors and “expected behaviour”. So it seems that I’ve missed something in ServiceStacks Authentication flow and I bleed sessions.

Could you please explain my wrongdoings?

I wont be able to identify the issue is without being able to run the code and seeing what the actual errors/stack traces are.

But if you’re saying the issue happens when running in parallel than that could point to a multi-threading issue. Since the AuthProvider is a singleton:

   var userRep = new AspNetMembershipAuthRepository(
        container.Resolve<IMembershipProviderWrapper>(),
        container.Resolve<IRoleProviderWrapper>(),
        container.Resolve<IMyDemoSystemDb>());

     container.Register<IUserAuthRepository>(userRep);

You need to make sure that each of its dependencies are ThreadSafe as well as the AuthProvider uses the same singleton instances for each request.

You can either add locks to synchronize access or resolve new instances of non ThreadSafe dependencies from the IOC inside your implementation with HostContext.TryResolve<T>.

After some more investigating I have found the following repeatable behaviour (I am using servicestack 4.0.47) of unauthorized from one of my tests:

If I start my service and open the swagger ui (which now contains username and password textboxes for basicauth). Then I go to the POST /authenticate service and leave everything blank. I also leave the username and password textboxes blank in the swagger header. If I execute, I get unauthorized as expected.

Then I enter valid credentials in the username and password box and execute again. I verify that the browser sends a basic auth header with the request and it does. But I still get unauthorized. This is not expected

Then do an authentication using a POST to /authenticate with a model containing username and password. When I do this, the authentication flow will trigger my custom sessions and repos etc which wasn’t done before. I get authenticated and following requests will succeed. But I don’t want to use the CredentialProvider. I only want to use the BasicAuthProvider but it doesn’t seem to trigger my custom providers at all.

Here is my configuration for the apphost and credentials:

  private void ConfigureAuthentication(Container container)
  {
     var userRep = new AspNetMembershipAuthRepository(
        container.Resolve<IMembershipProviderWrapper>(),
        container.Resolve<IRoleProviderWrapper>(),
        container.Resolve<IMyDemoSystemDb>());
     container.Register<IUserAuthRepository>(userRep);

     var authSession = new AspnetMembershipAuthSession(container.Resolve<IRoleProviderWrapper>());

     var authProviders = new IAuthProvider[] { new BasicAuthProvider() };

     var authFeature = new AuthFeature(() => authSession, authProviders);

     // See: https://servicestack.uservoice.com/forums/176786-feature-requests/suggestions/6440387-authfeature-serviceroutes-option-to-hide-route
     authFeature.DeleteSessionCookiesOnLogout = true;
     authFeature.IncludeRegistrationService = false;
     authFeature.IncludeAssignRoleServices = false;
     authFeature.ServiceRoutes[typeof(AuthenticateService)] = new[]
     {
        "/authenticate",
        "/authenticate/{provider}"
     };

Does that give any further clue? I cannot see anything that the AuthProvider should use something else than singeltons.

No, it’s still trying to guess in the Dark without access to a debugger or HTTP Headers / Stack Traces / logging, etc.

The HTTP Basic Auth should be calling the BasicAuthProvider which should be calling the Authentication. Are you sure none of your Auth Repository API’s are being called?

I’m quite sure, I set a breakpoint in OnAuthenticated in the custom session and in TryAuthenticate in the custom auth repository. Both these breakpoints fire in the case where I’m posting an auth model but neither of them fire when I’m sending credentials with basic auth.

You can try registering your own local copy of BasicAuthProvider which will let you debug into the BasicAuthProvider code which will hopefully provide better insight into what’s happening.

I finally found my issue, I was blind, I missed that authfeature took a func and not two parameters so changing:

     var authSession = new AspnetMembershipAuthSession(container.Resolve<IRoleProviderWrapper>());

     var authProviders = new IAuthProvider[] { new BasicAuthProvider() };

     var authFeature = new AuthFeature(() => authSession, authProviders);

To

     var authProviders = new IAuthProvider[] { new BasicAuthProvider() };

     var authFeature = new AuthFeature(() => new AspnetMembershipAuthSession(container.Resolve<IRoleProviderWrapper>()), authProviders);

Fixed the issue since I create a new session per call instead of re-using the same session for everyone (doh!).

1 Like

But I also think I’ve found a bug (or maybe an unexpected behaviour from my side):

  [Test]
  public void AuthenticatedDefaultRequestIsOk()
  {
     AuthenticatedClient.AlwaysSendBasicAuthHeader = true;
     var request = new Authenticate();
     RequestAndExpectHttpCode<AuthenticateResponse>(AuthenticatedClient, HttpVerbs.Get, request,
        HttpStatusCode.OK);
  }

If I send an empty request to the /authenticate service with basicauth headers set, I get unauthorized. I expected that request to be valid. But it seems that servicestack is checking the credentials request first and throws an unauthorised request. Shouldn’t it check both the request object and the http auth headers for credentials?

The /auth route on its own (i.e. without a provider) doesn’t attempt to authenticate, it returns the current authenticated session if they’re already authenticated or throws an 401 Unauthorized HTTP Error if they’re not. Users can use this to check whether or not they’re already authenticated with the server.

That explains it, setting the provider gave me the expected results. Thanks.