Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maintain state between WCF webservice calls from android

I have a project in VB.NET which is using asp.net membership to manage user authentication. Now I want to build android app for this project so I decided to learn WCF and I have got average hold on WCF webservices. Now the issue I am facing is that when the user login into the android app following things happen:

  1. Request goes to the webapplication and user credentials are authenticated.
  2. After that when the user tries submits any data or try to view data , request again goes to the web application but now the web application should authenticate the user based on the credentials he has provided in the first request for login from the membership authentication.

Now the issue I am facing how to authenticate user in asp.net membership for each WCF Request in Per-Session Service call mode from java(Android).

like image 527
killer Avatar asked Nov 10 '22 14:11

killer


1 Answers

There's several ways to do what I think you're asking for, I've thought of (and written) a few different potential solutions, however, the one I'm sharing here is something that I think will "slot-in" to existing solutions using ASP.NET Membership/Roles Provider. Hopefully I've given you enough information to do what you need to do, but you can always comment and ask more questions if anything's still unclear.

In your problem you describe using an ASP.NET Web Application containing a WCF Service for existing clients, but you're looking to expand to using Android (java) requests? Given that the ASP.NET Membership provider uses alot of "behind the scenes" SOAP interchanges (for authentication, authorization and encryption) that seem to be built into the service reference frameworks it'd be a fairly big task to write a java implementation...

So, I've written you an example of something that will integrate to the same "backend" provider, but will also allow you to send SOAP requests from any client without needing the service reference (I tested it using SoapUI for example)... I've written my solution in C# (as it's what the WCF samples were written in), however, you can quite easily use a code-converter to switch it to VB.NET. I also haven't provided you with the method to encrypt and decrypt passwords, you'll have to research that bit yourself.

You'll need to implement a new .svc file into your existing solution and create new web.config entries accordingly (I assume you know how to create a basicHttpBinding and service endpoint already).

You'll also need to duplicate your method calls (or instead, create a new class with the method content and reference it from wherever you're implementing the ServiceContract methods) and remove the "[PrincipalPermission(SecurityAction" attributes, and add the example methods below into the new service. e.g. (using methods from Microsoft's MembershipAndRoleProvider WCF Sample) -

    // Allows all Users to call the Add method
    [PrincipalPermission(SecurityAction.Demand, Role = "Users")]
    public double Add(double n1, double n2)
    {
        double result = n1 + n2;
        return result;
    }

Would become:

    // Allows all Users to call the Add method        
    public double Add(double n1, double n2, string username, string token)
    {
        string isAuthorized = IsAuthorized(username, "Users", token)
        if (isAuthorized.Contains("Success")
            double result = n1 + n2;
            return result;
        else
           throw new Exception("Authorization Exception: " + isAuthorized);
    }

Here's my implementation(s), integrated into the Microsoft WCF Sample MembershipAndRoleProvider (download from here):

IsolatedAuthService.svc

<%@ServiceHost Language="C#" Debug="true" Service="Microsoft.ServiceModel.Samples.IsolatedAuthService" CodeBehind="IsolatedAuthService.cs" %>

IIsolatedAuthService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace Microsoft.ServiceModel.Samples
{
    // Define a service contract.
    [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
    public interface IIsolatedAuthService
    {
        [OperationContract]
        string IsAuthorized(string username, string roleName, string token);
        [OperationContract]
        string AuthenticateUser(string username, string encryptedPassword);
    }
}

IsolatedAuthService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.Security;
using System.Web.Configuration;
using System.Configuration;
using System.IO;
using System.Security.Permissions;
using System.Security.Principal;
using System.ServiceModel.Activation;
using System.Threading;

namespace Microsoft.ServiceModel.Samples
{
    public class IsolatedAuthService : IIsolatedAuthService
    {
        public string IsAuthorized(string username, string roleName, string token)
        {
            MembershipUser user = Membership.GetAllUsers()[username];

            Configuration config = ConfigurationManager.OpenExeConfiguration(HostingEnvironment.MapPath("~") + "\\web.config");           

            SessionStateSection sessionStateConfig = (SessionStateSection)config.SectionGroups.Get("system.web").Sections.Get("sessionState");

            InMemoryInstances instance = InMemoryInstances.Instance;

            // Check for session state timeout (could use a constant here instead if you don't want to rely on the config).
            if (user.LastLoginDate.AddMinutes(sessionStateConfig.Timeout.TotalMinutes) < DateTime.Now)
            {
                // Remove token from the singleton in this instance, effectively a logout.                
                instance.removeTokenUserPair(username); 
                return "User Unauthorized - login has expired!";
            }

            if (!instance.checkTokenUserPair(username, token))
                return "User Unauthorized - not a valid token!";

            // Check for role membership.
            if (!Roles.GetUsersInRole(roleName).Contains(user.UserName))
                return "User Unauthorized - Does not belong in that role!";

            return "Success - User is Authorized!";
        }

        public string AuthenticateUser(string username, string encryptedPassword)
        {
            if (Membership.ValidateUser(username, Decrypt(encryptedPassword)))
            {
                // Not sure if this is actually needed, but reading some documentation I think it's a safe bet to do here anyway.
                Membership.GetAllUsers()[username].LastLoginDate = DateTime.Now;

                // Send back a token!
                Guid token = Guid.NewGuid();

                // Store a token for this username.
                InMemoryInstances instance = InMemoryInstances.Instance;
                instance.removeTokenUserPair(username); //Because we don't implement a "Logout" method.
                instance.addTokenUserPair(username, token.ToString());

                return token.ToString();
            }

            return "Error - User was not able to be validated!";
        }
    }
}

InMemoryInstances.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.ServiceModel.Samples
{
    public class InMemoryInstances
    {
        private static volatile InMemoryInstances instance;
        private static object syncRoot = new Object();

        private Dictionary<string, string> usersAndTokens = null;

        private InMemoryInstances() 
        {
            usersAndTokens = new Dictionary<string, string>();
        }

        public static InMemoryInstances Instance
        {
            get 
            {
                if (instance == null) 
                {
                    lock (syncRoot)                  
                    {
                        if (instance == null) 
                            instance = new InMemoryInstances();
                    }
                }

                return instance;
            }
        }

        public void addTokenUserPair(string username, string token)
        {
            usersAndTokens.Add(username, token);
        }

        public bool checkTokenUserPair(string username, string token)
        {
            if (usersAndTokens.ContainsKey(username)) {
                string value = usersAndTokens[username];
                if (value.Equals(token))
                    return true;
            }

            return false;
        }

        public void removeTokenUserPair(string username)
        {
            usersAndTokens.Remove(username);
        }
    }
}

Bare in mind, this solution will not work if you're load-balancing your WCF service across multiple servers (due to the in-memory instance class), you could change the solution to use a database table instead of the in-memory instances if this is a requirement for you.

like image 130
OBR_EXO Avatar answered Nov 14 '22 22:11

OBR_EXO