Custom Session not persisting to cache

This is a continuation of Custom session not saving to cache

Basic problem is that I have a custom authentication implementation that is hitting SQLServer (custom user table) and returning some additional information for authentication.

Everything is working except that it appears the session is not persisting into the cache. If I inspect the session in OnSessionFilter the IsAuthenticated flag is set to true but I still get a 401 from the service.

I started a new project to play around with the authentication and Postman is having no issues authenticating to and then hitting a protected Service. I’m seeing the cookies being returned by the authentication request and being sent in the request.

Gist of the relevant code is here: https://gist.github.com/ultimatemonty/cab62f46ccf708ddf2f1

There’s a lot of internal behavior being replaced here, what’s the reason for needing a custom DemoUserRepository? and a new DemoCredentialsAuthProvider? If you’re using a custom Auth Provider just put in all your custom logic in there and avoid the indirection to a partially implemented Auth repository.

Also I can’t tell if you’re replacing the SessionId, it needs to remain the same (i.e. match the SessionId cookies)

A lot of that was a result of troubleshooting I was doing when first implementing this. Will simplify things and see where that takes me.

@mythz

I simplified things down to just using the DemoCredentialsAuthProvider with no custom session or custom auth repo. Still seeing the same issue - a valid session is created and is returned by /Login. The cookie is saved in Postman and when making a request to a protected service the cookie is in the request but I still get the 401 response.

Updated gist at https://gist.github.com/ultimatemonty/cab62f46ccf708ddf2f1

Can you post the HTTP Request and Response Headers of the Authentication and the 401 Response?

@mythz here ya go. These were captured with Fiddler using Postman as the client.

Login Request

POST http://localhost:64499/auth/credentials HTTP/1.1
Host: localhost:64499
Connection: keep-alive
Content-Length: 77
Cache-Control: no-cache
Origin: chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36
Postman-Token: 45383aa8-373b-ed7d-48e7-be98895eeed3
Accept: */*
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8

{
    "username": "testuser@test.com",
    "password": "password"
}

Login Response

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/8.0
X-Powered-By: ServiceStack/4.052 Win32NT/.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
X-AspNet-Version: 4.0.30319
Set-Cookie: ss-id=gecCayZizxIwn8EWtASs; path=/; HttpOnly
Set-Cookie: ss-pid=reKfBPD9p3OJ4hLsQRzL; expires=Thu, 24-Jan-2036 14:15:59 GMT; path=/; HttpOnly
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcbG9jYWxBZG1pblxEb2N1bWVudHNcUHJvamVjdHNcU3luYXBzZU1YXHN5bmFwc2VteC1hcGktZG90bmV0XHN5bmFwc2VteC1hcGktZG90bmV0XHN5bmFwc2VteF9hcGlfZG90bmV0XGF1dGhcY3JlZGVudGlhbHM=?=
X-Powered-By: ASP.NET
Date: Sun, 24 Jan 2016 14:15:59 GMT
Content-Length: 135

{
  "subscriptionValid":true,
  "userId":"1",
  "sessionId":"gecCayZizxIwn8EWtASs",
  "userName":"testuser@test.com",
  "responseStatus":{}
}

Protected Endpoint Request

GET http://localhost:64499/Aircraft HTTP/1.1
Host: localhost:64499
Connection: keep-alive
Accept: application/json
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36
Postman-Token: 866a012b-6775-7298-e9c6-b69ad27c8b84
DNT: 1
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Cookie: ss-id=gecCayZizxIwn8EWtASs; ss-pid=reKfBPD9p3OJ4hLsQRzL

Protected Endpoint Response

HTTP/1.1 401 Unauthorized
Cache-Control: private
Vary: Accept
Server: Microsoft-IIS/8.0
WWW-Authenticate: credentials realm="/auth/credentials"
X-Powered-By: ServiceStack/4.052 Win32NT/.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcbG9jYWxBZG1pblxEb2N1bWVudHNcUHJvamVjdHNcU3luYXBzZU1YXHN5bmFwc2VteC1hcGktZG90bmV0XHN5bmFwc2VteC1hcGktZG90bmV0XHN5bmFwc2VteF9hcGlfZG90bmV0XEFpcmNyYWZ0?=
X-Powered-By: ASP.NET
Date: Sun, 24 Jan 2016 14:22:38 GMT
Content-Length: 0

Ok looks like the request is maintaining the same cookies so I’d look at making sure the session is being saved properly. Make sure the SessionId is being preserved (I.e it matches the cookie and you’re not overriding it) and try resolving the session after you save it with authService.Request.GetSession(reload:true)

I added session = authService.Request.GetSession(reload:true); after https://gist.github.com/ultimatemonty/cab62f46ccf708ddf2f1#file-democredentialsauthprovider-cs-L60 and session.Id was unchanged and matched what was being returned by the auth request.

The only other data populated in the session variable were CreatedAt, LastModified, and IsAuthenticated (set to true).

I tried updating the CorsFeature settings in https://gist.github.com/ultimatemonty/cab62f46ccf708ddf2f1#file-apphost-cs-L65-L69 to allowCredentials: true and still no go.

Not sure if this is related, but I dropped a breakpoint on https://gist.github.com/ultimatemonty/cab62f46ccf708ddf2f1#file-apphost-cs-L133 to see what was going on.

On the /Login request it’s getting hit 5 times. The first 2 times the id param is set and the session is null. On the 3rd hit it the value of the id param changes and session is populated. This same id and session persists for the last 2 hits before the request completes. id and session.id match on these.

On the protected endpoint request, there are 2 hits. On the first hit, id is set the sessionId returned by the auth request and session is null. On the 2nd hit, id is the same, and session is populated (session.id is the same as the id param here).

Just to be clear, is the session returned from GetSession(reload:true) the same as the session that was saved? Also are the UserName and Email populated and are they returned when loading the Session again?

ServiceStack sessions are very straight forward, the session is stored in the registered ICacheClient using the session Id. When a request is made ServiceStack just looks at the ss-id Cookie and loads the session from that, where if the Session exists at that key and if IsAuthenticated=true.

The saved session and session returned from GetSession(reload:true) are identical. Neither has the UserName or Email fields populated. Both have IsAuthenticated=true

Since you’re inheriting from CredentialsAuthProvider it should have a UserAuthName

Would it not be there because I’m not using the default authentication schema (UserAuth and UserAuthDetails)? I have my own that the Authenticate method is grabbing (a Users table).

Some additional information.

I moved the [Authenticate] attribute from the Service level down to the method level so that I could test a couple of different endpoints. On an unauthenticated endpoint I inspected this.GetSession() which returned back an AuthUserSession with the same SessionId as what was passed in with the cookie, and IsAuthenticated=true.

So the session looks like it is valid inside the request?

It doesn’t have it because you’re not using the existing implementation, you’re using a Custom AuthProvider and don’t have an Auth Repository - it’s up to you to populate the Session from your custom database.

The User IsAuthenticated is determined by the AuthProvider IsAuthorized() method, if you don’t override it you’re using the existing CredentialsAuthProvider.IsAuthorized() implementation which needs session.IsAuthenticated=true and a session.UserAuthName.

@mythz adding the UserAuthName and Email has resolved the issue!

Just curious - why would those fields be required when I’m using a custom auth provider? You had earlier indicated that the session is looked up by the SessionId and IsAuthenticated flag - it sounded like those were the only two fields needed to lookup the session?

Thanks for all of your assistance, especially on a weekend!

EDIT: your previous post hadn’t appeared when I was posting this. Thanks again for all the help!

Question is answered in my previous answer. You’re not creating a completely new AuthProvider, you’re inheriting some of the existing CredentialsAuth behavior as well.

Got it. Do the other AuthProviders have similar requirements?

Would like to submit a PR to the docs to clarify that if you are inheriting from an existing provider then fields x,y,z need to be manually populated in the session.

Most other Auth Providers are OAuth Providers, you can check their IsAuthorized() method for their requirements, most use the base impl that requires a AccessTokenSecret instead of a UserAuthName.

1 Like