I needed to pick an approach to security for my new web front end for my open source project
Presto. My first step was to ask for overall direction from some industry experts. So, special thanks to Jim Manico (
@manicode,
site) for helping me. While this approach is still evolving as I work through it, I wanted to summarize the security implementation here.
Overview
Instead of defining roles and allowing things based on those roles, define permissions (functionality seems to be a more intuitive word here) and allow things based on those permissions. For example, if I define an admin role, some company may want some admins to do some things and other admins to do different things, so don't lump them all together. Instead, create a list of specific functionality (print this, save that, etc.) and assign each user(?) to each functionality. Then there is a lot more flexibility.
With this approach, we won't have to change source code in the future. For example, if we wanted to add a new AD group, and we specified AD groups/users in code, we'd have to change code. With this approach, each piece of functionality gets its own unique name. Then each is mapped to AD groups in some central store.
Note: We ended up abandoning this approach for one main reason: our security line of trust is at the logic layer in our business logic classes. If we secure at the Web API, then we have to secure correctly at every Web API we implement. It's better to have security in one spot, which, in our case, is at each method in our logic layer.
Implementation
Web API
Web.config (the key is the functionality, the value is the AD group):
<add key="DeleteUser" value="Some AD Group"/>
(Note: The web.config is used here just as a demo to get this working. Perhaps this info should be in the DB.)
FunctionalityAttribute:
public class FunctionalityAttribute : AuthorizeAttribute
{
public string FunctionalityName { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
string adGroup = WebConfigurationManager.AppSettings[FunctionalityName];
if (actionContext.RequestContext.Principal.IsInRole(adGroup)) { return true; }
return false;
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
// Authenticated, but not authorized.
if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
{
// Use Forbidden because Unauthorized causes a login prompt to display.
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
API Controller:
[Functionality(FunctionalityName = "DeleteUser")]
public class AppsController : ApiController
(Note: The attributes will actually go on the methods, not the class. This example is just to test at the moment.)
Dealing with success and failure in the AngularJS repository call
$http.get('/PrestoWeb/api/apps/')
.then(function (result) {
data = result.data;
$rootScope.setUserMessage("Application list refreshed");
callbackFunction(data);
}, function (response) {
console.log(response);
if (response.status == 403) {
$rootScope.setUserMessage("Unauthorized");
callbackFunction(null);
}
});
JavaScript
if (user.canAccess("DeleteUser")) {
// Set AngularJS scope variable that shows that the delete button should be enabled
}
WCF Service
What's to stop someone from changing the JavaScript and getting the DELETE button enabled? Nothing, that's why you don't trust the browser. Whatever security we implement on the web page must also be implemented at the service or logic layer. In my case, that would be the WCF service. So while the user can enable the DELETE button by hacking the JS, he won't actually be able to delete because the security at the service (or logic) layer will prevent that.
ToDo: Show WCF security implementation here after I do it.
Resources
Cross Site Scripting Prevention Cheat Sheet
Java Authorization Guide
Some Notes
As a product, you need to support many different kinds of authentication.
As a service, you need to support SAML and similar for federation - especially for big customers.