asp.net-mvcasp.net-web-apirestful-authentication

What is the correct way of find out if user is logged in in MVC WEB API?


I am very confused about this problem. Restfull service make it up to you to decide which way to implement this functionallity.

Ive read multiple articles about this problem, but every article says something different.

For example some people propose sessions, but if you do that Web API is losing its "rest fullness". Other people suggest cookies.

I don't know if what i am done is actually done right:

On login of user I create a cookie which contains UserID(Guid) and on every request which needs user to be logged in I check if this id exists in the database.

Is it secure enough? Or how should I make it more secure? Or do I have to choose completely different way?


Solution

  • Just create authentication token on server-side and store it in your database or even in cache. Then send this token with requests from your client application. WebApi should check this token all the time. It's good enough and you have full control over your auth process.

    Let me share, how it works for me:

    Object with Auth details:

    public class TokenIdentity
    {
        public int UserID { get; set; }
    
        public string AuthToken { get; set; }
    
        public ISocialUser SocialUser { get; set; }
    }
    

    Web API Auth Controller:

      public class AuthController : ApiController
        {
            public TokenIdentity Post(
                SocialNetwork socialNetwork,
                string socialUserID,
                [FromUri]string socialAuthToken,
                [FromUri]string deviceRegistrationID = null,
                [FromUri]DeviceType? deviceType = null)
            {
                var socialManager = new SocialManager();
    
                var user = socialManager.GetSocialUser(socialNetwork, socialUserID, socialAuthToken);
    
                var tokenIdentity = new AuthCacheManager()
                    .Authenticate(
                        user,
                        deviceType,
                        deviceRegistrationID);
    
                return tokenIdentity;
            }
        }
    

    Auth Cache Manager:

    public class AuthCacheManager : AuthManager
        {
            public override TokenIdentity CurrentUser
            {
                get
                {
                    var authToken = HttpContext.Current.Request.Headers["AuthToken"];
                    if (authToken == null) return null;
    
                    if (HttpRuntime.Cache[authToken] != null)
                    {
                        return (TokenIdentity) HttpRuntime.Cache.Get(authToken);
                    }
    
                    return base.CurrentUser;
                }
            }
    
            public int? CurrentUserID
            {
                get
                {
                    if (CurrentUser != null)
                    {
                        return CurrentUser.UserID;
                    }
                    return null;
                }
            }
    
            public override TokenIdentity Authenticate(
                ISocialUser socialUser, 
                DeviceType? deviceType = null, 
                string deviceRegistrationID = null)
            {
                if (socialUser == null) throw new ArgumentNullException("socialUser");
                var identity = base.Authenticate(socialUser, deviceType, deviceRegistrationID);
    
                HttpRuntime.Cache.Add(
                    identity.AuthToken,
                    identity,
                    null,
                    DateTime.Now.AddDays(7),
                    Cache.NoSlidingExpiration,
                    CacheItemPriority.Default,
                    null);
    
                return identity;
            }
        }
    

    Auth Manager:

     public abstract class AuthManager
        {
            public virtual TokenIdentity CurrentUser
            {
                get
                {
                    var authToken = HttpContext.Current.Request.Headers["AuthToken"];
                    if (authToken == null) return null;
    
                    using (var usersRepo = new UsersRepository())
                    {
                        var user = usersRepo.GetUserByToken(authToken);
    
                        if (user == null) return null;
    
                        return new TokenIdentity
                        {
                            AuthToken = user.AuthToken,
                            SocialUser = user,
                            UserID = user.ID
                        };
                    }
                }
            }
    
            public virtual TokenIdentity Authenticate(
                ISocialUser socialUser, 
                DeviceType? deviceType = null, 
                string deviceRegistrationID = null)
            {
                using (var usersRepo = new UsersRepository())
                {
                    var user = usersRepo.GetUserBySocialID(socialUser.SocialUserID, socialUser.SocialNetwork);
    
                    user = (user ?? new User()).CopyFrom(socialUser);
    
                    user.AuthToken = System.Guid.NewGuid().ToString();
    
                    if (user.ID == default(int))
                    {
                        usersRepo.Add(user);
                    }
    
                    usersRepo.SaveChanges();
    
                    return new TokenIdentity
                    {
                        AuthToken = user.AuthToken,
                        SocialUser = user,
                        UserID = user.ID
                    };
                }
            }
        }
    

    Global Action Filter:

    public class TokenAuthenticationAttribute : System.Web.Http.Filters.ActionFilterAttribute
    {
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            if (actionContext.Request.RequestUri.AbsolutePath.Contains("api/auth"))
            {
                return;
            }
    
            var authManager = new AuthCacheManager();
    
            var user = authManager.CurrentUser;
    
            if (user == null)
            {
                throw new HttpResponseException(HttpStatusCode.Unauthorized);
            }
    
            //Updates the authentication
            authManager.Authenticate(user.SocialUser);
        }
    }
    

    Global.asax registration:

    GlobalConfiguration.Configuration.Filters.Add(new AuthFilterAttribute());
    

    The idea is that AuthCacheManager extends AuthManager and decorates it's methods and properties. If there is nothing inside cache then go check database.

    It's an example from real app, but I hope the idea is clear :)