When using temporary sessions it works fine. Log into the auth service and calling /auth without any parameters and it shows the display name, session id, etc.
When I log in with RememberMe=true, that call returns the session information properly. But on subsequent calls to /auth without any parameters, ServiceStack returns a 401 not authenticated. The session object's IsAuthenticated property is true and actually exists. My code checks for this and if it's false, forwards the user to the login page which doesn't happen so I know the user really is authenticated.
I am not doing anything different. How can I authenticate with a permanent session and get subsequent calls to /auth to acknowledge that I am logged in?
If it helps I'm using a CustomCredentialsProvider.
Update:
AppHost code:
public override void Configure(Funq.Container container)
{
//Set JSON web services to return idiomatic JSON camelCase properties
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
Config.RestrictAllCookiesToDomain = ConfigurationManager.AppSettings["cookieDomain"];
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomCredentialsProvider()
{ SessionExpiry =
TimeSpan.FromMinutes(Convert.ToDouble(ConfigurationManager.AppSettings["SessionTimeout"]))
},
}) //end IAuthProvider
{
IncludeAssignRoleServices = false,
IncludeRegistrationService = false,
HtmlRedirect = ConfigurationManager.AppSettings["mainSiteLink"] + "Login.aspx"
} //end AuthFeature initializers
);//end plugins.add AuthFeature
Plugins.Add(new PostmanFeature() { EnableSessionExport = true });// this is only for when we want the feature and it's NOT in DebugMode
Plugins.Add(new SwaggerFeature());
Plugins.Add(new CorsFeature(allowedOrigins: "*",
allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
allowedHeaders: "Content-Type, Authorization, Accept",
allowCredentials: true));
container.Register<IRedisClientsManager>
(c => new PooledRedisClientManager(2, ConfigurationManager.AppSettings["redisIpPort"]));
container.Register<ICacheClient>(c => c.Resolve<IRedisClientsManager>().GetCacheClient());
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
//Set MVC to use the same Funq IOC as ServiceStack
ControllerBuilder.Current.SetControllerFactory(new FunqControllerFactory(container));
#if DEBUG
Config.DebugMode = true;
typeof(Authenticate).AddAttributes
(
new RestrictAttribute
(RequestAttributes.HttpGet | RequestAttributes.HttpPost)
);
#else
typeof(Authenticate).AddAttributes(new RestrictAttribute(RequestAttributes.HttpPost));
#endif
RegisterTypedRequestFilter<Authenticate>((req, res, dto) =>
{
if (dto.UserName != null && dto.UserName != string.Empty
&& dto.Password != null && dto.Password != string.Empty)
if(dto.RememberMe == null)
dto.RememberMe = false;
});
RegisterTypedResponseFilter<AuthenticateResponse>((req, res, dto) =>
{
var appSettings = new ServiceStack.Configuration.AppSettings();
dto.UserId = AppHostBase.Instance.TryResolve<ICacheClient>().SessionAs<CustomUserSession>().UserId.ToString();
dto.Meta = new Dictionary<string, string>();
dto.Meta.Add("ExpiresMinutes", appSettings.Get("SessionTimeout"));
});
}
public static void Start()
{
Licensing.RegisterLicense(licenceKey);
new ServiceStackAppHost().Init();
}
Initial request headers:
https://****.com/api2/auth?username=user&password=passwordmberme=true
- GET /api2/auth?username=user&password=password&rememberme=true HTTP/1.1
- Accept: text/html, application/xhtml+xml, /
- Accept-Language: en-US
- User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
- Accept-Encoding: gzip, deflate
- Host: propel.zola360.com
- DNT: 1
- Connection: Keep-Alive
- Cookie: ss-pid=P2hslABCmSs7pomRqNz5; ss-opt=perm; X-UAId=
Initial response headers:
- HTTP/1.1 200 OK
- Cache-Control: private
- Content-Type: text/html
- Content-Encoding: gzip
- Vary: Accept-Encoding
- Server: Microsoft-IIS/7.5
- X-Powered-By: ServiceStack/4.033 Win32NT/.NET
- Access-Control-Allow-Origin: *
- Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
- Access-Control-Allow-Headers: Content-Type, Authorization, Accept
- Access-Control-Allow-Credentials: true
- X-AspNet-Version: 4.0.30319
- Set-Cookie: ss-id=pojZkNAdMcEcACDREcRM; domain=.zola360.com; path=/; HttpOnly
- Set-Cookie: ss-opt=perm; domain=.zola360.com; expires=Mon, 13-Nov-2034 16:11:09 GMT; - path=/; HttpOnly
- Set-Cookie: X-UAId=; domain=.zola360.com; expires=Mon, 13-Nov-2034 16:11:09 GMT; path=/; HttpOnly
- Set-Cookie: 47=0; domain=.zola360.com; path=/
- Set-Cookie: UserId=47; domain=.zola360.com; path=/
- X-Powered-By: ASP.NET
- Date: Thu, 13 Nov 2014 16:11:09 GMT
- Content-Length: 4129
Initial response body:
{"userId":"47","sessionId":"PKrITmRawxAtnaABCDgN","userName":"user","responseStatus":{},"meta":{"ExpiresMinutes":"360"}}
Subsequent call to /auth request:
- GET /api2/auth HTTP/1.1
- Accept: text/html, application/xhtml+xml, /
- Accept-Language: en-US
- User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
- Accept-Encoding: gzip, deflate
- Host: propel.zola360.com
- DNT: 1
- Connection: Keep-Alive
- Cookie: ss-pid=cvgslABCmSs6pomYdLu0; ss-opt=perm; X-UAId=; ss-id=lYWZkFAdMcZcABCDcRM; 47=0; UserId=47
Subsequent call to /auth response
- HTTP/1.1 401 Not Authenticated
- Cache-Control: private
- Content-Type: text/html
- Vary: Accept
- Server: Microsoft-IIS/7.5
- X-Powered-By: ServiceStack/4.033 Win32NT/.NET
- Access-Control-Allow-Origin: *
- Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
- Access-Control-Allow-Headers: Content-Type, Authorization, Accept
- Access-Control-Allow-Credentials: true
- X-AspNet-Version: 4.0.30319
- X-Powered-By: ASP.NET
- Date: Thu, 13 Nov 2014 16:11:23 GMT
- Content-Length: 9731
Subsequent call to /auth body:
{"responseStatus":{"errorCode":"Not Authenticated","message":"Not Authenticated","stackTrace":"[Authenticate: 11/13/2014 3:27:49 PM]:\n[REQUEST: {}]\nServiceStack.HttpError: Not Authenticated\r\n at ServiceStack.Auth.AuthenticateService.Post(Authenticate request)\r\n at lambda_method(Closure , Object , Object )\r\n at ServiceStack.Host.ServiceRunner`1.Execute(IRequest request, Object instance, TRequest requestDto)","errors":[]}}
Update I crafted a small Python3 script to authenticate myself and call some other web service. After authentication using RememberMe=true, the cookies come back as expected: ss-id/pid are set fine and ss-opt=perm. I figured I would print the header cookie and just paste it into a header of another request to call a different service marked with [Authenticate]. It didn't work. So I tried something silly and pasted the ss-pid cookie value into the ss-id one. It worked.
Here's the failing cookie string (session redacted :)):
cookie = "ss-id=ss-ID-session-cookie; domain=.zola360.com; path=/; HttpOnly, ss- pid=ss-PID-session-cookie; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, ss-opt=perm; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, X-UAId=; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, 47=0; domain=.zola360.com; path=/, UserId=47; domain=.zola360.com; path=/"
And simply pasting the ss-pid value into ss-id works:
cookie = "ss-id=ss-PID-session-cookie; domain=.zola360.com; path=/; HttpOnly, ss- pid=ss-PID-session-cookie; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, ss-opt=perm; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, X-UAId=; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, 47=0; domain=.zola360.com; path=/, UserId=47; domain=.zola360.com; path=/"
And the Python3 script I used:
import httplib2 as http
import json
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=UTF-8'
}
uri = 'https://mysite.com'
path = '/api2/auth/credentials'
target = urlparse(uri+path)
method = 'POST'
body = '{"username": "username", "password": "password", "RememberMe": "true"}'.encode()
h = http.Http()
response, content = h.request(target.geturl(), method, body, headers)
#save the cookie and use it for subsequent requests
cookie = response['set-cookie']
print(cookie)
path2 = '/api2/time/start'
target2 = urlparse(uri+path2)
headers['cookie'] = cookie
response, content = h.request(target2.geturl(), 'GET', body, headers)
# assume that content is a json reply
# parse content with the json module
data = json.loads(content.decode())
print(data)
It seems that something still looks at the value of ss-id even if ss-opt=perm.
When Authenticating with
GET /api2/auth?username=user&password=...
it is sent with your permanent cookiess-pid
, i.e:The
rememberme=true
option tells ServiceStack to maintain the users session against the permanentss-pid
cookie. This option is maintained in the usersss-opt=perm
cookie, which the HTTP Response tells the client to add with:Although not important in this case, since the temporary session
ss-id
was missing from the Request, ServiceStack tells the client to add a new one with:The issue is with the subsequent request to
GET /api2/auth
where the client is not re-sending thess-pid
cookie it originally authenticated with (i.e.P2hslABCmSs7pomRqNz5
vscvgslABCmSs6pomYdLu0
):Which ServiceStack doesn't know about (i.e. doesn't maintain any session against) which is why it returns with a
401 Not Authenticated
as expected.HTTP Client should be configured to resend Cookies
It's not clear what HTTP Client you're using but it should be configured to re-send cookies which is normally the default behavior. Ajax will send both the permanent
ss-pid
cookies and the temporaryss-id
only for that browser session, e.g. the temporaryss-id
cookie will be discarded when the browser is closed and making a new request will receive a newss-id
cookie.With the C# Service Clients, it only resends permanent cookies so the client needs to be authenticated with
RememberMe = true
, e.g:Once Authenticated the same authenticated
client
instance can be used to access a protected record multiple times as seen in this Auth Test: