14

I am attempting to make a Windows Forms application that plugs into some services exposed by ASP.NET MVC WebAPI, but am having a great deal of trouble with the authentication/login part.

I cannot seem to find an example that just demonstrates how to do this from Windows Forms, everything I find seems to be very convoluted and includes a lot of very deep plumbing, or seems targeted to other ASP.NET websites, and not windows forms.

Is there something I am missing? Is this just not possible? Or is it just not intended? I've looked at things like this .NET WebApi Authentication that claim to do it, but I don't see how to use cookies from a Windows Forms standpoint. I've also gone over http://blogs.msdn.com/b/webdev/archive/2012/08/26/asp-net-web-api-and-httpclient-samples.aspx and still have had very little luck.

Community
  • 1
  • 1
Derek
  • 769
  • 1
  • 7
  • 14

2 Answers2

12

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 win forms 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.

Andrei
  • 42,814
  • 35
  • 154
  • 218
  • 2
    Have you got a code sample? Darin's is interesting but you might have a better one. – hawbsl Oct 03 '13 at 13:21
  • 1
    @hawbsl I've updated my answer. Hope you will find anything interesting there and get some idea for your application. – Andrei Oct 03 '13 at 13:34
  • 1
    +50 +10 i haven't tried it out but it looks like an excellent roadmap to follow – hawbsl Oct 03 '13 at 18:38
  • thanks @hawbsl, It's a part of working application (as you can see). Happy to help you! – Andrei Oct 03 '13 at 20:11
4

You could use token based authentication. Here's a great article illustrating how you could write a custom action filter that uses RSA public/private cryptography.

Druid
  • 6,423
  • 4
  • 41
  • 56
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    I'm a bit confused. I don't have action filters in Windows Forms. – Derek Mar 11 '13 at 22:24
  • 1
    I'm referring to a login authentication, not really encryption. – Derek Mar 11 '13 at 22:25
  • You should use action filters in your Web API in order to protect your actions. The WinForms application will act as a client to those actions I suppose. Also the encrypted token could contain the username accessing your method. So you could achieve authentication and since you know the user, even authorization with this approach. – Darin Dimitrov Mar 11 '13 at 22:25
  • Hrnm.. So I setup the website to use action filters on every request, and then I use `HttpClient` to connect? – Derek Mar 11 '13 at 22:29
  • 1
    Yeap, that's the idea. Please read the article I have linked to in my answer and try playing with the code illustrated in it. – Darin Dimitrov Mar 11 '13 at 22:31
  • 2
    This still isn't making a lot of sense. All of these assume you already have the user authenticated. That's the part I'm having trouble with. – Derek Mar 11 '13 at 23:49
  • I guess that the user authenticates in your WinForm application, isn't he? You should have an action on your Web API that will be accessible to non-authenticated users and which will allow to verify the credentials of a user. – Darin Dimitrov Mar 12 '13 at 06:31
  • 1
    Right, but what I am still not understanding is what data gets passed for an authenticated user. – Derek Mar 20 '13 at 15:49
  • Just to make sure, your service is deployed publicly, i.e. is not sitting behind firewall, and you want to access it from WinForms app connecting to it over internet? – Dmitry Sevastianov Sep 27 '13 at 18:27