Trying to add API Key authorization to an existing WCF service hosted on IIS 7
Followed Ron Jacob's tutorial for creating a class derived from ServiceAuthorizationManager.
It is not being invoked.
In case my understanding is wrong, I am expecting that all I have to do is correctly make the class and refer to it in Web.Config.
At that point my test web client should cease to get data from the service until the client is altered to handle API Key process flow.
The client however still consumes the contracts correctly and the Eventlog messages that I have placed in the ServiceAuthorizationManager class are not being generated.
I thought it must be the behavior node that I created
in the web.config but I have created that both manually and using the Visual Studio config editor tool and both entries don't work.
I believe the web config serviceAuthorization node is correct in that it correctly refers to the Namespace.Class of the authorization class and I have double checked that the assembly in the bin directory of the Webservice is CouponParkingWCF.dll.
The Class code is:
namespace CouponParkingWCF
{
public class APIKeyAuthorization:ServiceAuthorizationManager
{
public const string APIKEY = "ApiKey";
public const string APIKEYLIST = "APIKeyList";
public string GetAPIKey(OperationContext operationContext)
{
// Get the request message
ClsLogger.WriteInfoLog("InsideGetAPIKey");
var request = operationContext.RequestContext.RequestMessage;
// Get the HTTP Request
var requestProp = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
// Get the query string
NameValueCollection queryParams = HttpUtility.ParseQueryString(requestProp.QueryString);
// Return the API key (if present, null if not)
return queryParams[APIKEY];
}
public List<Guid> APIKeys
{
get
{
// Get from the cache
// Could also use AppFabric cache for scalability
var keys = HttpContext.Current.Cache[APIKEYLIST] as List<Guid>;
if (keys == null)
keys = PopulateAPIKeys();
return keys;
}
}
private List<Guid> PopulateAPIKeys()
{
Dt dt = new Dt();
List<Guid> keyList = dt.GetApiKeys();
return keyList;
}
public bool IsValidAPIKey(OperationContext operationContext)
{
// if verification is disabled, return true
//if (Global.APIKeyVerification == false)
// return true;
ClsLogger.WriteInfoLog("InsideIsValidAPIKey");
//return true;
string key = GetAPIKey(operationContext);
Guid apiKey;
// Convert the string into a Guid and validate it
if (Guid.TryParse(key, out apiKey) && APIKeys.Contains(apiKey))
{
return true;
}
// Send back an HTML reply
CreateErrorReply(operationContext, key);
return false;
}
private void CreateErrorReply(OperationContext operationContext, string key)
{
ClsLogger.WriteErrorLog("We have an Authorization Error. Oh Dear.");
}
protected override bool CheckAccessCore(OperationContext operationContext)
{
return IsValidAPIKey(operationContext);
}
}
}
The web config behaviors node is:
<behaviors>
<endpointBehaviors>
<behavior name="RestJSONEndpointBehavior">
<webHttp helpEnabled="false" defaultBodyStyle="Bare" defaultOutgoingResponseFormat="Json" />
</behavior>
<behavior name="RestXMLEndpointBehavior">
<webHttp helpEnabled="false" defaultOutgoingResponseFormat="Xml" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<serviceAuthorization serviceAuthorizationManagerType="CouponParkingWCF.APIKeyAuthorization, CouponParkingWCF, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null" />
</behavior>
<behavior name="wsdl">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
hopefully someone can spot what I have done wrong.
Thanks
Bob
The problem is that I put the serviceAuthorization node inside its own behavior node.
It should go inside the existing"wsdl" node
<serviceBehaviors>
<behavior name="wsdl">
<serviceAuthorization serviceAuthorizationManagerType="CouponParkingWCF.APIKeyAuthorization, CouponParkingWCF, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null" />
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
This got the API KEY working but then we need to have it working for GET and POST.
Ron's example shows using the querystring which is fine for GET but not for POST.
So we moved to putting the API Key into the header. The WCF key extraction code is:
public string GetAPIKey(OperationContext operationContext)
{
try
{
var request = operationContext.RequestContext.RequestMessage;
var requestProp = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
string key = requestProp.Headers["ApiKey"];
return key;
}
catch (Exception exception)
{
ClsLogger.WriteErrorLog("GetAPIKey " + exception.Message);
throw;
}
}
Related
I'm trying to assemble a .Net 6 WCF Service with WCFCore, using a basicHttpBinding, and I'm strugling to add a service authorization manager.
My purpose is to enable WCF to read and validate bearer tokens and use OAuth. I can't move to REST because of legacy applications compatibility, so I need to keep WCF but use bearer tokens.
My service at this stage is quite simple:
[ServiceContract]
public interface IService
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
}
public class Service : IService
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
// Use a data contract as illustrated in the sample below to add composite types to service operations.
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
}
My Program.cs:
var builder = WebApplication.CreateBuilder();
builder.Services.AddServiceModelServices();
builder.Services.AddServiceModelConfigurationManagerFile("wcf.config");
builder.Services.AddServiceModelMetadata();
builder.Services.AddSingleton<IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior>();
builder.Services.AddSingleton<OAuthAuthorizationManager>();
var app = builder.Build();
app.UseServiceModel(bld =>
{
bld.AddServiceEndpoint<Service, IService>(new BasicHttpBinding(BasicHttpSecurityMode.Transport), "/Service.svc");
var mb = app.Services.GetRequiredService<ServiceMetadataBehavior>();
mb.HttpsGetEnabled = true;
});
app.Run();
Then my wcf.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="basicBinding" receiveTimeout="00:10:00">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="CoreWCFService.Service" behaviorConfiguration="Default">
<endpoint address="basic" binding="basicHttpBinding" bindingConfiguration="basicBinding" contract="CoreWCFService.IService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Default">
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
<serviceAuthorization serviceAuthorizationManagerType="CoreWCFService.OAuthAuthorizationManager,CoreWCFService" />
<dataContractSerializer maxItemsInObjectGraph="10000000" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
But when I call the service with tokens, nothing happens on the authorization manager, the operation runs simply ignoring this service behavior.
Is there anyone out there that can help me with this?
You may refer to the Corewcf project template. There are a few things to note:
The interface and its implementation need to be separated to facilitate subsequent maintenance and invocation of the interface.
We need to look at the UseServiceModel part in Program.cs.
I have a WCF Service which is using DI via MEF. That part is working fine.
I also have a Custom UserNamePasswordValidator which works as long as I use a parameterless constructor and 'new' everything up. However I would like to introduce MEF into that as well.
The service is hosted in IIS so I have to intercept it somewhat to get MEF to work as it is.
I've provided cross sections of my code below and any help would be appreciated!
Here is my web.config: -
<behavior name="StandardServiceBehaviour">
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"WebService.Validators.CustomUserNamePasswordValidator, WebService" />
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<dataContractSerializer maxItemsInObjectGraph="67108864" />
</behavior>
My custom validator is as follows: -
[Export(typeof(ICustomUserNamePasswordValidator))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class CustomUserNamePasswordValidator : UserNamePasswordValidator, ICustomUserNamePasswordValidator
{
[ImportingConstructor]
public CustomUserNamePasswordValidator([NotNull] IConnectionProvider connectionProvider)
{
}
.....
}
I'm using a custom attribute on my Web Service called WebServiceExport, which subclasses the MEF Export attribute and includes an InstanceProvider: -
[WebServiceExport(typeof(IGeneralService))]
public class GeneralService : IGeneralService
{
....
}
Here is the Export Attribute: -
public class WebServiceExportAttribute : ExportAttribute, IContractBehavior, IContractBehaviorAttribute
{
public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, DispatchRuntime dispatch)
{
var contractType = description.ContractType;
dispatch.InstanceProvider = new MefInstanceProvider(contractType);
}
....
}
And finally here is the Instance Provider: -
public class MefInstanceProvider : IInstanceProvider
{
public MefInstanceProvider(Type serviceContract)
{
_serviceContract = serviceContract;
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
BuildInstance(); //compose MEF parts
}
....
}
I am looking at implementing IDispatchMessageInpector & IClientMessageInpector to look at the message objects in AfterReceiveRequest and BeforeSendRequest Methods.
My requirement is to make changes at code level of WCF service. No Configuration Changes.
How to attach this behaviour to all the endpoints which are calling this service and to service itself. Is implementing IContractBehaviour helps me?
Edit 1:
WCF Service is hosted on IIS. Is it possible to add the behavior thru code?
Edit 2:
Seems like using ServiceHostFactory we can achieve this.
How can i add behavior to client endpoint which are defined in webconfig?
yes, it is possible to add behavior for services hosted in IIS. Behaviors are not concerned with the hosting environment of the service. Carlos Figueira's blog provides examples of all types of behaviors you could apply to Service, Endpoints, Contracts and Operations. A sample code that I tried for my service hosted in IIS (with endpoint defined in web.config file) - Config file here needs to add the behavior as ExtensionElement
public class MyEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
Console.WriteLine("applying dispatch behavior");
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyInspector());
endpointDispatcher.DispatchRuntime.OperationSelector = new MyOperationSelector();
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
public override Type BehaviorType
{
get { return this.GetType(); }
}
protected override object CreateBehavior()
{
return new MyEndpointBehavior();
}
}
public class MyOperationSelector : IDispatchOperationSelector
{
public string SelectOperation(ref Message message)
{
Console.WriteLine("good luck");
string action = message.Headers.Action;
return action.Substring(action.LastIndexOf('/') + 1);
}
}
public class MyInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
return (Message) request;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
}
}
}
Config file with behavior added as extension element -
<system.serviceModel>
<services>
<service name="RouteToServiceA.Service1">
<endpoint address="Service1" binding="basicHttpBinding" contract="RouteToServiceA.IService1" behaviorConfiguration="testEndPoint" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="testEndPoint">
<testBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="testBehavior" type="RouteToServiceA.MyEndpointBehavior, RouteToServiceA" />
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Using ServiceHostFactory we can add service behavior whereas adding behavior configuration to client endpoints which are in config seems like not possible to achieve. So i am going with configuration changes
For my recent project, I created a Web service that returns an array of custom type to jquery client-side code. WCF is called by $.ajax command and is in the same domain.
When I run my web applicaiton on localhost (which is IIS run on local machine), everything works fine. When I deploy it to our integration server, suddenly ajax call to WCF ends with an error: "parsererror - unterminated string constant" and status of 200. Returned message is however something like "[{\"Text\":\"Test dodatnih naslov", which of course is not a correct json format.
Correct response should have been: "[{"Text":"Test dodatnih naslovov","Value":"100"},{"Text":"Test dodatnih naslovov - ISO2","Value":"101"},{"Text":"UPDATE","Value":"102"}]"
I have traced WCf service for malfuncitons, but it does not seem to be crashing. I also tried and set timeout to ajax call, but to no avail. Some help would be much appreciated.
My IIS is IIS7, where integration runs IIS6 on Windows Server 2008.
js file
function InsuranceClientContact_ItemsRequesting(o, e) {
var $ = $telerik.$;
var urlSvc = ServiceBaseUrl + '/GetContacts'
$.ajax({
type: "POST",
url: urlSvc,
data: '{"ixClient": ' + selectedItemId + '}', //selectedItemId is a positive number
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function (data) {
// do something
},
error: function (result) {
var msg = result.status + " - " + result.statusText;
setTimeout(function () { throw new Error(msg) }, 0);
}
});
wcf interface
namespace Sid.Skode.Web.Services.Populate {
[ServiceContract]
public interface IInsuranceClientContactService {
[OperationContract]
[WebInvoke(Method="POST",
BodyStyle=WebMessageBodyStyle.WrappedRequest,
ResponseFormat=WebMessageFormat.Json)]
Contact[] GetContacts(long ixClient);
}
[DataContract]
public class Contact {
[DataMember]
public string Text;
[DataMember]
public string Value;
}
}
wcf service implementation
namespace Sid.Skode.Web.Services.Populate {
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]
public class InsuranceClientContactService : IInsuranceClientContactService {
public Contact[] GetContacts( long ixClient ) {
return GetContactsFromDatabase( ixClient );
}
#region Private methods
private Contact[] GetContactsFromDatabase( long ixClient ) {
DataTable dt = GetDataFromDataBaseById( ixClient );
return ConvertDataTableToContactArray( dt );
}
private DataTable GetDataFromDataBaseById( long ixClient ) {
AutoCompleteBLL model = new AutoCompleteBLL();
return model.SearchContactsByPartner( ixClient );
}
private Contact[] ConvertDataTableToContactArray( DataTable dt ) {
Contact[] rgContact = new Contact[dt.Rows.Count];
int cnContact = 0;
foreach (DataRow dr in dt.Rows) {
if (!dr.IsNull( "NAZIV" )) {
Contact contact = new Contact();
contact.Text = dr["NAZIV"].ToString();
contact.Value = dr["ID_DODATEN_KONTAKT"].ToString();
rgContact[cnContact++] = contact;
}
}
return rgContact;
}
#endregion
}
}
web.config wcf part
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="httpServiceBehavior">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="httpEndpointBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="webHttpBindingWithTransportWindowsSecurity">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</webHttpBinding>
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="false" aspNetCompatibilityEnabled="true" />
<services>
<service name="Sid.Skode.Web.Services.Populate.InsuranceClientContactService" behaviorConfiguration="httpServiceBehavior">
<endpoint address="" binding="webHttpBinding" bindingConfiguration="webHttpBindingWithTransportWindowsSecurity"
contract="Sid.Skode.Web.Services.Populate.IInsuranceClientContactService"
behaviorConfiguration="httpEndpointBehavior">
</endpoint>
<endpoint
address="mex"
binding="mexHttpsBinding"
bindingConfiguration=""
contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
As described here, you need remove all instances of RadCompression http module from web config. Then, it works.
I just need to secure my WF services. Can't find any resources on this. How to do it?
Already tried:
class Program
{
static void Main(string[] args)
{
using (WorkflowServiceHost host = new WorkflowServiceHost(new Workflow1(), new Uri("http://localhost/Test")))
{
host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new Test();
host.Open();
Console.Write("ready");
Console.ReadLine();
}
}
}
public class Test : UserNamePasswordValidator
{
public Test()
{
Console.Write("hit");
}
public override void Validate(string userName, string password)
{
Console.Write("never hit");
}
}
And a config
<bindings>
<wsHttpBinding>
<binding>
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<!--<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="myAssembly.Test, myAssembly" />
</serviceCredentials>-->
</behavior>
</serviceBehaviors>
Can't create a fixed name endpoint because they are dynamically created
UPDATE - I tried the configuration bellow and worked, but I want a more granular way to set what binding each service use
<protocolMapping>
<add scheme="http" binding="wsHttpBinding"/>
</protocolMapping>
We have an episode of Workflow TV that should help. Workflow TV - Workflow Services Security
As far as the messaging part this is just WCF so anything you can do with WCF should work here.
That said with workflow you typically need more fine grained control on all but the first request. For example all employees can start en expense report but only the employee who started a specific expense report can add expenses to it and submit it. You can do these kind of security checks using the WF Security Pack.
A little hackish, but works. Overrided WorkflowServiceHost in order to grab unknown contract names and added service endpoints for each one.
const string DEFAULT_WORKFLOW_SERVICE_BINDING_NAME = "WorkflowDefaultBinding";
static void Main(string[] args)
{
MyWorkflowServiceHost host = new MyWorkflowServiceHost(new CountingWorkflow2(), new Uri(hostBaseAddress));
foreach (var contractName in host.ImplementedContractsNames)
{
// now I'm able to choose which binding to use depending on a condition
var binding = new WSHttpBinding(DEFAULT_WORKFLOW_SERVICE_BINDING_NAME);
host.AddServiceEndpoint(contractName, binding, string.Empty);
}
}
And MyWorkflowServiceHost
public class MyWorkflowServiceHost : WorkflowServiceHost
{
public MyWorkflowServiceHost(Activity activity, params Uri[] baseAddresses)
: base(activity, baseAddresses)
{
}
private IDictionary<string, System.ServiceModel.Description.ContractDescription> _implementedContracts;
public IEnumerable<string> ImplementedContractsNames
{
get
{
foreach (var contract in _implementedContracts)
yield return contract.Key;
}
}
protected override System.ServiceModel.Description.ServiceDescription CreateDescription(out System.Collections.Generic.IDictionary<string, System.ServiceModel.Description.ContractDescription> implementedContracts)
{
System.ServiceModel.Description.ServiceDescription description = base.CreateDescription(out implementedContracts);
_implementedContracts = implementedContracts;
return description;
}
}
Adding a unamed WSHttpBinding and the following section on service model should work too, but for default configuration
<protocolMapping>
<add scheme="http" binding="wsHttpBinding"/>
</protocolMapping>