Obtaining a value within a SOAP Header from the OperationContext - c#

I have the following code in C# that looks for an apiKey in the the following SOAP header:
SOAP Header:
<soap:Header>
<Authentication>
<apiKey>CCE4FB48-865D-4DCF-A091-6D4511F03B87</apiKey>
</Authentication>
</soap:Header>
C#:
This is what I have so far:
public string GetAPIKey(OperationContext operationContext)
{
string apiKey = null;
// Look at headers on incoming message.
for (int i = 0; i < OperationContext.Current.IncomingMessageHeaders.Count; i++)
{
MessageHeaderInfo h = OperationContext.Current.IncomingMessageHeaders[i];
// For any reference parameters with the correct name.
if (h.Name == "apiKey")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
apiKey = xr.ReadElementContentAsString();
}
}
// Return the API key (if present, null if not).
return apiKey;
}
PROBLEM: Returning null instead of the actual apiKey value:
CCE4FB48-865D-4DCF-A091-6D4511F03B87
UPDATE 1:
I added some logging. It looks like h.Name is in fact "Authentication", which means it won't actually be looking for "apiKey", which then means it won't be able to retrieve the value.
Is there a way to grab the <apiKey /> inside of <Authentication />?
UPDATE 2:
Ended up using the following code:
if (h.Name == "Authentication")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
xr.ReadToDescendant("apiKey");
apiKey = xr.ReadElementContentAsString();
}

I think your h.Name is Authentication because it is root type and apiKey is property of Authentication type. Try logging values of h.Name to some log file and check what does it return.
if (h.Name == "Authentication")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
//apiKey = xr.ReadElementContentAsString();
xr.ReadToFollowing("Authentication");
apiKey = xr.ReadElementContentAsString();
}

Ended up using the following code:
if (h.Name == "Authentication")
{
// Read the value of that header.
XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);
xr.ReadToDescendant("apiKey");
apiKey = xr.ReadElementContentAsString();
}

There is a shorter solution:
public string GetAPIKey(OperationContext operationContext)
{
string apiKey = null;
MessageHeaders headers = OperationContext.Current.RequestContext.RequestMessage.Headers;
// Look at headers on incoming message.
if (headers.FindHeader("apiKey","") > -1)
apiKey = headers.GetHeader<string>(headers.FindHeader("apiKey",""));
// Return the API key (if present, null if not).
return apiKey;
}

Related

Authentication for BasicHttpBinding using Request Hearder

I have a BasicHttpBinding WCF service. I want to get user name and password in request header. I searched in in the internet for this but I see just WSHttpBinding. I want to have something like this:
//WCF client call
WCFTestService.ServiceClient myService = new
WCFTestService.ServiceClient();
myService.ClientCredentials.UserName.UserName = "username";
myService.ClientCredentials.UserName.Password = "p#ssw0rd";
MessageBox.Show(myService.GetData(123));
myService.Close();
but I don't know what should I write for server side?
Thanks
You could create a custom Authorization Class by inheriting the ServiceAuthorizationManager class and pull out the credentials from the request header.
Your code could be similar to the following:
public class CustomAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
//Extract the Authorization header, and parse out the credentials converting the Base64 string:
var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if ((authHeader != null) && (authHeader != string.Empty))
{
var svcCredentials = System.Text.Encoding.ASCII
.GetString(Convert.FromBase64String(authHeader.Substring(6)))
.Split(':');
var user = new
{
Name = svcCredentials[0],
Password = svcCredentials[1]
};
if ((user.Name == "username" && user.Password == "p#ssw0rd"))
{
//User is authorized and originating call will proceed
return true;
}
else
{
//not authorized
return false;
}
}
else
{
//No authorization header was provided, so challenge the client to provide before proceeding:
WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"YourNameSpace\"");
//Throw an exception with the associated HTTP status code equivalent to HTTP status 401
throw new WebFaultException(HttpStatusCode.Unauthorized);
}
}
}
In addition to that, you need to set the serviceAuthorizationManagerType attribute of the serviceAuthorization element to your custom class in the web.config file.
Something similar to this:
<serviceAuthorization serviceAuthorizationManagerType="YourNameSpace.CustomAuthorizationManager, YourAssemblyName"/>
In the client side, you also need to add the credentials to the request headers.
HttpRequestMessageProperty httpReqProp = new HttpRequestMessageProperty();
httpReqProp.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("username"+ ":" + "p#ssw0rd"));
Security note:
Keep in mind that in Basic Authentication, the username and password will be sent as non-encrypted text in the request header. You should only implement this with SSL.

Overriding the default User-Agent header for Exchange request

I am sending a request to EWS as below:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new NetworkCredential(username, password),
UserAgent = "myClient"
};
var subscription = service.SubscribeToPushNotifications(
new[] { inboxFolderFoldeID },
new Uri("some post back url"),
15,
null,
EventType.NewMail,
EventType.Created,
EventType.Deleted,
EventType.Modified,
EventType.Moved,
EventType.Copied);
But, it would result into a request having the User-Agent header as myClient (ExchangeServicesClient/15.00.0913.015) where the rest of the string is coming from the EWS library where it is using this default value. Is there a way to remove the default part of the header and just have it as myClient?
Edit: I can see that EWS library seems to be simply prefixing the value passed in the request: https://github.com/OfficeDev/ews-managed-api/blob/master/Core/ExchangeServiceBase.cs
You will need to recompile the library from GitHub as the scope of the existing variables won't allow you to change them any other way. eg all you need to do is modify UserAgent
public string UserAgent
{
get { return this.userAgent; }
set { this.userAgent = value + " (" + ExchangeService.defaultUserAgent + ")"; }
}
and get rid of the prefix then when you set the property on the ExchangeService class it will only be your custom value.

How can I retrieve Basic Authentication credentials from the header?

I am trying to write some simple tests User Authentication mechanism which uses Basic Authentication. How can I retrieve the credentials from the header?
string authorizationHeader = this.HttpContext.Request.Headers["Authorization"];
Where do I go from here? There are several tutorials but I new to .NET and authentication, could you explain in your answer exactly step-by-step the what and why you are doing.
From my blog:
This will explain in detail how this all works:
Step 1 - Understanding Basic Authentication
Whenever you use Basic Authentication a header is added to HTTP Request and it will look similar to this:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Source: http://en.wikipedia.org/wiki/Basic_access_authentication
"QWxhZGRpbjpvcGVuIHNlc2FtZQ==" is just "username:password" encoded in Base64(http://en.wikipedia.org/wiki/Base64). In order to access headers and other HTTP properties in .NET (C#) you need to have access to the current Http Context:
HttpContext httpContext = HttpContext.Current;
This you can find in System.Web namespace.
Step 2 - Getting the Header
Authorization header isn't the only only one in the HttpContext. In order to access the header, we need to get it from the request.
string authHeader = this.httpContext.Request.Headers["Authorization"];
(Alternatively you may use AuthenticationHeaderValue.TryParse as suggested in pasx’s answer below)
If you debug your code you will see that the content of that header looks similar to this:
Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Step 3 - Checking the header
You've already extracted the header now there are several things you need to do:
Check that the header isn't null
Check that the Authorization/Authentication mechanism is indeed "Basic"
Like so:
if (authHeader != null && authHeader.StartsWith("Basic")) {
//Extract credentials
} else {
//Handle what happens if that isn't the case
throw new Exception("The authorization header is either empty or isn't Basic.");
}
Now you have check that you are have something to extract data from.
Step 4 - Extracting credentials
Removing "Basic " Substring
You can now attempt to get the values for username and password. Firstly you need to get rid of the "Basic " substring. You can do it like so:
string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
See the following links for further details:
http://msdn.microsoft.com/en-us/library/system.string.substring(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/t97s7bs3(v=vs.110).aspx
Decoding Base64
Now we need to decode back from Base64 to string:
//the coding should be iso or you could use ASCII and UTF-8 decoder
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
Now username and password will be in this format:
username:password
Splitting Username:Password
In order to get username and password we can simply get the index of the ":"
int seperatorIndex = usernamePassword.IndexOf(':');
username = usernamePassword.Substring(0, seperatorIndex);
password = usernamePassword.Substring(seperatorIndex + 1);
Now you can use these data for testing.
The Final Code
The final code may look like this:
HttpContext httpContext = HttpContext.Current;
string authHeader = this.httpContext.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic")) {
string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
int seperatorIndex = usernamePassword.IndexOf(':');
var username = usernamePassword.Substring(0, seperatorIndex);
var password = usernamePassword.Substring(seperatorIndex + 1);
} else {
//Handle what happens if that isn't the case
throw new Exception("The authorization header is either empty or isn't Basic.");
}
Just adding to the main answer, the best way to get rid of the "Basic" substring is to use AuthenticationHeaderValue Class:
var header = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentials = header.Parameter;
It will throw a FormatException if the content of the header is not valid, e.g.: the "Basic" part is not present.
Alternatively if you do not want to have exception, use AuthenticationHeaderValue.TryParse
Awesome answer from #DawidO.
If you are just looking to extract the basic auth creds and rely on the .NET magic given you have HttpContext, this will also work:
public static void StartListener() {
using (var hl = new HttpListener()) {
hl.Prefixes.Add("http://+:8008/");
hl.AuthenticationSchemes = AuthenticationSchemes.Basic;
hl.Start();
Console.WriteLine("Listening...");
while (true) {
var hlc = hl.GetContext();
var hlbi = (HttpListenerBasicIdentity)hlc.User.Identity;
Console.WriteLine(hlbi.Name);
Console.WriteLine(hlbi.Password);
//TODO: validater user
//TODO: take action
}
}
}
Remember, using strings can be less secure. They will remain in memory untill they are picked by GC.

Adding and Retrieving data from request context

I'm trying to attach an api key to the OperationContext outgoing message header as follows:
public static void AddApikeyToHeader(string apikey, IContextChannel channel, string address)
{
using (OperationContextScope scope = new OperationContextScope(channel))
{
MessageHeader header = MessageHeader.CreateHeader("apikey", address, apikey);
OperationContext.Current.OutgoingMessageHeaders.Add(header);
}
}
but then I have no idea how to retrieve the header on the server side. I'm using a Service authorisation manager and I get the current operating context and try to retrieve the header like this:
public string GetApiKey(OperationContext operationContext)
{
var request = operationContext.RequestContext.RequestMessage;
var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
return prop.Headers["apikey"];
}
but there is no apikey header attached there. Also, on debugging when I inspect the operationContext I cant seem to see my apikey header anywhere. Can anyone see where I'm going wrong?
You can add custom header by this way :
using (ChannelFactory<IMyServiceChannel> factory =
new ChannelFactory<IMyServiceChannel>(new NetTcpBinding()))
{
using (IMyServiceChannel proxy = factory.CreateChannel(...))
{
using ( OperationContextScope scope = new OperationContextScope(proxy) )
{
Guid apiKey = Guid.NewGuid();
MessageHeader<Guid> mhg = new MessageHeader<Guid>(apiKey);
MessageHeader untyped = mhg.GetUntypedHeader("apiKey", "ns");
OperationContext.Current.OutgoingMessageHeaders.Add(untyped);
proxy.DoOperation(...);
}
}
}
And service side, you can get header like :
Guid apiKey =
OperationContext.Current.IncomingMessageHeaders.GetHeader<Guid>("apiKey", "ns");
I'm assuming that you trying to consume your service using some Http Protocol based transport (SOAP, REST etc). I'm also assuming that what you want is to authorize the caller using the supplied API key. If both of those conditions apply to your question, you can read on.
I recently had to tackle a similar problem only that I did not pass an API key but a username/password hash combination using some HTTP custom headers. I ultimately solved it by implementing a custom authorization policy that once configured in Web.config hooked nicely into the WCF Pipeline.
The snippet below should be enough to get you started. You probably would have to replace the x-ms-credentials-XXX headers by a single one representing your API key.
internal class RESTAuthorizationPolicy : IAuthorizationPolicy
{
public RESTAuthorizationPolicy()
{
Id = Guid.NewGuid().ToString();
Issuer = ClaimSet.System;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
const String HttpRequestKey = "httpRequest";
const String UsernameHeaderKey = "x-ms-credentials-username";
const String PasswordHeaderKey = "x-ms-credentials-password";
const String IdentitiesKey = "Identities";
const String PrincipalKey = "Principal";
// Check if the properties of the context has the identities list
if (evaluationContext.Properties.Count > 0 ||
evaluationContext.Properties.ContainsKey(IdentitiesKey) ||
!OperationContext.Current.IncomingMessageProperties.ContainsKey(HttpRequestKey))
return false;
// get http request
var httpRequest = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestKey];
// extract credentials
var username = httpRequest.Headers[UsernameHeaderKey];
var password = httpRequest.Headers[PasswordHeaderKey];
// verify credentials complete
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
// Get or create the identities list
if (!evaluationContext.Properties.ContainsKey(IdentitiesKey))
evaluationContext.Properties[IdentitiesKey] = new List<IIdentity>();
var identities = (List<IIdentity>) evaluationContext.Properties[IdentitiesKey];
// lookup user
using (var con = ServiceLocator.Current.GetInstance<IDbConnection>())
{
using (var userDao = ServiceLocator.Current.GetDao<IUserDao>(con))
{
var user = userDao.GetUserByUsernamePassword(username, password);
...
Did you take a look at this question: How to add a custom HTTP header to every WCF call? ? It may contain your solution.

How to sign custom Soap Header?

I've added a custom soap header <MyApp:FOO> element to the <soap:Header> element and the requirments states that i must sign this element , how would one do that?
<MyApp:FOO> contains a number of things (username, preferences, etc) that identifies a user on higher level.
I've succesfully used a policy file and now a policyClass with CertificateAssertions and SoapFilters to sign wsu:Timestamp, wsu:action, wsu:MessageId etc. But now the <MyApp:FOO> element needs to signed aswell.
What i've understood this far is that the element that needs to be signed must be indentified with a wsu:Id attribute and then transformed using xml-exc-c14n.
So, how do I specify that the soap header should be signed aswell?
This is the current class that i use for signing my message.
internal class FOOClientOutFilter: SendSecurityFilter
{
X509SecurityToken clientToken;
public FOOClientOutFilter(SSEKCertificateAssertion parentAssertion)
: base(parentAssertion.ServiceActor, true)
{
// Get the client security token.
clientToken = X509TokenProvider.CreateToken(StoreLocation.CurrentUser, StoreName.My, "CN=TestClientCert");
// Get the server security token.
serverToken = X509TokenProvider.CreateToken(StoreLocation.LocalMachine, StoreName.My, "CN=TestServerCert");
}
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
// Sign the SOAP message with the client's security token.
security.Tokens.Add(clientToken);
security.Elements.Add(new MessageSignature(clientToken));
}
}
My current version of SecureMessage seems to do the trick..
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
//EncryptedData data = new EncryptedData(userToken);
SignatureReference ssekSignature = new SignatureReference();
MessageSignature signature = new MessageSignature(clientToken);
// encrypt custom headers
for (int index = 0; index < envelope.Header.ChildNodes.Count; index++)
{
XmlElement child =
envelope.Header.ChildNodes[index] as XmlElement;
// find all FOO headers
if (child != null && child.Name == "FOO")
{
string id = Guid.NewGuid().ToString();
child.SetAttribute("Id", "http://docs.oasis-" +
"open.org/wss/2004/01/oasis-200401-" +
"wss-wssecurity-utility-1.0.xsd", id);
signature.AddReference(new SignatureReference("#" + id));
}
}
// Sign the SOAP message with the client's security token.
security.Tokens.Add(clientToken);
security.Elements.Add(signature);
}
Including supplementary articles from MSDN
How to: Add an Id Attribute to a SOAP Header
How to: Digitally Sign a Custom SOAP Header

Categories