Geeks With Blogs

News


Dylan Smith ALM / Architecture / TFS

I had to come up with a solution to provide Authentication for our ERP applications.  There were two major use case scenario's.  Either the user would be logged into Windows/Network using a domain account, and we could authenticate them by using Impersonation on our Web Server, or the user would be a shop floor operator who don't have domain accounts, primarily because multiple operators share the same computer.  The operators would have to authenticate themselves at the application level using a fingerprint scanner.  We store the fingerprint templates for all the operators in a SQL Server table to authenticate against.  There are number of problems we had to solve.  The fingerprint scanner interaction code has to live in the UI because the Web Service won't have access to the fingerprint scanner connected to the client PC (obviously).  However, we have a trust boundary at the Web Service layer, so we have to anticipate and protect against malicious consumers of our service. 

I'll use the example of our Web Service method ScrapParts().  ScrapParts() needs to know which PartNumber to scrap, what quantity to scrap, and who did the scrapping. 

1) We could just have the UI retrieve the fingerprint templates from the database by calling some web service method, then perform the authentication on the client and pass the EmployeeId directly to ScrapParts(). There are a few reasons why we decided this wouldn't work.  The performance would be unacceptable if the UI had to download the entire list of fingerprint templates every time it needed to authenticate.  Also this would not meet our security standards since it would be easily exploitable by a malicious user, they would be able to send any EmployeeId they wished to ScrapParts() whether they had authenticated or not (for ScrapParts() that's not such a big concern, but for methods such as LoginToShift() it is a larger concern).

2) We could implement an Authenticate() method in the Web Service that accepts the fingerprint scan (as a byte array), performs the authentication, then returns an EmployeeId.  The UI then passes that EmployeeId into ScrapParts().  Obviously this suffers from the same vulnerability as the last scenario, since a malicious user could just skip the Authenticate process entirely and pass in any EmployeeId they desired.  There is an advantage over method 1) however, since in this case the fingerprint templates don't need to be downloaded to the client, instead the comparison is done on the middle-tier.

3) We could pass the fingerprint scan directly to the ScrapParts() method.  This is an improvement over the last 2 options, since now a malicious user could not just pass in any EmployeeId, but they would have to actually have a fingerprint scan of the EmployeeId.  We still felt that this solution was somewhat vulnerable.  If a malicious user had access to a fingerprint scan of another EmployeeId they could impersonate that person.  They could retrieve the scan either because the other employee was also malicious and they were working together, or by intercepting the network traffic and extracting the fingerprint scan from a legitimate web service call (a replay attack).

4) The solution we ultimately came up with was to have an Authenticate() function in the Web Service that accepted a fingerprint scan (byte array), it would authenticate the fingerprint against the templates in the database.  Once it had authenticated the scan against a specific employee id it would generate a GUID and persist the GUID along with the associated EmployeeID into the database.  It would then return this GUID back to the UI.  When the UI called ScrapParts() instead of passing in a fingerprint scan, or an EmployeeId, it would pass in this GUID.  ScrapParts() would then call a private function in the business layer that would look up the GUID in the database, and return the associated EmployeeId; at the same time it would also place an ExpiryDate on the GUID record in the database so that this GUID could not be used again.  This idea of passing a GUID around instead of a fingerprint scan mitigates the risk of 2 or more malicious employees bypassing the system by sharing their fingerprints with each other.  And the idea of expiring the GUID so it's only one-time-use mitigates the risk of a replay attack.

We still have to deal with the scenario where the user is not a shop floor operator, and is logged into their computer using a domain account.  In this scenario, the UI can pass anything it wants into the GUID argument of ScrapParts, and ScrapParts will check to see if it is running under a valid domain account that is associated with a valid employee by checking the Employees database table (the Employees table has a field to associate EmployeeId with a domain account).

Most of this functionality can be factored out so that the actual effort to implement it in each method is minimal.  All of the Web Service methods that require authentication will have to have a GUID parameter added to their signature.  When the UI needs to call one of these methods it will do something like this:

MyWebService.ScrapParts(GetAuthGuid(), PartNumber, Quantity);

The GetAuthGuid function lives in the UI and looks something like this:

Guid GetAuthGuid(){
    if(MyWebService.GetLoggedOnEmployee() == 0)
        return MyWebService.Authenticate(ScanFingerprint());
    return null;
}

ScanFingerprint() contains the code to interact with the fingerprint scanner device and retrieve the actual scan as a byte array.  GetAuthGuid() uses 2 web service methods, GetLoggedOnEmployee() and Authenticate(), these 2 methods look like this:

[WebMethod()] int GetLoggedOnEmployee(){
    //retrieve the Active Directory account name from the current user context
    //look up that account name in the employees table to see if a matching employee can be found
    //if a match found return the employeeId, if not return 0
}

[WebMethod()] Guid Authenticate(byte[] fingerprintScan){
    //Retrieve Fingerprint Templates
    //Loop through templates and compare to fingerprintScan
    //if match found generate Guid
    //Store Guid in database along with related EmployeeId
    //return Guid
}

 

The ScrapParts() method (or any method that accepts an AuthGuid) will just have one extra line of code and look something like this:

[WebMethod()] void ScrapParts(Guid authGuid, string part, int qty){
    int EmployeeId = ValidateGuid(authGuid);
    //Perform scrap parts logic using EmployeeId
}

The ValidateGuid() is a function that lives in the middle-tier and not exposed to the UI.  It looks something like this:

int ValidateGuid(Guid authGuid){
    if(GetLoggedOnEmployee() == 0){
        //check if a non-expired record in the database matches authGuid
        //if found update the ExpiryDate on the row to Now
        //return the related EmployeeId
    }else{
        return GetLoggedOnEmployee();
    }
}

 

 

So the impact is relatively minor.  Every web method that needs authentication has to add an extra argument, and one line of code.  Every time the UI needs to call one of these methods it calls a single function to get the Guid it should use.  This should satisfy all of our requirements, both the different use cases, and the security requirements.

Leave a comment to let me know what you think.

Posted on Friday, June 30, 2006 6:56 AM | Back to top


Comments on this post: Authentication Solution

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Dylan Smith | Powered by: GeeksWithBlogs.net