0

I am using ServiceStack v4 with custom Authentication. This is setup and working correctly. I can call the /auth service and get a returned AuthorizationResponse with unique SessionId.

I also have swagger-ui plugin setup. Using it, I can authenticate via /auth and then call one of my other services which require authentication without issue.

Now, from a secondary MVC application using the c# JsonServiceClient I can again successfully make a call to /auth and then secured services using the same client object. However, if I dispose of that client (after saving the unique sessionId to a cookie), then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401. If I call a non-secure service, but then try to access the unique user session, it returns a new session.

If I look at the request headers in that service, the cookie or Header is clearly set with the sessionId. The sessionId also exists in the sessionCache. The problem seems to be that the code which tries to get the session from the request isn't finding it.

To be more specific, it appears that ServiceExtensions.GetSessionId is looking at the HostContext and not the calling Request. I'm not sure why. Perhaps I misunderstand something along the way here.

If I directly try and fetch my expected session with the following code it's found without issue.

  var req = base.Request;
  var sessionId = req.GetHeader("X-" + SessionFeature.PermanentSessionId);

  var sessionKey = SessionFeature.GetSessionKey(sessionId);
  var session = (sessionKey != null ? Cache.Get<IAuthSession>(sessionKey) : null)?? SessionFeature.CreateNewSession(req, sessionId);

So, am I missing something obvious here? Or maybe not so obvious in creating my secondary client?

Sample code of client calls

Here is my authorization code. It's contained in a Controller class. This is just the relevant parts.

using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
    try
    {
        loginResult = client.Post(new Authenticate()
                       {
                           UserName = model.Email,
                           Password = model.Password,
                           RememberMe = model.RememberMe
                       });

        Response.SetCookie(new HttpCookie(SessionFeature.PermanentSessionId, loginResult.SessionId));

        return true;
     }
}

Here is my secondary client setup and service call, contained in it's own controller class in another area of the MVC application

using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
    var cCookie = HttpContext.Request.Cookies.Get(SessionFeature.PermanentSessionId);
    if (cCookie != null)
    {
        client.Headers.Add("X-" + SessionFeature.PermanentSessionId, cCookie.Value);
        client.Headers.Add("X-" + SessionFeature.SessionOptionsKey, "perm");

    }

    response = client.Get(new SubscriptionStatusRequest());
}

Additional Update

During the Authenticate process the following function is called from HttpRequestExtensions with the name = SessionFeature.PermanentSessionId

public static class HttpRequestExtensions
{
    /// <summary>
    /// Gets string value from Items[name] then Cookies[name] if exists.
    /// Useful when *first* setting the users response cookie in the request filter.
    /// To access the value for this initial request you need to set it in Items[].
    /// </summary>
    /// <returns>string value or null if it doesn't exist</returns>
    public static string GetItemOrCookie(this IRequest httpReq, string name)
    {
        object value;
        if (httpReq.Items.TryGetValue(name, out value)) return value.ToString();

        Cookie cookie;
        if (httpReq.Cookies.TryGetValue(name, out cookie)) return cookie.Value;

        return null;
    }

Now what occurs is the httpReq.Items contains a SessionFeature.PermanentSessionId value, but I have no clue why and where this gets set. I don't even understand at this point what the Items container is on the IRequest. The code thus never gets to the functionality to check my cookies or headers

tracstarr
  • 103
  • 10

2 Answers2

2

The Session wiki describes the different cookies used by ServiceStack Session.

If the client wants to use a Permanent SessionId (i.e. ss-pid), it also needs to send a ss-opt=perm Cookie to indicate it wants to use the permanent Session. This Cookie is automatically set when authenticating with the RememberMe=true option during Authentication.

There was an issue in the Session RequestFilter that was used to ensure Session Id's were attached to the current request weren't using the public IRequest.GetPermanentSessionId() API's which also looks for SessionIds in the HTTP Headers. This has been resolved with this commit which now lets you make Session requests using HTTP Headers, e.g:

//First Authenticate to setup an Authenticated Session with the Server
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate
{
    provider = CredentialsAuthProvider.Name,
    UserName = "user",
    Password = "p@55word",
    RememberMe = true,
});

//Use new Client instance without Session Cookies populated 
var clientWithHeaders = new JsonServiceClient(BaseUrl);
clientWithHeaders.Headers["X-ss-pid"] = authResponse.SessionId;
clientWithHeaders.Headers["X-ss-opt"] = "perm";

var response = clientWithHeaders.Send(new AuthOnly()); //success

This fix is available from v4.0.37+ that's now available on MyGet.

mythz
  • 141,670
  • 29
  • 246
  • 390
  • Interesting. I will give that a try now (including the additional opt=perm). For me the documentation isn't necessarily clear on this. The best answer I had found to date also didn't seem to include this need. [link](http://stackoverflow.com/questions/19160839/reconnecting-to-servicestack-session-in-an-asp-net-mvc4-application). I will return shortly with my results. – tracstarr Feb 10 '15 at 13:16
  • This didn't make a difference. I tried using both headers and cookies adding the additional ss-opt. I will update my question with additional sample code of calling functions. – tracstarr Feb 10 '15 at 15:23
  • @tracstarr FYI fixed an issue with SessionIds being overridden, example above now works from v4.0.37+ that's [available on MyGet](https://github.com/ServiceStack/ServiceStack/wiki/MyGet). – mythz Feb 11 '15 at 05:57
  • Great. I'm taking a look right now. I've run into a bit of an issue with my initial Auth call via the JsonServiceClient. I'm going to put together a side project to test just in case my references are crossed or something else is off. Here's the error I get back... – tracstarr Feb 11 '15 at 19:02
  • ResponseBody={"responseStatus":{"errorCode":"MissingMethodException","message":"Method not found: 'System.Collections.Generic.Dictionary`2 ServiceStack.Web.IResponse.get_Items()'.","stackTrace":" at ServiceStack.HttpResponseExtensionsInternal.ShouldWriteGlobalHeaders(IResponse httpRes)\r\n at ServiceStack.HttpResponseExtensionsInternal.ApplyGlobalResponseHeaders(IResponse httpRes)\r\n at ServiceStack.HttpResponseExtensionsInternal.WriteToResponse – tracstarr Feb 11 '15 at 19:03
  • @tracstarr MissingMethodException is usually because you have dirty dlls running different versions of ServiceStack together. Make sure to clean your [NuGet package cache](https://github.com/ServiceStack/ServiceStack/wiki/MyGet#redownloading-myget-packages) and delete your `/pacakges` folder so VS.NET fetches the latest packages. After the packages are downloaded check to make sure you only have the same version (i.e. v4.0.37) in your packages folder. – mythz Feb 12 '15 at 00:07
  • Yep, turns out one of my assemblies still had a reference to 4.0.36. I've since tested and things seem to be working good. Thank you. – tracstarr Feb 12 '15 at 13:14
1

However, if I dispose of that client (after saving the unique sessionId to a cookie)

If the client is disposed where is the cookie you are saving the sessionId located? This answer might provide some additional information.

then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401

If you store/save a valid sessionId as a string you should be able to supply it within a CookieContainer of a new client (given the sessionId is still authenticated). I know you said you tried adding the sessionId as a Cookie but I don't a see sample within your question using the CookieContainer so it should look something like...

using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
    var cCookieId = savedCookieId; //a string that I believe you saved from a successfully authenticated client that is now disposed
    if (cCookieId != null)
    {
        var cookie = new Cookie(SessionFeature.PermanentSessionId, cCookieId);
        //cookie.Domian = "somedomain.com" //you will probably need to supply this as well
        client.CookieContainer.Add(cookie)
    }

    response = client.Get(new SubscriptionStatusRequest());
}
Community
  • 1
  • 1
paaschpa
  • 4,816
  • 11
  • 15
  • So you're right in my above example I'm not saving the specific cookie returned by the client, but the sessionId. According to the documents this is all that's needed - especially if using headers instead of cookies in a subsequent new client request. I've also done exactly as you show in your example code with the same result. I've spent some time trying to debug things further. What I've found is that the method HttpRequestExtensions.GetItemOrCookie gets called looking for a ss-pid value in the IRequest.Items container. It's finding one and returning it (but it's not the one I want.) – tracstarr Feb 11 '15 at 00:10
  • The issue now is I don't know what, how or why this Items container contains a sessionId and why it's being selected over looking in either cookies or header values. – tracstarr Feb 11 '15 at 00:13