Reputation: 1500
I am trying to set up authentication with my ServiceStack service by following this tutorial.
My service is decorated with the [Authenticate]
attribute.
My AppHost looks like this:
public class TestAppHost : AppHostHttpListenerBase
{
public TestAppHost() : base("TestService", typeof(TestService).Assembly) { }
public static void ConfigureAppHost(IAppHost host, Container container)
{
try
{
// Set JSON web services to return idiomatic JSON camelCase properties.
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
// Configure the IOC container
IoC.Configure(container);
// Configure ServiceStack authentication to use our custom authentication providers.
var appSettings = new AppSettings();
host.Plugins.Add(new AuthFeature(() =>
new AuthUserSession(), // use ServiceStack's session class but fill it with our own data using our own auth service provider
new IAuthProvider[] {
new UserCredentialsAuthProvider(appSettings)
}));
}
}
where UserCredentialsAuthProvider
is my custom credentials provider:
public class UserCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
try
{
// Authenticate the user.
var userRepo = authService.TryResolve<IUserRepository>();
var user = userRepo.Authenticate(userName, password);
// Populate session properties.
var session = authService.GetSession();
session.IsAuthenticated = true;
session.CreatedAt = DateTime.UtcNow;
session.DisplayName = user.FullName;
session.UserAuthName = session.UserName = user.Username;
session.UserAuthId = user.ID.ToString();
}
catch (Exception ex)
{
// ... Log exception ...
return false;
}
return true;
}
}
In my user tests I initialize and start my TestAppHost on http://127.0.0.1:8888
, then use JsonServiceClient to authenticate itself to the service like so:
var client = new JsonServiceClient("http://127.0.0.1:8888/")
var response = client.Send<AuthResponse>(new Auth
{
provider = UserCredentialsAuthProvider.Name,
UserName = username,
Password = password,
RememberMe = true
});
But getting the following exception:
The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.GetResponse()
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](Object request)...
The ServiceStack.ServiceInterface.Auth.Auth
request contains the correct username and passsword, and the request is being posted to:
http://127.0.0.1:8888/json/syncreply/Auth
I am not sure why the URL is not /json/auth/credentials
or what I might be doing wrong. Any suggestions?
Tracing the chain of events up the stack I found the following:
JsonDataContractSerializer.SerializeToStream
correctly serializes the Auth request into Json. However, the System.Net.HttpRequestStream
passed to JsonDataContractDeserializer
by EndpointHandlerBase
has a stream of the correct length that is filled with nulls (zero bytes). As a result, the request object passed to CredentialsAuthProvider.Authenticate
has nulls in all its properties.
How can the HTTP stream get stripped of its data?
Upvotes: 1
Views: 657
Reputation: 1500
Got it!!!
The problem was the following pre-request filter that I added for logging purposes in TestAppHost.Configure
:
PreRequestFilters.Add((httpReq, httpRes) =>
{
LastRequestBody = httpReq.GetRawBody();
});
as seen here.
When the GetRawBody()
method reads the request InputStream it leaves it in the EOS state, and all subsequent read attempts return nothing.
So obviously GetRawBody()
can only be safely used with buffered streams, but unfortunately it quietly causes a very nasty bug instead of throwing an exception when used with a non-buffered stream.
Upvotes: 1