Servicestack 4.0.33 and X-ss-Id header always returns unauthorized.

208 views
Skip to first unread message

Arun Pereira

unread,
Nov 24, 2014, 3:34:51 AM11/24/14
to servic...@googlegroups.com
I currently am trying to use servicestack 4.0.33 (licensed) with the x-ss-id header.  But it doesn't seem to want to co-operate. I am using a custom auth provider which inherits from the BasicAuthProvider as below 

public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            var log = LogManager.GetLogger(this.GetType());

            using (var ravenSession = this.store.OpenSession())
            {
                var user = ravenSession.Query<User>().SingleOrDefault(x => x.EmailAddress == session.UserAuthName && x.IsActive && x.IsVerified);

                if (user == null)
                {
                    log.WarnFormat("AuthProvider.OnAuthenticated - GetUser failed - {0}", session.UserAuthName);
                    session.IsAuthenticated = false;                    
                    return null;
                }
                
                session.UserAuthId = user.Id.ToString(CultureInfo.InvariantCulture);
                session.Email = user.EmailAddress;
                session.DisplayName = user.FirstName;
                session.FirstName = user.FirstName;
                session.LastName = user.LastName;
                session.IsAuthenticated = true;                
                session.Roles = new List<string> { user.Role.ToString() };
            }            
            
            authService.SaveSession(session);
            // base.OnAuthenticated(authService, session, tokens, authInfo);
            
            return new HttpResult(session);
        }

public override bool TryAuthenticate(IServiceBase authService, string username, string password)
        {
            var log = LogManager.GetLogger(this.GetType());

            IUser user;

            try
            {
                using (var ravenSession = this.store.OpenSession())
                {
                    user = new AuthenticationService(ravenSession).Authenticate(username, password);
                }                
            }
            catch (ApplicationException ax)
            {
                log.WarnFormat("AuthProvider.TryAuthenticate - ValidateUser failed - {0} - Error - {1}", username, ax);
                return false;
            }

            if (user != null)
            {
                return true;
            }

            log.WarnFormat("AuthProvider.TryAuthenticate - ValidateUser failed - {0}", username);
            return false;
        }


My javascript sets the header as below 

$http.defaults.headers.common['X-ss-id'] = $.cookie('SessionId');
$http.defaults.headers.common['ss-id'] = $.cookie('SessionId');

CORS support is configured as follows: 

Plugins.Add(new CorsFeature("*", "GET, POST, PUT, DELETE, OPTIONS", "Content-Type, Authorization, Accept, Session-Id, X-ss-id, x-ss-pid, ss-id", true));

Auth feature configuration is as follows

container.Register<ICacheClient>(new MemoryCacheClient());

                container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));

                var authProvider = new IAuthProvider[] { new CurrentAuthProvider(container.Resolve<IDocumentStore>()) };

                var authFeature = new AuthFeature(() => new AuthUserSession(), authProvider)
                                      {
                                          IncludeAuthMetadataProvider = true,
                                          HtmlRedirect = "/auth/current",                                          
                                      };

                Plugins.Add(authFeature);



What am I doing wrong? What is the best way to troubleshoot what is happening within the AuthenticateAttribute handler. 

tracstarr

unread,
Feb 9, 2015, 4:03:01 PM2/9/15
to servic...@googlegroups.com
Did you ever figure this out? I'm pretty much having the same issue. My custom auth works and I store the returned sessionId and then apply it to the header when making an api request. But, the service call shows as unauthenticated. The headers have the correct Id and I can see in my SessionFactory that there is a session record that exists with that Id. I must be missing something simple as to why it's not "picked up" and used.

Arun Pereira

unread,
Feb 10, 2015, 10:48:45 AM2/10/15
to servic...@googlegroups.com
I did not. I use a customauthenticate attribute. 

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class CustomAuthenticateAttribute : RequestFilterAttribute
    {        
        /// <summary>
        /// Initializes a new instance of the <see cref="CustomAuthenticateAttribute"/> class.
        /// </summary>
        /// <param name="applyTo">
        /// The apply to.
        /// </param>
        public CustomAuthenticateAttribute(ApplyTo applyTo)
            : base(applyTo)
        {
            this.Priority = (int)RequestFilterPriority.Authenticate;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomAuthenticateAttribute"/> class.
        /// </summary>
        public CustomAuthenticateAttribute()
            : this(ApplyTo.All)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomAuthenticateAttribute"/> class.
        /// </summary>
        /// <param name="provider">
        /// The provider.
        /// </param>
        public CustomAuthenticateAttribute(string provider)
            : this(ApplyTo.All)
        {
            this.Provider = provider;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomAuthenticateAttribute"/> class.
        /// </summary>
        /// <param name="applyTo">
        /// The apply to.
        /// </param>
        /// <param name="provider">
        /// The provider.
        /// </param>
        public CustomAuthenticateAttribute(ApplyTo applyTo, string provider)
            : this(applyTo)
        {
            this.Provider = provider;
        }

        /// <summary>
        /// Gets or sets the name of the authentication provider used.
        /// </summary>
        public string Provider { get; set; }

        /// <summary>
        /// Gets or sets the html redirect to be used when authentication fails
        /// </summary>
        public string HtmlRedirect { get; set; }

        /// <summary>
        /// The execute.
        /// </summary>
        /// <param name="req">
        /// The req.
        /// </param>
        /// <param name="res">
        /// The res.
        /// </param>
        /// <param name="requestDto">
        /// The request dto.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// </exception>
        public override void Execute(IRequest req, IResponse res, object requestDto)
        {
            if (AuthenticateService.AuthProviders == null)
            {
                throw new InvalidOperationException("The AuthService must be initialized by calling AuthService.Init to use an authenticate attribute");
            }

            var matchingOAuthConfigs = AuthenticateService.AuthProviders.Where(x => this.Provider.IsNullOrEmpty() || x.Provider == this.Provider).ToList();

            if (matchingOAuthConfigs.Count == 0)
            {
                res.WriteError(req, requestDto, "No OAuth Configs found matching {0} provider".Fmt(this.Provider ?? "any"));
                res.EndRequest();
                return;
            }
            
            AuthenticateIfBasicAuth(req, res);
            SetSessionIfSessionIdHeader(req, res);
            SetSessionIfSessionIdCookie(req, res);

            using (var cache = req.GetCacheClient())
            {
                var sessionId = req.GetSessionId();
                var session = sessionId != null ? cache.Get<IAuthSession>("urn:iauthsession:" + sessionId) : null;

                if (session != null && matchingOAuthConfigs.Any(x => session.IsAuthorized(x.Provider)))
                {
                    return;
                }

                var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect;

                if (htmlRedirect != null && req.ResponseContentType.MatchesContentType("text/html"))
                {
                    var url = htmlRedirect;
                    if (url.SafeSubstring(0, 2) == "~/")
                    {
                        url = req.GetBaseUrl().CombineWith(url.Substring(2));
                    }

                    url = url.AddQueryParam("redirect", req.AbsoluteUri);
                    res.RedirectToUrl(url);
                    return;
                }

                AuthProvider.HandleFailedAuth(matchingOAuthConfigs[0], session, req, res);
            }
        }

        /// <summary>
        /// Gets the value of the session id from the request headers.
        /// </summary>
        /// <param name="httpReq">
        /// The http request being handled.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private static string SessionIdFromHeader(IRequest httpReq)
        {
            var sessionId = httpReq.Headers["Session-Id"];
            return sessionId;
        }

        /// <summary>
        /// Gets the value of the session id from cookies.
        /// </summary>
        /// <param name="httpReq">
        /// The http request being handled.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private static string SessionIdFromCookies(IRequest httpReq)
        {
            return httpReq.GetCookieValue("ss-id");
        }

        /// <summary>
        /// Sets the response values for sessions from the cookie.
        /// </summary>
        /// <param name="req">
        /// The request being handled.
        /// </param>
        /// <param name="res">
        /// The response being handled.
        /// </param>
        private static void SetSessionIfSessionIdCookie(IRequest req, IResponse res)
        {
            var tokenSessionId = SessionIdFromCookies(req);

            if (tokenSessionId == null)
            {
                return;
            }

            req.Items[SessionFeature.SessionId] = tokenSessionId;
            req.Items[SessionFeature.PermanentSessionId] = tokenSessionId;
        }

        /// <summary>
        /// Sets the response values for sessions from the headers.
        /// </summary>
        /// <param name="req">
        /// The request being handled.
        /// </param>
        /// <param name="res">
        /// The response being handled.
        /// </param>
        private static void SetSessionIfSessionIdHeader(IRequest req, IResponse res)
        {
            var tokenSessionId = SessionIdFromHeader(req);

            if (tokenSessionId == null)
            {
                return;
            }

            req.Items[SessionFeature.SessionId] = tokenSessionId;
            req.Items[SessionFeature.PermanentSessionId] = tokenSessionId;
        }

        /// <summary>
        /// Handles authentication if basic authentication is being used.
        /// </summary>
        /// <param name="req">
        /// The request being handled
        /// </param>
        /// <param name="res">
        /// The response being handled.
        /// </param>
        private static void AuthenticateIfBasicAuth(IRequest req, IResponse res)
        {
            // Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
            // Required to get req.GetSessionId()
            SessionFeature.AddSessionIdToRequestFilter(req, res, null); 

            var userPass = req.GetBasicAuthUserAndPassword();
            if (userPass == null)
            {
                return;
            }

            var authService = req.TryResolve<AuthenticateService>();
            
            authService.Post(new Authenticate
                                                {
                                                    provider = "providername",
                                                    UserName = userPass.Value.Key,
                                                    Password = userPass.Value.Value
                                                });
        }
    }

And you can apply it with the following to the DTO or the service

[CustomAuthenticate(ApplyTo.Get | ApplyTo.Put | ApplyTo.Post | ApplyTo.Delete)]


Also, make sure you support Options on each service and enable CORS support. in Apphost.Configure

this.Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Session-Id"));

tracstarr

unread,
Feb 11, 2015, 9:01:00 AM2/11/15
to servic...@googlegroups.com
Thanks for the update. I too was expecting to have to go down this route. However, after further investigation and posting my issue and findings on Stackoverflow  here it turns out there was a small issue that's been resolved in the latest pre-release here. (v 4.0.37+)
Reply all
Reply to author
Forward
0 new messages