I am trying to implement security for the WCF rest service which will be exposed over the net for consuming. Here are the requirements
Authorization for Service and Specific API's
The service should authorize the partner and check if the partner has the access to the API which is called and i have multiple partners calling these restful APIs.
How do I authorize each of these partners for APIs in a centralized way?
Authentication for the User
I need to perform Authentication for users in order to perform the Add,Delete operations.
How do I authenticate the users for specific APIs in centralized way.
Look at Azure Storage REST api security documentation here to get a fair idea about how MS has designed security around their API.
Most of the REST API i have seen use a API token based approach where these tokens are passed along each request to identify the caller.
Also look at this thread
I used the following appraoch to implement the Authorization and Authentication fot the rest services.
Used the Custom Attribute which Implements the Attribute, IOperationBehavior, IParameterInspector
Here is the Implementation.
public class AuthorizationAttribute : Attribute, IOperationBehavior, IParameterInspector
{
public SLCE.Operations Operation { get; set; }
public bool IsAuthenticationRequired { get; set; }
#region IOperationBehavior Members
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.ParameterInspectors.Add(this);
}
public void Validate(OperationDescription operationDescription)
{
}
#endregion
#region IParameterInspector Members
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{
}
public object BeforeCall(string operationName, object[] inputs)
{
string publicKey = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
bool flag = AuthorizationHelper.CheckPartnerAuthorization(this.Operation, publicKey);
if (!flag)
{
LicensingValidationHelper.ThrowLicensingException(HttpStatusCode.Unauthorized, SLCE.LicensingStatus.PartnerNotAuthorized.ToString());
}
else if(IsAuthenticationRequired)
{
string authenticationKey = WebOperationContext.Current.IncomingRequest.Headers["Authentication"];
bool isAuthenticated = AuthorizationHelper.CheckUserAuthentication(authenticationKey);
if (!isAuthenticated)
{
LicensingValidationHelper.ThrowLicensingException(HttpStatusCode.Unauthorized, SLCE.LicensingStatus.UserNotAuthorized.ToString());
}
}
return null;
}
#endregion
}
Implemented a custom Beahavior to handle the exceptions.
public class LicensingBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
int errorHandlerCount = endpointDispatcher.ChannelDispatcher.ErrorHandlers.Count;
base.AddServerErrorHandlers(endpoint, endpointDispatcher);
IErrorHandler webHttpErrorHandler = endpointDispatcher.ChannelDispatcher.ErrorHandlers[errorHandlerCount];
endpointDispatcher.ChannelDispatcher.ErrorHandlers.RemoveAt(errorHandlerCount);
RestErrorHandler newHandler = new RestErrorHandler(webHttpErrorHandler,DefaultOutgoingResponseFormat);
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(newHandler);
}
}
Then implemented the IErrorhandler to send the status code and description if the Authorization or authentication fails.
public class RestErrorHandler : IErrorHandler
{
IErrorHandler _originalErrorHandler;
WebMessageFormat _format;
public RestErrorHandler(IErrorHandler originalErrorHandler,WebMessageFormat format)
{
this._originalErrorHandler = originalErrorHandler;
this._format = format;
}
public bool HandleError(Exception error)
{
return error is WebProtocolException;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
WebProtocolException licensingException = error as WebProtocolException;
if (licensingException != null)
{
fault = Message.CreateMessage(version, null, new ValidationErrorBodyWriter(licensingException));
if (_format == WebMessageFormat.Json)
{
HttpResponseMessageProperty prop = new HttpResponseMessageProperty();
prop.StatusCode = licensingException.StatusCode;
prop.Headers[HttpResponseHeader.ContentType] = "application/json; charset=utf-8";
fault.Properties.Add(HttpResponseMessageProperty.Name, prop);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
}
else if(_format == WebMessageFormat.Xml)
{
HttpResponseMessageProperty prop = new HttpResponseMessageProperty();
prop.StatusCode = licensingException.StatusCode;
prop.Headers[HttpResponseHeader.ContentType] = "application/xml; charset=utf-8";
fault.Properties.Add(HttpResponseMessageProperty.Name, prop);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Xml));
}
}
else
{
this._originalErrorHandler.ProvideFault(error, version, ref fault);
}
}
class ValidationErrorBodyWriter : BodyWriter
{
private WebProtocolException validationException;
Encoding utf8Encoding = new UTF8Encoding(false);
public ValidationErrorBodyWriter(WebProtocolException validationException)
: base(true)
{
this.validationException = validationException;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("root");
writer.WriteAttributeString("type", "object");
writer.WriteStartElement("StatusCode");
writer.WriteAttributeString("type", "string");
writer.WriteString(this.validationException.StatusCode.ToString());
writer.WriteEndElement();
writer.WriteStartElement("Description");
writer.WriteAttributeString("type", "string");
writer.WriteString(this.validationException.StatusDescription);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
}