How to enable basic authentication on WCF 4.0 REST Service? - wcf

First of all there are plenty of articles on the net for this but most of them are for WCF 3.5. I'm using some features of WCF 4.0 which makes the scenario a bit different.
Following is my contract:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet(UriTemplate="x?v={value}")]
string GetData(int value);
}
Following is my service class:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}
Following is my web.config
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint automaticFormatSelectionEnabled="true" />
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
<handlers>
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd"/>
</handlers>
</system.webServer>
I'm not using any svc file so in Global.asax I've written the following line of code to expose my service at path 'blah'
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add(new ServiceRoute("blah", new WebServiceHostFactory(), typeof(Service1)));
}
Now for enabling basic authentication if I change standard endpoint in web.config as follows:
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint automaticFormatSelectionEnabled="true">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</standardEndpoint>
</webHttpEndpoint>
</standardEndpoints>
I get the following error:
Could not find a base address that
matches scheme https for the endpoint
with binding WebHttpBinding.
Registered base address schemes are
[http].
What should I do from this point onwards to make it work? And where will I check the username/password supplied with each request?

This can't be tested in ASP.NET Development Server and for running in IIS 7.5 we have to install basic authentication and also we need to create a certificate and associate ssl binding with that application.
Doing all of that resulted in webservice methods to require basic authentication but it was working against AD.
There is no way to change it to authenticate it yourself.

Related

How to Implement custom authentication in WCF service

I would like to create WCF restful service for mobile application with custom authentication. First request should be login, specially client sending username, password and getting access token. Then all other requests should be check access token. Also for authentication I would like to use asp.net membership provider in other words to use Forms based authentication.
At first, we should configure the Asp.net SQL membership Provider. Then we should use Username/password security mode so that authenticate the client with custom credential.
Please refer to the below configuration.
<connectionStrings>
<add name="SqlConn" connectionString="server=myserver;database=aspnetdb;uid=sa;password=123456;" providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.web>
<membership defaultProvider="SqlMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add
name="SqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="SqlConn"
applicationName="WcfService2"
enablePasswordRetrieval="false"
enablePasswordReset="false"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
passwordFormat="Hashed" />
</providers>
</membership>
<roleManager enabled ="true"
defaultProvider ="SqlRoleProvider" >
<providers>
<add name ="SqlRoleProvider"
type="System.Web.Security.SqlRoleProvider"
connectionStringName="SqlConn"
applicationName="WcfService2"/>
</providers>
</roleManager>
<compilation debug="true" targetFramework="4.7.2" />
<httpRuntime targetFramework="4.7.2"/>
</system.web>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding>
<security mode="Message">
<message clientCredentialType="UserName"></message>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="SqlRoleProvider">
</serviceAuthorization>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="SqlMembershipProvider"/>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" findValue="974ad39ff0b86210f5e7d661e56945ad5c2d3770"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="wsHttpBinding" scheme="http" />
</protocolMapping>
If we use WCF to create Restful Service, we should replace the WSHttpbinding with Webhttpbinding.
Before setup the connection string, we should install the asp.net sql membership provider. it ordinarily located in the “C:\Windows\Microsoft.NET\Framework64\v4.0.30319” folder.
Aspnet_regsql.exe utility.
Here is a simple tutorial.
http://mahedee.net/asp-net-membership-step-by-step/
Here is an official example.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/membership-and-role-provider
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-use-the-aspnet-membership-provider
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-use-the-aspnet-role-provider-with-a-service
Feel free to let me know if the problem still exist.
Here is my solution without service configuration. If you have configured asp-net membership provider in web.config.
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class SPHostedWCFService
{
[OperationContract]
[WebGet(UriTemplate = "Login?username={username}&password={password}", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public void Login(string username, string password)
{
FormsAuthenticationTicket ticket = null;
MembershipProvider membershipProvider = GetMembershipProvider();
if (membershipProvider.ValidateUser(username, password))
{
SPUser user = RunWithEP.web.EnsureUser(username);
ticket = new FormsAuthenticationTicket( 1, username, DateTime.Now, DateTime.Now.AddDays(1), true, user.ID.ToString());
}
if (ticket != null)
{
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Current.Response.Cookies.Add(cookie);
}
else
{
HttpContext.Current.Response.Write("Username or password incorrect.");
}
}
[OperationContract]
[WebGet(UriTemplate = "DoWork", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[PrincipalPermission(SecurityAction.Demand, Authenticated = false)]
public string DoWork()
{
if (HttpContext.Current.Request.IsAuthenticated)
{
return "authenticated request";
}
else
{
HttpContext.Current.Response.Write("Username not authenticated.");
return "not authenticated request";
}
}
}
GetMembershipProvider() specific to may environment, specially I'm using in SharePoint.

404 errors for PUT and DELETE requests on deployed WCF RESTful Service

I have deployed an MVC3 and WCF web service as a single application. Both work as expected. GET and POST requests work perfectly, but the PUT and DELETE requests return 404 errors. These work fine locally. Initially it was requesting a username/password for PUT/DELETE requests.
Here is my WebServer config from my web.config file
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true">
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAVModule" />
</handlers>
<security>
<authorization>
<remove users="*" roles="" verbs="" />
<add accessType="Allow" users="*"
verbs="GET,HEAD,POST,DEBUG,PUT,DELETE" />
</authorization>
</security>
</system.webServer>
Here are my PUT and DELETE methods:
[OperationContract]
[WebInvoke(UriTemplate = "{id}", Method = "PUT")]
public MyResource Put(MyResource updatedResource, int id)
{
MyResource existingResource = Database.GetResourceById(id);
existingResource.Name = updatedResource.Name;
Database.SaveResource(existingResource);
return existingResource;
}
[OperationContract]
[WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
public MyResource Delete(int id)
{
MyResource sampleResource = Database.DeleteResourceById(id);
return sampleResource;
}
My set up:
.NET 4.0
MVC3
IIS 7.0
Note: I am on a shared hosting plan, therefore do not have direct access to IIS7.0 a so I need to make changes via the web.config file.
Enable Tracing on your service and see why you get a 404 error when you try for a PUT or DELETE action.

Enabling WCF Help page changes response from JSON to XML

I'm working on creating a WCF web service that communicates via JSON. I got the service to a point that it's working and I'm trying to set up the help page so the developers that will consume the service can have some documentation to work by.
The issue that I'm running into is that when I did get the help page up and running, all the responses being sent out by my service changed from JSON to XML.
I'll be the first to admit that I'm very new to this. There might be some fundamental flaw with how I've structured my service, or it might be as simple as a flag I missed in the web.config... I'm really at a loss at this point.
What I found, through basically just trial and error and beating my head against the wall, was if I change the name attribute of the following line in the Web.config:
<standardEndpoint name="serviceEndpoint" helpEnabled="true" automaticFormatSelectionEnabled="true">
To be empty string:
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true">
The help page magically shows up, but my services are now spitting out XML instead of JSON.
I think it's probably better to over-share than to under-share for something as specific as this, so here's what I think is the relevant bits of the set-up. I apologize for the mono-tone code, I can edit it to be more readable if I figure out how.
Service Interface:
[OperationContract]
[Description("DESCRIPTIONATION HAPPENS")]
[WebInvoke(Method = "GET",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "GetYears")]
GetYearsReply GetYears();
...
Service Implementation:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MPG : IMPG
{
public GetYearsReply GetYears()
{
GetYearsReply reply = new GetYearsReply();
reply.YearList = generateYears();
return reply;
}
...
Global.asax:
<%# Application Codebehind="Global.asax.cs" Inherits="MPG_Service.Global" Language="C#" %>
Global.asax.cs:
namespace MPG_Service
{
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteTable.Routes.Add(new ServiceRoute("garage", new WebServiceHostFactory(), typeof(MPG)));
}
}
}
Web.config:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
<standardEndpoints>
<webHttpEndpoint>
<!--
Configure the WCF REST service base address via the global.asax.cs file and the default endpoint
via the attributes on the <standardEndpoint> element below
-->
<standardEndpoint name="serviceEndpoint" helpEnabled="true" automaticFormatSelectionEnabled="true">
<!--<security mode="Transport">
<transport clientCredentialType="None"/>
</security>-->
</standardEndpoint>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>
If anyone has any insight into why this behavior is happening, or any other major screw-ups in my code I'd love any input.
Your client is saying that it accepts XML (application/xml), so that's what WCF is returning. That is consistent with the Automatic Formatting rules (see details at http://msdn.microsoft.com/en-us/library/ee476510.aspx). If you don't want that behavior, then set autoFormatSelectionEnabled to false in your configuration.

WCF REST WebService content type is wrong

I created a simple REST Service using the WCF REST Template 40(CS) which is working just fine. There only is a problem that the response uses "application/json" as content type but I need "text/plain".
The problem is already explained in a blog post but because of the template I'm not using a .svc file. So the proposed solution does not work for me.
My Service contract:
[ServiceContract]
public interface ICouchService
{
[OperationContract]
[WebInvoke(ResponseFormat = WebMessageFormat.Json, UriTemplate = "/", Method = "GET")]
ServiceInformation Hello();
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "/piclib/{id}")]
CouchDocument GetDocument(string id);
}
web.config:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="false"/>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
If you want to return an arbitrary content from a WCF REST service, you need to use the Raw programming model - http://blogs.msdn.com/b/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-web.aspx. The template you used defines the endpoint using the service route, so it's all set up for you. Now you need to define the operation returning a Stream parameter, and set the appropriate content type in the operation: WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";

Subscribing to TFS events and WCF

Sorry for asking a question about something I don't know much about, but I've been pulling my hair out trying to get this working.
So, I have a WCF service that is hosted on IIS and seems to be working insomuch that I can "see" it on the network by going to http://servername/MyService.svc in a browser.
That .svc looks like:
<% #ServiceHost Service="Foo.Bar" %>
The relevant code looks like:
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06Services/Notification/03")]
public interface IBar
{
[OperationContract(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", ReplyAction = "*")]
[XmlSerializerFormat(Style = OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml);
}
and:
public class Bar : IBar
{
public void Notify(string eventXml, string tfsIdentityXml)
{
// Just some test output to see if it worked
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "tfs.txt");
File.WriteAllText(path, tfsIdentityXml + eventXml);
}
}
That's all been built and the ensuing .dll put into the bin dir in the site root in IIS.
I now want to subscribe via bissubscribe.exe (or a similar method) to TFS check-in events. I tried doing something like:
bissubscribe /eventType CheckinEvent
/address http://servername/MyService.svc
/deliveryType Soap
/server mytfsserver
But nothing; it doesn't even look like there was log activity. So keeping in mind I know nothing about WCF, what am I doing wrong? I imagine the address param is one thing; am I not supposed to point it to the .svc?
I have created a blog post how you can use WCF in combination with the Event Services of TFS: http://www.ewaldhofman.nl/post/2010/08/02/How-to-use-WCF-to-subscribe-to-the-TFS-2010-Event-Service-rolling-up-hours.aspx
TFS 2010 and WCF 4.0 configurations are described below...
Method signature:
public void Notify(string eventXml) /* No SubscriptionInfo! */
Web config:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="Microsoft.TeamFoundation, Version=10.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
</assemblies>
</compilation>
</system.web>
<system.serviceModel>
<services>
<service behaviorConfiguration="NotificationServiceBehavior" name="TF.CheckinListener.CheckinListener">
<endpoint address="Notify" binding="wsHttpBinding" bindingConfiguration="noSecurity" contract="TF.CheckinListener.ICheckinListener" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="NotificationServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
</binding>
<wsHttpBinding>
<binding name="noSecurity" maxBufferPoolSize="20000000" maxReceivedMessageSize="200000000">
<readerQuotas maxStringContentLength="200000000" maxArrayLength="200000000" />
<security mode="None" />
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
Subscription address for bissubscribe:
http://MachineName/VirtualDirectoryName/Service.svc/Notify
One point that jumps out is the fact you have a method that doesn't return anything except void. Those should be marked as "one-way" method in WCF:
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06Services/Notification/03")]
public interface IBar
{
[OperationContract(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", ReplyAction = "*", IsOneWay=true)]
[XmlSerializerFormat(Style = OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml);
}
Add the "IsOneWay=true" to your [OperationContract] attribute.
Other than that, there's nothing obviously wrong in your code, but to really tell, we'd need a lot more config info to really tell. Try the IsOneWay=true first and see if that solves your issue.
How is your service configured? In particular, is it configured to use basicHttpBinding?
Try creating a client to call your service to make sure it can be called.
Then, see if there's an example service from the TFS SDK - see if you can get the example to work.
I was able to complete this connection with the following:
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
public interface ITeamSystemObserver : IObservable
{
[OperationContract( Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", ReplyAction = "*" )]
[XmlSerializerFormat(Style=OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml, SubscriptionInfo SubscriptionInfo);
}
Note you are missing the SubscriptionInfo parameter. Here is my web.config:
<basicHttpBinding>
<binding name="TfsEventServiceBasic">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" />
</security>
</binding>
</basicHttpBinding>