How to sign custom Soap Header? - c#

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

Related

After authentication directed to developer sandbox and not to embedded signing?

I am using a code example for Authorization Code Grant with DocuSign, and the authentication seems to work okay, but the code never reaches the actual creation of an envelope for signage. After authentication I get directed to the developer sandbox, am assuming this not correct because it should send me to Eg001EmbeddedSigning Controller. Am I missing something?
I have tried adding the integrator key and secret which I do have. I've tried navigating to Eg001EmbeddingSigning/Create from my localhost and it cannot find the page.
View I expect to occur after authentication:
#model DocuSign.Managers.ViewModels.SigningViewModel
<h4>DocuSign Embedded Signing Ceremony</h4>
<p>This example sends an envelope, and then uses an embedded signing ceremony for the first signer.</p>
<p>
Embedded signing provides a smoother user experience for the signer: the DocuSign signing ceremony is initiated from
your website.
</p>
#if (ViewBag.showDoc == true)
{
<p><a target='_blank' href='#ViewBag.documentation'>Documentation</a> about this example.</p>
}
<form class="eg" action="" method="post" data-busy="form">
<div class="form-group">
<label for="signerEmail">Signer Email</label>
<input type="email" class="form-control" id="signerEmail" name="signerEmail"
aria-describedby="emailHelp" placeholder="pat#example.com" required
value="#ViewBag.Locals.DsConfig.SignerEmail">
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label for="signerName">Signer Name</label>
<input type="text" class="form-control" id="signerName" placeholder="Pat Johnson" name="signerName"
value="#ViewBag.Locals.DsConfig.SignerName" required>
</div>
<input type="hidden" name="_csrf" value="#ViewBag.csrfToken">
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Index Page:
#{
var locals = ViewData["locals"] as Locals;//ViewBag.Locals;
bool showDoc;
Boolean.TryParse(ViewData["showDoc"].ToString(), out showDoc);//ViewBag.ShowDoc;
}
#if (locals.User == null)
{
<!-- IF not signed in -->
<div class="jumbotron">
<h1 class="display-4">Welcome!</h1>
<p class="Xlead">
DocuSign with
OAuth Authorization Code Grant.
</p>
</div>
}
<div id="index-page">
<h2>Welcome</h2>
<p>This launcher both demonstrates use of the OAuth Authorization Code Grant flow and includes multiple usage examples for the DocuSign eSignature REST API.</p>
#if (ViewBag.showDoc)
{
<p><a target='_blank' href='#ViewBag.documentation'>Documentation</a> on using OAuth Authorization Code Grant from a C# .NET Core MVC application.</p>
}
<h2>Basic Examples</h2>
<h4 id="example001">1. Embedded Signing Ceremony</h4>
<p>
This example sends an envelope, and then uses an embedded signing ceremony for the first signer.
With embedded signing, the DocuSign signing ceremony is initiated from your website.
</p>
DocuSign Controller:
namespace DocuSign.Controllers
{
[Route("ds/[action]")]
public class AccountController : Controller
{
[HttpGet]
public IActionResult Login(string returnUrl = "/")
{
return Challenge(new AuthenticationProperties() { RedirectUri = returnUrl });
}
public IActionResult MustAuthenticate()
{
return View();
}
public async System.Threading.Tasks.Task<IActionResult> logout()
{
await AuthenticationHttpContextExtensions.SignOutAsync(HttpContext);
return LocalRedirect("/");
}
}
Eg001EmbeddedSigningController:
namespace DocuSign.Views
{
[Route("eg001")]
public class Eg001EmbeddedSigningController : EgController
{
private string dsPingUrl;
private string signerClientId = "1000";
private string dsReturnUrl;
private readonly DocuSignManager _docuSignManager;
public Eg001EmbeddedSigningController(DSConfiguration config, IRequestItemsService requestItemsService, DocuSignManager docuSignManager)
: base(config, requestItemsService)
{
dsPingUrl = config.AppUrl + "/";
dsReturnUrl = config.AppUrl + "/dsReturn";
ViewBag.title = "Embedded Signing Ceremony";
_docuSignManager = docuSignManager;
}
// ***DS.snippet.0.start
private string DoWork(string signerEmail, string signerName,
string accessToken, string basePath, string accountId)
{
// Data for this method
// signerEmail
// signerName
// accessToken
// basePath
// accountId
// dsPingUrl -- class global
// signerClientId -- class global
// dsReturnUrl -- class global
// Step 1. Create the envelope definition
EnvelopeDefinition envelope = MakeEnvelope(signerEmail, signerName);
// Step 2. Call DocuSign to create the envelope
var config = new Configuration(new ApiClient(basePath));
config.AddDefaultHeader("Authorization", "Bearer " + accessToken);
EnvelopesApi envelopesApi = new EnvelopesApi(config);
EnvelopeSummary results = envelopesApi.CreateEnvelope(accountId, envelope);
string envelopeId = results.EnvelopeId;
// Save for future use within the example launcher
RequestItemsService.EnvelopeId = envelopeId;
// Step 3. create the recipient view, the Signing Ceremony
RecipientViewRequest viewRequest = MakeRecipientViewRequest(signerEmail, signerName);
// call the CreateRecipientView API
ViewUrl results1 = envelopesApi.CreateRecipientView(accountId, envelopeId, viewRequest);
// Step 4. Redirect the user to the Signing Ceremony
// Don't use an iFrame!
// State can be stored/recovered using the framework's session or a
// query parameter on the returnUrl (see the makeRecipientViewRequest method)
string redirectUrl = results1.Url;
return redirectUrl;
}
private RecipientViewRequest MakeRecipientViewRequest(string signerEmail, string signerName)
{
// Data for this method
// signerEmail
// signerName
// dsPingUrl -- class global
// signerClientId -- class global
// dsReturnUrl -- class global
RecipientViewRequest viewRequest = new RecipientViewRequest();
// Set the url where you want the recipient to go once they are done signing
// should typically be a callback route somewhere in your app.
// The query parameter is included as an example of how
// to save/recover state information during the redirect to
// the DocuSign signing ceremony. It's usually better to use
// the session mechanism of your web framework. Query parameters
// can be changed/spoofed very easily.
viewRequest.ReturnUrl = dsReturnUrl + "?state=123";
// How has your app authenticated the user? In addition to your app's
// authentication, you can include authenticate steps from DocuSign.
// Eg, SMS authentication
viewRequest.AuthenticationMethod = "none";
// Recipient information must match embedded recipient info
// we used to create the envelope.
viewRequest.Email = signerEmail;
viewRequest.UserName = signerName;
viewRequest.ClientUserId = signerClientId;
// DocuSign recommends that you redirect to DocuSign for the
// Signing Ceremony. There are multiple ways to save state.
// To maintain your application's session, use the pingUrl
// parameter. It causes the DocuSign Signing Ceremony web page
// (not the DocuSign server) to send pings via AJAX to your
// app,
viewRequest.PingFrequency = "600"; // seconds
// NOTE: The pings will only be sent if the pingUrl is an https address
viewRequest.PingUrl = dsPingUrl; // optional setting
return viewRequest;
}
private EnvelopeDefinition MakeEnvelope(string signerEmail, string signerName)
{
// Data for this method
// signerEmail
// signerName
// signerClientId -- class global
// Config.docPdf
//byte[] buffer = System.IO.File.ReadAllBytes(Config.docPdf);
byte[] buffer = System.IO.File.ReadAllBytes("C:\\Users\\username\\source\\repos\\DocuSign\\DocuSign\\Test 2.pdf");
EnvelopeDefinition envelopeDefinition = new EnvelopeDefinition();
envelopeDefinition.EmailSubject = "Please sign this document";
Document doc1 = new Document();
String doc1b64 = Convert.ToBase64String(buffer);
doc1.DocumentBase64 = doc1b64;
doc1.Name = "Lorem Ipsum"; // can be different from actual file name
doc1.FileExtension = "pdf";
doc1.DocumentId = "3";
// The order in the docs array determines the order in the envelope
envelopeDefinition.Documents = new List<Document> { doc1 };
var user = _docuSignManager.GetUserInfo();
// Create a signer recipient to sign the document, identified by name and email
// We set the clientUserId to enable embedded signing for the recipient
// We're setting the parameters via the object creation
Signer signer1 = new Signer {
Email = user.Email,
Name = user.UserName,
ClientUserId = signerClientId,
RecipientId = "1"
};
var position = _docuSignManager.GetPositionData(); //TODO Add to get by username.
//Get the field location.
//var field =_docuSignManager.GetField();
var fieldInfo = _docuSignManager.GetFieldByPosition(position);
// Create signHere fields (also known as tabs) on the documents,
// We're using anchor (autoPlace) positioning
//
// The DocuSign platform seaches throughout your envelope's
// documents for matching anchor strings.
SignHere signHere1 = new SignHere
{
DocumentId = "1",
PageNumber = fieldInfo.PageNumber.ToString(),
RecipientId = "1",
TabLabel = position.PositionName,
XPosition = fieldInfo.XOffset.ToString(),
YPosition = fieldInfo.YOffset.ToString()
};
// Tabs are set per recipient / signer
Tabs signer1Tabs = new Tabs
{
SignHereTabs = new List<SignHere> { signHere1 }
};
signer1.Tabs = signer1Tabs;
// Add the recipient to the envelope object
Recipients recipients = new Recipients
{
Signers = new List<Signer> { signer1 }
};
envelopeDefinition.Recipients = recipients;
// Request that the envelope be sent by setting |status| to "sent".
// To request that the envelope be created as a draft, set to "created"
envelopeDefinition.Status = "sent";
return envelopeDefinition;
}
// ***DS.snippet.0.end
public override string EgName => "eg001";
[HttpPost]
public IActionResult Create(SigningViewModel model)
{
// Data for this method
// signerEmail
// signerName
// dsPingUrl -- class global
// signerClientId -- class global
// dsReturnUrl -- class global
string accessToken = RequestItemsService.User.AccessToken;
string basePath = RequestItemsService.Session.BasePath + "/restapi";
string accountId = RequestItemsService.Session.AccountId;
// Check the token with minimal buffer time.
bool tokenOk = CheckToken(3);
if (!tokenOk)
{
// We could store the parameters of the requested operation
// so it could be restarted automatically.
// But since it should be rare to have a token issue here,
// we'll make the user re-enter the form data after
// authentication.
RequestItemsService.EgName = EgName;
return Redirect("/ds/mustAuthenticate");
}
string redirectUrl = DoWork(model.Email, model.Name, accessToken, basePath, accountId);
// Redirect the user to the Signing Ceremony
return Redirect(redirectUrl);
}
}
}
I expect after authentication to get sent to the document for signing within the browser.
I suggest to gather additional information:
Fiddler logs can show the various redirects and which urls are called
DocuSign API logs can show any APIs being called and any errors
Using VS Debugger, put a breakpoint inside MakeRecipientViewRequest(). see
if you get there, and if it follows to completion and what url is coming back from API. Try this url yourself to see if it works.
There are two different "returns" involved here.
During the OAuth flow: After the user authenticates (and consents to using your app, a one time process), the DocuSign authentication server should redirect to your app's OAuth url. Your code (or your framework's code) should then exchange the authorization code provided as a query parameter for an access token.
I think this is your issue.
Check that you have the redirect uri set up in the DocuSign Admin tool for your integration key.
Added: Set the redirect URI for your application's Integration Key from the Admin tool, using the API and Keys screen. (See the navigation screenshot, below.)
Important: You must set the Redirect URI to be exactly the same as what your application uses. Eg, http://localhost:8080/ds/callback You can use http or https. localhost is fine.
Also check what redirect uri is being used when your app starts the oauth process with DocuSign. (Visible on the url sent to DocuSign to start OAuth)
See the Prerequisites section of the readme for the return URI setting.
The signing ceremony "return"--
After you obtain the access token and create the envelope, you use the recipient view API method to obtain a URL for the signing ceremony. Here, you provide your return url which is used after the user completes the signing ceremony.
To debug this, start by downloading the request logs from DocuSign to assure that you're specifying the return url as you expect.

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.

Validate Google id token with C#

I'm currently creating a web application on which the user can login via his Google account. This works client side but I would also like to secure REST API calls. To do so, I send the "Google id token" with each request via the "Authorization" header. Now, I would like to verify in C# that the token passed is valid. I found that there is a .NET library to do so but I didn't find anywhere any clear documentation on how to simply validate the token.
Does anyone have some pointer for this?
My answer is the same as the answer above with a little bit more details.
using Google.Apis.Auth;
using Google.Apis.Auth.OAuth2;
GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(Token);
...
The payload object contains all the information that you need.
According to the "Verify the integrity of the ID token" documentation multiple things must be checked, for the id token to be valid, not just the signature.
One of those is whether "the ID token is equal to [...] your app's client IDs". Since we never give the client ID to GoogleJsonWebSignature.ValidateAsync(token) it seems we need to check it manually. I'm assuming it's really just checking the signature and we need to do all of the other checks manually.
My first shot at this:
bool valid = true;
try
{
GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(Token);
if (!payload.Audience.Equals("YOUR_CLIENT_ID_1234567890.apps.googleusercontent.com"))
valid = false;
if (!payload.Issuer.Equals("accounts.google.com") && !payload.Issuer.Equals("https://accounts.google.com"))
valid = false;
if (payload.ExpirationTimeSeconds == null)
valid = false;
else
{
DateTime now = DateTime.Now.ToUniversalTime();
DateTime expiration = DateTimeOffset.FromUnixTimeSeconds((long)payload.ExpirationTimeSeconds).DateTime;
if (now > expiration)
{
valid = false;
}
}
}
catch (InvalidJwtException e)
{
valid = false;
}
For future reference the following verifications are checked internally by the Google.Apis.Auth library and no extra validations are required (both passing settings or checking the payload):
bad jwt (null, empty, too long, missing signature)
wrong algorithm
invalid signature
invalid issuer
signature time without tolerance
The following however require input by the developer in order to be validated. They can be passed with GoogleJsonWebSignature.ValidationSettings:
audience
hosted domain
signature time with tolerance
Source: Google.Apis.Auth.Tests/GoogleJsonWebSignatureTests.cs
According to the docs, the token must be validated by verifying the signature with Google's public key. Also check the aus, iss and exp claims, and the hd claim if applies.
Therefore only the aus (and hd) have to be tested explicitly by the developer.
try
{
//...
var validationSettings = new GoogleJsonWebSignature.ValidationSettings
{
Audience = new string[] { "[google-signin-client_id].apps.googleusercontent.com" }
};
var payload = await GoogleJsonWebSignature.ValidateAsync(idToken, validationSettings);
//...
}
catch (InvalidJwtException ex)
{
//...
}
Yet another simplified answer (for .net 6):
Add this nuget package to your project:
https://www.nuget.org/packages/Google.Apis.Auth
Add using statement:
using Google.Apis.Auth;
Create this method in your controller:
[AllowAnonymous]
[HttpPost("verify")]
public async Task<ActionResult> Verify(){
string token = Request.Headers["Authorization"].ToString().Remove(0,7); //remove Bearer
var payload = await VerifyGoogleTokenId(token);
if (payload==null)
{
return BadRequest("Invalid token");
}
return Ok(payload); }
Create the VerifyGoogleTokenId function:
public async Task<GoogleJsonWebSignature.Payload> VerifyGoogleTokenId(string token){
try
{
// uncomment these lines if you want to add settings:
// var validationSettings = new GoogleJsonWebSignature.ValidationSettings
// {
// Audience = new string[] { "yourServerClientIdFromGoogleConsole.apps.googleusercontent.com" }
// };
// Add your settings and then get the payload
// GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(token, validationSettings);
// Or Get the payload without settings.
GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(token);
return payload;
}
catch (System.Exception)
{
Console.WriteLine("invalid google token");
}
return null;
}
Test the implementation by sending a post request to yourapi.com/verify. Dont forget the authorization header.
Say thanks with an up vote.

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.

Categories