How to add ws-security header in .net core? - c#

I'm trying to make a call to a webservice and want to manually add the ws-security headers into the request because .net core 2.2 currently does not support ws-security.
I have created my custom security header class:
public class SoapSecurityHeader : MessageHeader
{
private readonly string _password, _username;
public SoapSecurityHeader(string id, string username, string password)
{
_password = password;
_username = username;
}
public override bool MustUnderstand => true;
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsse", Name, Namespace);
writer.WriteAttributeString("s", "mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
writer.WriteXmlnsAttribute("wsse", Namespace);
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsse", "UsernameToken", Namespace);
writer.WriteAttributeString("wsu", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "UsernameToken-32");
// Username
writer.WriteStartElement("wsse", "Username", Namespace);
writer.WriteValue(_username);
writer.WriteEndElement();
// Password
writer.WriteStartElement("wsse", "Password", Namespace);
writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
writer.WriteValue(_password);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
And this is my method calling the SOAP service:
public ActionResult<Ted_Result> Get(DateTime dateFrom, DateTime dateTo, int? pageFrom, int? pageTo)
{
BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
EndpointAddress endpointAddress = new EndpointAddress(new Uri("https://localhost/SomeService.svc"));
ChannelFactory<IConnectPublicService> factory = new ChannelFactory<IConnectPublicService>(basicHttpBinding, endpointAddress);
GetContractNoticesResponseMessage result = null;
// Bypass SSL/TLS secure channel validation
#if DEBUG
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck
};
#endif
// Debugging inspector
factory.Endpoint.EndpointBehaviors.Add(new InspectorBehavior());
IConnectPublicService serviceProxy = factory.CreateChannel();
((ICommunicationObject)serviceProxy).Open();
var opContext = new OperationContext((IClientChannel)serviceProxy);
var soapSecurityHeader = new SoapSecurityHeader("UsernameToken-32", "sampleUsername", "samplePassword123");
// Adding the security header
opContext.OutgoingMessageHeaders.Add(soapSecurityHeader);
var prevOpContext = OperationContext.Current; // Optional if there's no way this might already be set
OperationContext.Current = opContext;
var info = new ExternalIntegrationRequestMessageInfo
{
UserCode = "1000249",
CompanyCode = "200000040"
};
var request = new GetContractNoticesRequestMessage
{
Info = info,
DateFrom = dateFrom,
DateTo = dateTo,
PageFrom = pageFrom,
PageTo = pageTo
};
result = serviceProxy.GetContractNoticesAsync(request).ConfigureAwait(false).GetAwaiter().GetResult();
return Ok(result);
}
If I put a breakpoint inside the inspector at BeforeSendRequest I can see that the security header is added to the request:
<wsse:Security s:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-32" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>sampleUsername</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">samplePassword123</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
And putting a breakpoint inside the inspector at AfterReceiveReply, I get the CORRECT result, but I still get an exception.
The result:
<...>
<s:Header>
<...>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>2019-01-11T19:42:53.606Z</u:Created>
<u:Expires>2019-01-11T19:47:53.606Z</u:Expires>
</u:Timestamp>
</o:Security>
</s:Header>
<s:Body>
<GetContractNoticesResponseMessage>
<ContractNotices>....</ContractNotices>
</GetContractNoticesResponseMessage>
</s:Body>
The exception:
An unhandled exception occurred while processing the request.
ProtocolException: The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood by the recipient of this message, causing the message to not be processed. This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process. Please ensure that the configuration of the client's binding is consistent with the service's binding.
Why do I still get an exception after calling the webservice successfully?

For .net core 2.2 you need to pass Security header manually. You'll need to-do some workarounds - WCF isn't fully implemented yet in .Net Core (has been stated by project contributors). Assuming the requirements aren't too complex, you should be able to get something going without too much headache.
public class SecurityHeader : MessageHeader
{
public UsernameToken UsernameToken { get; set; }
public override string Name => "Security";
public override string Namespace => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
public override bool MustUnderstand => true;
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializer serializer = new XmlSerializer(typeof(UsernameToken));
serializer.Serialize(writer, this.UsernameToken);
}
}
[XmlRoot(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
{
[XmlAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
public string Id { get; set; }
[XmlElement]
public string Username { get; set; }
}
Add below code in BeforeSendRequest method
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var soapSecurityHeader = new SecurityHeader()
{
UsernameToken = new UsernameToken()
{
Username = "User Name"
}
};
request.Headers.Add(soapSecurityHeader);
}

I did some digging and in the AfterReceiveReply you could do this:
public void AfterReceiveReply(ref Message reply, object correlationState)
{
var security = reply.Headers.Where(w => w.Namespace == "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd").First();
reply.Headers.UnderstoodHeaders.Add(security);
}
I suppose that in this step you could also check the value of the timestamp, if DateTime.UtcNow is in range and act upon that...?

Related

Add security header to a SOAP message

I'm not really proficient in soap calls.
I should make the following soap call.
I suffered with it for several days, but it doesn't work.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v1="http://eeszt.gov.hu/ns/helloworld/ws/HelloWorldService/v1">
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasisopen.org/wss/2004/01/oasis- 200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurityutility-1.0.xsd">
<wsu:Timestamp wsu:Id="TS-B05DFFAAB2CD6C6C9E14363584263357">
<wsu:Created>2015-07-08T12:27:06.335Z</wsu:Created>
<wsu:Expires>2015-07-08T12:32:06.335Z</wsu:Expires>
</wsu:Timestamp>
<saml:Assertion ID="id-" ...</saml:Assertion>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<v1:helloWorldRequest>
...
</v1:helloWorldRequest>
</soapenv:Body>
</soapenv:Envelope
That's what I tried
Create custom binding and webservice
CustomBinding wsBinding = new CustomBinding();
string baseAddress = "https://dev-if.eeszt.gov.hu:443/TOR";
EndpointAddress wsEndPointAddress = new EndpointAddress(new Uri(baseAddress));
wsBinding = CreateBindingForTp();
TorWSClient twsClient = new TorWSClient(wsBinding, wsEndPointAddress);
X509Certificate2 x509 = new X509Certificate2(#eesztCert.certFileName, #eesztCert.certPassword, X509KeyStorageFlags.MachineKeySet);
byte[] rawData = ReadFile(#eesztCert.certFileName);
x509.Import(rawData, #eesztCert.certPassword, X509KeyStorageFlags.MachineKeySet);
twsClient.ClientCredentials.ClientCertificate.Certificate = x509;
twsClient.Open();
Add security header and call webservice
SoapSecurityHeader soapSecurityHeader = new SoapSecurityHeader(Saml.samlTicket);
using (OperationContextScope scope = new OperationContextScope((IContextChannel)twsClient.InnerChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(soapSecurityHeader);
}
try
{
gtrspt = twsClient.getTorzs(gtrt);
}
catch (Exception ex)
{
MessageBoxEx.Show(ex.Message, "Error !", MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
}
Create security header
public class SoapSecurityHeader : MessageHeader
{
private readonly string _samlticket;
private readonly DateTime _createdDate;
public SoapSecurityHeader(string samlTicket)
{
_samlticket = samlTicket;
_createdDate = DateTime.Now;
}
public string Id { get; set; }
public override string Name
{
get { return "Security "; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsse", Name, Namespace);
writer.WriteXmlnsAttribute("wsu", "wsu http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsu", "Timestamp", null);
writer.WriteAttributeString("wsu", "Id", null, "TimeId1");
writer.WriteElementString("wsu", "Created", null, _createdDate.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"));
writer.WriteElementString("wsu", "Expires", null, _createdDate.AddSeconds(300).ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"));
writer.WriteEndElement();
string sml = Saml.samlTicket;
writer.WriteRaw("<saml:Assertion ID=" + #"""");
writer.WriteRaw(sml + #"""");
writer.WriteRaw("</saml:Assertion>");
}
}
}
Binding
public CustomBinding CreateBindingForTp()
{
CustomBinding customBinding = new CustomBinding();
TransportSecurityBindingElement security = new TransportSecurityBindingElement()
{
IncludeTimestamp = true,
MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10
};
TextMessageEncodingBindingElement encoding = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
HttpsTransportBindingElement transport = new HttpsTransportBindingElement() { TransferMode = TransferMode.Buffered, RequireClientCertificate = true, MaxReceivedMessageSize = Int32.MaxValue };
customBinding.Elements.Clear();
customBinding.Elements.Add(security);
customBinding.Elements.Add(encoding);
customBinding.Elements.Add(transport);
return customBinding;
}
I generated the proxy class from wsdl. Only one wsse header should be added.
The wsse header is not included in the webservice call.
I want a simpler solution.
Sorry for my bad English.
Thanks for the help.

Soapheader cannot be serialized

well I am trying to develop a soap client, it wanted its custom soapheader(i.e.usercred) to serialized, but after doing so I get this as error
System.Runtime.Serialization.InvalidDataContractException: 'Type 'ConsoleApp1.Program+usercred' cannot inherit from a type that is not marked with DataContractAttribute or SerializableAttribute. Consider marking the base type System.Web.Services.Protocols.SoapHeader with DataContractAttribute or SerializableAttribute, or removing them from the derived type.'
it kinda wants soapheader to be also serialized plz help
There are several ways to add custom soup header in your soup request.
For example you can create soup request using HTTPRequest where you can build soup envelope as you want. Client to send SOAP request and receive response
public override string Process(string UserName,string Password)
{
string uri = "https://serviceURL";
HttpWebRequest req = (HttpWebRequest)WebRequest.CreateDefault(new Uri(uri));
req.ContentType = "application/soap+xml; charset=utf-8";
req.Method = "POST";
string soapRequest = BuildSoapRequest(UserName,Password);
StreamWriter stm = new StreamWriter(req.GetRequestStream(), Encoding.UTF8);
stm.Write(soapRequest);
stm.Close();
try
{
HttpWebResponse wr = (HttpWebResponse)req.GetResponse();
StreamReader srd = new StreamReader(wr.GetResponseStream());
string response = srd.ReadToEnd();
return ExtractResponse(response);
}
catch (WebException e)
{
string error = "";
HttpWebResponse errRsp = (HttpWebResponse)e.Response;
if (errRsp != null)
{
using (StreamReader rdr = new StreamReader(errRsp.GetResponseStream()))
{
string line;
while ((line = rdr.ReadLine()) != null)
{
error += line;
}
}
}
throw new Exception(e.Message + "<br/> " + error);
}
catch (Exception e)
{
throw e;
}
}
private string BuildSoapRequest(string UserName,string Password)
{
StringBuilder soapRequest = new StringBuilder();
soapRequest.Append("<soap:Envelope xmlns:cor=\"http://www.caqh.org/SOAP/WSDL/CORERule2.2.0.xsd\" xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:tem=\"http://tempuri.org/\">");
soapRequest.Append("<soap:Header>");
soapRequest.Append("<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">");
soapRequest.Append("<wsse:UsernameToken>");
soapRequest.Append("<wsse:Username>" + UserName + "</wsse:Username>");
soapRequest.Append("<wsse:Password>" + Password + "</wsse:Password>");
soapRequest.Append("</wsse:UsernameToken>");
soapRequest.Append("</wsse:Security>");
soapRequest.Append("</soap:Header>");
soapRequest.Append("<soap:Body>");
soapRequest.Append("</soap:Body>");
soapRequest.Append("</soap:Envelope>");
return soapRequest.ToString();
}
private static string ExtractResponse(string soap)
{
}
If you are consuming WCF service then you can add custom behavior in your request.
Custom Endpoint Behavior not being used in WCF Client with Service Reference
public class CustomClientBehavior : IEndpointBehavior
{
string _username;
string _password;
public CustomClientBehavior(string username, string password)
{
_username = username;
_password = password;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
CustomInspector inspector = new CustomInspector(_username, _password);
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
public class CustomClientBehaviorExtensionElement : BehaviorExtensionElement
{
string _username;
string _password;
public CustomClientBehaviorExtensionElement(string username, string password)
{
_username = username;
_password = password;
}
public override Type BehaviorType
{
get { return typeof(CustomClientBehavior); }
}
protected override object CreateBehavior()
{
return new CustomClientBehavior(_username, _password);
}
}
public class CustomInspector : IClientMessageInspector
{
string _username;
string _password;
public CustomInspector(string username, string password)
{
_username = username;
_password = password;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
return;
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Clear();
string headerText = "<wsse:UsernameToken xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">" +
"<wsse:Username>{0}</wsse:Username>" +
"<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
"{1}</wsse:Password>" +
"</wsse:UsernameToken>";
headerText = string.Format(headerText, _username, _password);
XmlDocument MyDoc = new XmlDocument();
MyDoc.LoadXml(headerText);
XmlElement myElement = MyDoc.DocumentElement;
System.ServiceModel.Channels.MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", myElement, false);
request.Headers.Add(myHeader);
return Convert.DBNull;
}
}
Test Client should be like
TestService.Client objClient = new TestService.Client();
objClient.Endpoint.Behaviors.Add(new CustomClientBehavior(UserName, Password));
You can also try WebService Headers Authentication

Getting results via WSDL - C#

I'm trying to add this Header class to my SOAP request but can't see how. The Header class was given to me as part of the implementation but there's no instructions on how to use it and I'm a bit stuck - I've not used WSDL and web services before. I'm sure the answer must be blindingly easy but I just can't see it.
Header Requirements
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-19" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>##USERNAME##</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">##PASSWORD##</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
Header class
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
namespace Consuming
{
public class SecurityHeader : MessageHeader
{
private readonly UsernameToken _usernameToken;
public SecurityHeader(string id, string username, string password)
{
_usernameToken = new UsernameToken(id, username, password);
}
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializer serializer = new XmlSerializer(typeof(UsernameToken));
serializer.Serialize(writer, _usernameToken);
}
}
[XmlRoot(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
{
public UsernameToken()
{
}
public UsernameToken(string id, string username, string password)
{
Id = id;
Username = username;
Password = new Password() { Value = password };
}
[XmlAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
public string Id { get; set; }
[XmlElement]
public string Username { get; set; }
[XmlElement]
public Password Password { get; set; }
}
public class Password
{
public Password()
{
Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
}
[XmlAttribute]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
}
Code
var mySoapHeader = new SecurityHeader("ID","Username","password");
var client = new GroupWebServiceClient(); // Created from Add Web Reference
client.?????? = mySoapHeader;// I can't see how to add the Header to the request
var response = new groupNameListV1();
response = client.getAllDescendants("6335");//This needs the header - omitting gives "An error was discovered processing the <wsse:Security> header"
EDIT
I figured it out in the end, turns out it was pretty easy - Adding the solution in case anyone else finds it useful
using (new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(
new SecurityHeader("ID", "USER", "PWD"));
var response = new groupNameListV1();
response = client.getAllDescendants("cng_so_6553");
//other code
}
Generally you need to add behavior extension.
Create a class that implements IClientMessageInspector. In the BeforeSendRequest method, add your custom header to the outgoing message. It might look something like this:
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
{
httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
Then create an endpoint behavior that applies the message inspector to the client runtime. You can apply the behavior via an attribute or via configuration using a behavior extension element.
Here is a example of how to add an HTTP user-agent header to all request messages. I used this in a few of my clients.
Is this what you had in mind?

Unable to use PLAINTEXT signature with a DotNetOpenAuth ServiceProvider

I am building an OAuth 1.0(a) authorization server using DotNetOpenAuth (NuGet package DotNetOpenAuth.OAuth.ServiceProvider, version = 4.1.4.12333). The server is hosted in an ASP.NET application but that's irrelevant to the question.
My ServiceProvider is configured like this:
private ServiceProvider GetServiceProvider()
{
var baseUri = "http://myauth.com";
return new ServiceProvider(
new ServiceProviderDescription
{
UserAuthorizationEndpoint = new MessageReceivingEndpoint(
new Uri(baseUri + "/get_request_token"),
HttpDeliveryMethods.GetRequest
),
RequestTokenEndpoint = new MessageReceivingEndpoint(
new Uri(baseUri + "/authorize"),
HttpDeliveryMethods.PostRequest
),
AccessTokenEndpoint = new MessageReceivingEndpoint(
new Uri(baseUri + "/get_token"),
HttpDeliveryMethods.PostRequest
),
ProtocolVersion = ProtocolVersion.V10a,
TamperProtectionElements = new ITamperProtectionChannelBindingElement[]
{
new PlaintextSigningBindingElement(),
new HmacSha1SigningBindingElement(),
},
},
tokenManager,
new OAuthServiceProviderMessageFactory(tokenManager)
);
}
The relevant part of my get_request_token endpoint looks like this:
var serviceProvider = GetServiceProvider();
var tokenRequest = serviceProvider.ReadTokenRequest();
Now when a consumer sends the following request to this endpoint:
GET /get_request_token?oauth_nonce=C5657420BCE5F3224914304376B5334696B09B7FFC17C105A7F9629A008869DC&oauth_timestamp=1356006599&oauth_consumer_key=sampleconsumer&oauth_signature_method=plaintext&oauth_signature=samplesecret%26&oauth_version=1.0&oauth_callback=http%3a%2f%2flocalhost%3a30103%2fCustomOAuth1 HTTP/1.1
Host: localhost:8180
Connection: close
(broken for clarity):
oauth_nonce=C5657420BCE5F3224914304376B5334696B09B7FFC17C105A7F9629A008869DC
oauth_timestamp=1356006599
oauth_consumer_key=sampleconsumer
oauth_signature_method=plaintext
oauth_signature=samplesecret%26
oauth_version=1.0
oauth_callback=http%3a%2f%2flocalhost%3a30103%2fCustomOAuth1
The serviceProvider.ReadTokenRequest() method throws an exception:
The UnauthorizedTokenRequest message required protections {All} but the channel could only apply {Expiration, ReplayProtection}.
at DotNetOpenAuth.Messaging.Channel.ProcessIncomingMessage(IProtocolMessage message)
at DotNetOpenAuth.Messaging.Channel.ReadFromRequest(HttpRequestBase httpRequest)
at DotNetOpenAuth.Messaging.Channel.TryReadFromRequest[TRequest](HttpRequestBase httpRequest, TRequest& request)
at DotNetOpenAuth.OAuth.ServiceProvider.ReadTokenRequest(HttpRequestBase request)
at DotNetOpenAuth.OAuth.ServiceProvider.ReadTokenRequest()
at OAuthServers.OAuth1.Services.OAuth1Service.Any(GetRequestTokenRequest request)
at lambda_method(Closure , Object , Object )
at ServiceStack.ServiceHost.ServiceRunner`1.Execute(IRequestContext requestContext, Object instance, TRequest request)
On the other hand if the client sends the following request:
GET /get_request_token?oauth_callback=http%3a%2f%2flocalhost%3a65271%2foauth1%2fHandleAccessToken&oauth_consumer_key=sampleconsumer&oauth_nonce=rGFvxlWm&oauth_signature_method=HMAC-SHA1&oauth_signature=HV%2f5Vq%2b0cF3NrtiISE9k4jmgCrY%3d&oauth_version=1.0&oauth_timestamp=1356007830 HTTP/1.1
Host: localhost:8180
Connection: close
(broken for clarity):
oauth_callback=http%3a%2f%2flocalhost%3a65271%2foauth1%2fHandleAccessToken
oauth_consumer_key=sampleconsumer
oauth_nonce=rGFvxlWm
oauth_signature_method=HMAC-SHA1
oauth_signature=HV%2f5Vq%2b0cF3NrtiISE9k4jmgCrY%3d
oauth_version=1.0
oauth_timestamp=1356007830
it succeeds.
As you can see the only difference between those 2 requests is the oauth_signature_method being used. In the first case PLAINTEXT is used whereas in the second HMAC-SHA1.
Is it possible to make DotNetOpenAuth accept a PLAINTEXT signature method for the request token endpoint along with the GET verb (even if the OAuth 1.0(a) specification recommends POST to be used for this endpoint)? Is there some config option that could relax this requirement on the server?
At the moment modifying the client is not an option for me.
OAuth Authentication is done in three steps:
The Consumer obtains an unauthorized Request Token.
The User authorizes the Request Token.
The Consumer exchanges the Request Token for an Access Token.
So here's what that would look like:
public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager
{
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
public InMemoryTokenManager(string consumerKey, string consumerSecret)
{
if (String.IsNullOrEmpty(consumerKey))
{
throw new ArgumentNullException("consumerKey");
}
this.ConsumerKey = consumerKey;
this.ConsumerSecret = consumerSecret;
}
public string ConsumerKey { get; private set; }
public string ConsumerSecret { get; private set; }
#region ITokenManager Members
public string GetConsumerSecret(string consumerKey)
{
if (consumerKey == this.ConsumerKey)
{
return this.ConsumerSecret;
}
else
{
throw new ArgumentException("Unrecognized consumer key.", "consumerKey");
}
}
public string GetTokenSecret(string token)
{
return this.tokensAndSecrets[token];
}
public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
this.tokensAndSecrets[response.Token] = response.TokenSecret;
}
public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
{
this.tokensAndSecrets.Remove(requestToken);
this.tokensAndSecrets[accessToken] = accessTokenSecret;
}
/// <summary>
/// Classifies a token as a request token or an access token.
/// </summary>
/// <param name="token">The token to classify.</param>
/// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
public TokenType GetTokenType(string token)
{
throw new NotImplementedException();
}
#endregion
#region IOpenIdOAuthTokenManager Members
public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization)
{
this.tokensAndSecrets[authorization.RequestToken] = string.Empty;
}
#endregion
}
Following block of code may help you to generate plain text signature
public static string GetSignature(OAuthSignatureMethod signatureMethod, AuthSignatureTreatment signatureTreatment, string signatureBase, string consumerSecret, string tokenSecret)
{
if (tokenSecret.IsNullOrBlank())
{
tokenSecret = String.Empty;
}
consumerSecret = UrlEncodeRelaxed(consumerSecret);
tokenSecret = UrlEncodeRelaxed(tokenSecret);
string signature;
switch (signatureMethod)
{
case OAuthSignatureMethod.HmacSha1:
{
var crypto = new HMACSHA1();
var key = "{0}&{1}".FormatWith(consumerSecret, tokenSecret);
crypto.Key = _encoding.GetBytes(key);
signature = signatureBase.HashWith(crypto);
break;
}
case OAuthSignatureMethod.PlainText:
{
signature = "{0}&{1}".FormatWith(consumerSecret, tokenSecret);
break;
}
default:
throw new NotImplementedException("Only HMAC-SHA1 is currently supported.");
}
var result = signatureTreatment == OAuthSignatureTreatment.Escaped
? UrlEncodeRelaxed(signature)
: signature;
return result;
}

How do I restrict access to some methods in WCF?

I am a bit lost getting started with a simple WCF service. I have two methods and I want to expose one to the world and the second one I want to limit to certain users. Eventually I want to be able to use a client application to use the restricted method. So far I can access both methods anonymously:
C# Code
namespace serviceSpace
{
[ServiceContract]
interface ILocationService
{
[OperationContract]
string GetLocation(string id);
[OperationContract]
string GetHiddenLocation(string id);
}
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class LocationService : ILocationService
{
[WebGet(UriTemplate = "Location/{id}")]
public string GetLocation(string id)
{
return "O hai, I'm available to everyone.";
}
// only use this if authorized somehow
[WebGet(UriTemplate = "Location/hush/{id}")]
public string GetHiddenLocation(string id)
{
return "O hai, I can only be seen by certain users.";
}
}
}
Configuration
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true"
automaticFormatSelectionEnabled="true"/>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>
How do I get started?
A lot of the answers I found were almost what I needed but not quite right. I wound up setting up ASP.net membership and implementing a custom attribute to pull an authorization header and process login as the request came in. All of the magic happens in BeforeCall and ParseAuthorizationHeader below:
public class UsernamePasswordAuthentication : Attribute, IOperationBehavior, IParameterInspector
{
public void ApplyDispatchBehavior(OperationDescription operationDescription,
DispatchOperation dispatchOperation)
{
dispatchOperation.ParameterInspectors.Add(this);
}
public void AfterCall(string operationName, object[] outputs,
object returnValue, object correlationState)
{
}
public object BeforeCall(string operationName, object[] inputs)
{
var usernamePasswordString = parseAuthorizationHeader(WebOperationContext.Current.IncomingRequest);
if (usernamePasswordString != null)
{
string[] usernamePasswordArray = usernamePasswordString.Split(new char[] { ':' });
string username = usernamePasswordArray[0];
string password = usernamePasswordArray[1];
if ((username != null) && (password != null) && (Membership.ValidateUser(username, password)))
{
Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(username), new string[0]);
return null;
}
}
// if we made it here the user is not authorized
WebOperationContext.Current.OutgoingResponse.StatusCode =
HttpStatusCode.Unauthorized;
throw new WebFaultException<string>("Unauthorized", HttpStatusCode.Unauthorized);
}
private string parseAuthorizationHeader(IncomingWebRequestContext request)
{
string rtnString = null;
string authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
var authStr = authHeader.Trim();
if (authStr.IndexOf("Basic", 0) == 0)
{
string encodedCredentials = authStr.Substring(6);
byte[] decodedBytes = Convert.FromBase64String(encodedCredentials);
rtnString = new ASCIIEncoding().GetString(decodedBytes);
}
}
return rtnString;
}
public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void Validate(OperationDescription operationDescription)
{
}
}
From there I just need to add my new attribute to the service contract entries. Any request to that method will require a valid authorization header or a Not Authorized response will be sent back with doing any further processing.
[ServiceContract]
interface ILocationService
{
[OperationContract]
string GetLocation(string id);
[OperationContract]
[UsernamePasswordAuthentication] // this attribute will force authentication
string GetHiddenLocation(string id);
}
Use the following steps to restrict access to specific Windows users:
Open the Computer Management Windows applet.
Create a Windows group that contains the specific Windows users to which you wish to give access. For example, a group can be called “CalculatorClients”.
Configure your service to require ClientCredentialType = “Windows”. This will require clients to connect using Windows authentication.
Configure your service methods with the PrincipalPermission attribute to require connecting users be members of the CalculatorClients group.
// Only members of the CalculatorClients group can call this method.
[PrincipalPermission(SecurityAction.Demand, Role = "CalculatorClients")]
public double Add(double a, double b)
{
return a + b;
}

Categories

Resources