Iisexpress-proxy + JsonServiceStackClient loses authentication information

Not sure how much this is a problem with JsonServiceStackClient or iisexpress-proxy but its driving me mad!

In my local environment I’m using iisexpress-proxy to route into my instance of servicestack running on iisexpress, Using my mobile application I can log in fine and then issue a request which gets executed correctly. However issue a third request it returns a 401.

#Authorised First Request : 200
##sessionid
SZMMJ8QLxsOPAccsfDta
##header

{Connection:close,Content-Type:application/json,Accept:*/*,Accept-Encoding:"gzip, deflate",Accept-Language:en-gb,Cookie:X-UAId=; ss-opt=perm; ss-pid=SZMMJ8QLxsOPAccsfDta,Host:"localhost:2117",User-Agent:Expo/1.20.2.109829 CFNetwork/887 Darwin/17.0.0,x-forwarded-host:"192.168.1.158:3000",x-forwarded-proto:http,x-forwarded-port:3000,x-forwarded-for:"::ffff:192.168.1.222"}

##items

{AspSessionIDManagerInitializeRequestCalled:True,__haspreauth:True,__session:PhoenixFleet.ServiceInterface.CustomUserSession,ss-id:eCDV9Mhp2Gv7WMny1Ck4,_requestDurationStopwatch:System.Diagnostics.Stopwatch,_logged:True}

#2nd Request : 401
##sessionid
SZMMJ8QLxsOPAccsfDta,ss-id=eCDV9Mhp2Gv7WMny1Ck4
##header
{Connection:close,Content-Type:application/json,Accept:/,Accept-Encoding:“gzip, deflate”,Accept-Language:en-gb,Cookie:“ss-id=eCDV9Mhp2Gv7WMny1Ck4; X-UAId=; ss-opt=perm; ss-pid=SZMMJ8QLxsOPAccsfDta,ss-id=eCDV9Mhp2Gv7WMny1Ck4”,Host:“localhost:2117”,User-Agent:Expo/1.20.2.109829 CFNetwork/887 Darwin/17.0.0,x-forwarded-host:“192.168.1.158:3000”,x-forwarded-proto:http,x-forwarded-port:3000,x-forwarded-for:"::ffff:192.168.1.222"}

##items
{AspSessionIDManagerInitializeRequestCalled:True,__haspreauth:True,__session:PhoenixFleet.ServiceInterface.CustomUserSession}

My question is why would SS think that my request is Unauthorized when I have logged in?

I’m not sure what format this is in or how to read it or whether it can be trusted, please just provide the raw HTTP Headers exactly how it’s returned from ServiceStack, using a tool like Fiddler or WireShark.

ServiceStack returns a 401 if the Session Cookies doesn’t reference a valid User Session in the Cache Client using the session key format:

urn:iauthsession:{sessionId}

If an already authenticated is returning a 401 that usually means either the ss-* session cookies have changed or there is no longer a valid User Session registered in the ICacheClient at the key above.

The data is from the csvRequestLogger

    Plugins.Add(new RequestLogsFeature {
    RequestLogger = new CsvRequestLogger(),
});

I was having trouble getting localhost traffic hence the use of the RequestLogsFeature.

The Key for logged in session is still in CacheEntry table.

Then we wont be able to tell from here as checking if there’s a valid UserSession is primarily what [Authenticate] does.

You can debug the Auth Logic by overriding IsAuthorized() in a Custom UserSession, e.g:

class CustomUserSession : AuthUserSession
{
    public override bool IsAuthorized(string provider)
    {
        var isAuthenticated = base.IsAuthorized(provider);
        return isAuthenticated;
    }
}

Then register to use the CustomUserSession in your AuthFeature with:

new AuthFeature(() => new CustomUserSession(), ...)

The default implementation is that it goes through and call IAuthProvider.IsAuthorized() which is ultimately what determines if a User is Authenticated. E.g. if you’re using the CredentialsAuthProvider it requires the IsAuthenticated=true and UserAuthName to be populated:

return session != null && session.IsAuthenticated && !session.UserAuthName.IsNullOrEmpty();
    [DataContract]
    public class CustomUserSession : AuthUserSession
    {
        [DataMember]
        public Enums.DriverType DriverType { get; set; }
       [DataMember]
        public int DriverId { get; set; }
        
        public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            base.OnAuthenticated(authService, session, tokens, authInfo);
            
            var conn = authService.TryResolve<IOrmliteSqlServerConnection();

            Driver driver = null;
            using (var db = conn.OpenDbConnection())
            {
                    driver = db.Single<DriverInformationSummary>(x => x.Email == session.UserAuthName);
            }


            this.DriverType = driver.DriverType;
            
        }
       
    }

Using the advice you given, I’ve overridden the IsAuthorized of the above class to debug.

base.IAuthenticated is false and base.UserAuthName is null when accessing from my mobile device, but interestingly during the OnAuthenticated method if you inspect the class properties the session.IsAuthenticated is set to true.

    public override bool IsAuthorized(string provider)
    {
        
        var tokens = this.GetAuthTokens(provider);
        var providerBase = AuthenticateService.GetAuthProvider(provider);

   
        var isUserSessionAuthenticated = base.IsAuthenticated;
        var userNameEmpty = base.UserAuthName.IsNullOrEmpty();

        var result = providerBase.IsAuthorizedSafe(this, tokens);

        var isAuthenticated = base.IsAuthorized(provider);
        return isAuthenticated;
    }

For reference below is a console log of the filter request request sent from my javascript client

Object {
  "ss-id": Object {
    "HttpOnly, X-UAId": "",
    "HttpOnly, ss-opt": "perm",
    "HttpOnly, ss-pid": "RFGEHJG45zb1HWIgYyqD",
    "expires": 2037-10-25T15:31:55.000Z,
    "httpOnly": true,
    "name": "ss-id",
    "path": "/",
    "value": "uZuqztApYDe0JieA62J6",
  },
} Request {
  "_bodyInit": undefined,
  "_bodyText": "",
  "credentials": "include",
  "headers": Headers {
    "map": Object {
      "content-type": Array [
        "application/json",
      ],
      "cookie": Array [
        "ss-id=uZuqztApYDe0JieA62J6",
      ],
    },
  },
  "method": "GET",
  "mode": "cors",
  "referrer": null,
  "url": "http://192.168.1.158:3000/json/reply/xxxxxxxxxxxxxxxx",
}

and the ICache entry in sql

So after a user has logged in and they then makes a request - how does service stack repopulates the session and sets IsAuthenticated? does it look at the Cache entry?

ServiceStack only saves the UserSession during Authentication, normally when OnAuthenticated() or Authenticate() is called depending on the AuthProvider. Both are called when the user is Authenticated which sets and saves the Users Session, e.g:

try
{
    session.IsAuthenticated = true;
    session.OnAuthenticated(authService, session, tokens, authInfo);
    AuthEvents.OnAuthenticated(authService.Request, session, authService, tokens, authInfo);
}
finally
{
    this.SaveSession(authService, session, SessionExpiry);
    authService.Request.Items[Keywords.DidAuthenticate] = true;
}

What else can I try to debug to see why SS might not match the incoming ss-pid and ss-id?

Is there anything wrong with the request that I’ve highlighted above?

Why do you think it’s not matching the incoming ss-id/ss-pid? It uses these session ids to retrieve the Users Session from the ICacheClient.

You can try doing what ServiceStack does so you can debug and see why it’s getting the UserSession it’s getting. Instead of using the built-in [Authenticate] attribute, use a local modified copy of AuthenticateAttribute so you can debug it.

Otherwise try calling IRequest.GetSession() from a Request Filter or Service to see the UserSession that ServiceStack retrieves that request. This is the implementation of GetSession().

If you want to be able to debug the entire Request pipeline you could also clone the ServiceStack repo and have your project reference debug builds of ServiceStack.dll. The master version includes v5 changes, so you may want to use the source code release for your version instead.

[quote=“mythz, post:8, topic:4760”]
Why do you think it’s not matching the incoming ss-id/ss-pid? It uses these session ids to retrieve the Users Session from the ICacheClient.

Because the session is not coming back from the db even though the ss-pid is the same as from the value in the cookies.

I’ve created a modified copy of the authenticate attribute and the ss cookie ids look ok

Checked sql and the entry is there

Then I call req.GetSession() in the attribute method I get what looks like a brand new session and not the session saved in the db.

Is your CustomUserSession registered in the AuthFeature?

new AuthFeature(() => new CustomUserSession(), ...)

If its is, try retrieving the session from the ICacheClient yourself, e.g:

var sessionId = httpReq.GetSessionId();
var sessionKey = SessionFeature.GetSessionKey(sessionId);
var session = httpReq.GetCacheClient().Get<IAuthSession>(sessionKey);

I don’t know where the issue is, but there’s not much ServiceStack is doing other that retrieving the UserSession from the cache.

I may have found the issue…
I’ve referenced the SS debug dlls as you suggested and stepped into HttpRequestAuthentication.GetCookieValue where it attempts to get the cookiename ss-pid

The value for ss-pid for whatever reason is “Zg6wuMpdZLSNwIXHKJrc,ss-id=kfoJELDNa9SSoPdESGdN” where it should be just "Zg6wuMpdZLSNwIXHKJrc"

Its weird how ss-id and value has been appended to the value.

The effect is then in ServiceExtensions.GetSession routine the following code can’t find the session with the funky value.

var sessionKey = SessionFeature.GetSessionKey(sessionId);

I’ve included a screen shot of the requests cookie values

Any suggestions where I go from here?

Could it be that iisexpress-proxy is corrupting the cookies? i.e. does it work when you don’t use it?

I don’t think it’s corrupting the cookies, had a look at the source and all it is doing is x-forwarding and not touching the cookies.

I can’t really test without it as I’m calling from within the Expo application to my dev machine on port 3000.

When I authenticate via Postman to my SS instance on port 3000 it’s fine.

something is not delivering the Cookies correctly, can you please provide the raw HTTP Headers as they’re sent, not through an object or UI dump, the issue is a malformed HTTP Request and you’ll need to see the raw HTTP Request to identify it.

Here is the captured request from wireshark

That’s still not the raw text, you can use follow tcp stream in WireShark to get it, but it does show the ss-pid Cookie is not sent correctly. Now we need to determine what’s sending them incorrectly, if you have steps we can use to repro this issue we can see if it’s the client or something else.