As the title allready explains I want to secure my webservice.
I've read that you can do this using an soap authentication header, but then the username en password are passed as plain text.
I was wondering what I should do to secure my webservice?
Examples would be great.
I have an example of a company we work with that has 2 webservices.
One to do the security and one to get the needed data but I don't have their side of the code the system looks great though:
bool loginSuccessFull = false;
/// knooppunt
string loginID = ConfigurationManager.AppSettings["WebServiceLogin"];
string password = ConfigurationManager.AppSettings["WebServicePass"];
//A. The m_SecurityService object is created and initialised
Security securityService = new Security();
securityService.CookieContainer = new System.Net.CookieContainer();
string challenge = securityService.InitializeLogin(loginID);
string pwd = password;
string response = pwd + challenge;
System.Security.Cryptography.SHA1CryptoServiceProvider SHA1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
SHA1.Initialize();
byte[] hash = SHA1.ComputeHash(System.Text.Encoding.Default.GetBytes(response));
System.Text.StringBuilder builder = new System.Text.StringBuilder();
foreach (byte b in hash)
builder.Append(b.ToString("x2"));
//2. A login is done with the m_SecurityService object
if (securityService.Login(builder.ToString()))
{
string ssoToken = Request.QueryString["SSOTOKEN"];
string ssoID = Request.QueryString["SSOID"];
if (!String.IsNullOrEmpty(ssoToken) && !String.IsNullOrEmpty(ssoID))
{
// Check with webserice if the token is valid.
Knooppunt.SSO.GenericSSO sso = new Knooppunt.SSO.GenericSSO();
sso.CookieContainer = securityService.CookieContainer;
try
{
if (sso.validateSSOToken(Convert.ToInt32(ssoID), ssoToken))
{
loginSuccessFull = true;
FormsAuthentication.RedirectFromLoginPage("default user", false);
}
}
catch
{ }
}
}
If it truly is a webservice, you should be using Windows Communication Foundation to generate the proxy and make the call. It makes a lot of this code much, much easier.
Honestly, it looks like the package that is used to connect to the web service that you are using (SSO?) is pretty non-standard, and does nothing more than derive from HttpWebRequest, which is VERY low-level, and too complex to use.
If you are going to secure your own web service (and you are exposing it over an HTTP channel), the easiest way is to get a digital certificate for your host and then use basic HTTP authentication over HTTPS.
You could also use other aspects of the WS-Security specifications (e.g. encoding the message, etc, etc) to secure your service.
Note that WCF supports all of these options, so you don't have to do any of this coding out of the box, and you can host it in IIS as well.
A good beginners reference to WCF is Michelle Bustamante's "Learning WCF: A Hands-On Guide".
After that, for more advanced WCF content (especially if you want to learn about concepts revolving around security in WCF and WS-* in general) I highly recommend "Programming WCF Services" by Juval Lowy.
Related
I have been contemplating on a dilemma for hours. I have a Visual Studio Solution that contains a WCF, WebForms, UWP, Xamarin and a SharedLibrary Projects.
I intend to use the WCF project as the backend which talks to the database and process Email and SMS integration and feed the other apps.
OPTION A
Currently, The WCF is hosted on an Azure App Service which makes it accessible via POST, GET, etc from the url which is: https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers
With such arrangements, I can perform a POST request to get data from the apps:
string response = string.Empty;
string url = "https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers";
try
{
var values = new Dictionary<string, string>
{
{ "data", Encryption.EncryptString(dat.ToString()) } //dat is incoming method param
};
string jsonString = JsonConvert.SerializeObject(values);
var cli = new WebClient();
cli.Headers[HttpRequestHeader.ContentType] = "application/json";
response = cli.UploadString($"{url}", jsonString);
var result = JsonConvert.DeserializeObject<string>(response);
topic.InnerText = Encryption.DecryptString(result.ToString());
}
catch (Exception)
{
return string.Empty;
}
The method above is a simple one as I have other ones where I Deserialize with Models/Classes.
OPTION B
I equally have access to the methods defined in service1 by adding the project reference to my WebForms which surprisingly is also compatible with xamarin but not with UWP. Nevertheless, I am interested in the WebForms scenario. Below is an example method:
using BackEnd;
//Service1 service1 = new Service1();
//var send = service1.GetUsers(dat.ToString()); //dat is incoming method param
//topic.InnerText = send;
Obviously, using the Option B would eliminate the need to encrypt, decrypt, serialize or deserialize the data being sent. However, I have serious performance concerns.
I need to know the better option and if there is yet another alternative (probably an Azure Resource), you can share with me.
If you decide to use https endpoint of the Azure website, option A is secure because of SSL encryption. So you don't have to encrypt/decrypt it by yourself. The only tip is to create a proper authorization mechanism. For example use TransportWithMessageCredential. An example is provided in below article https://www.codeproject.com/Articles/1092557/WCF-Security-and-Authentication-in-Azure-WsHttpBin
I have a scenario where a C#.NET program must be supplied a secret that is derived from a Username/Password combination. This secret is non-reversible (think of it like a hash) but has the requirement that the secret can only be created with a .NET library.
The problem here is that there is the program that actually contains the username/password is different than the program that needs the secret and is not a .NET program, so the information has to be sent through some IPC.
It is worth noting that the program the has the username/password keeps them in memory for as long as the user is logged in. I realize that this itself is very insecure, but it isn't something that I am able to change.
I am looking for a way for the C#.NET program to get the secret as securely as possible.
Two options that I came up with are to use a Named Pipe to transfer the username/password directly from one application to the other, or to use COM components so the first application can send the username/password to the COM component, which can compute the secret and write it to a location that can be read later by the second application.
for the Named Pipe method I would expect the credential requesting component to look something like the following
using (var a = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous, TokenImpersonationLevel.Impersonation))
{
var requestString = new StreamString(a);
var credString = new StreamString(a);
await a.ConnectAsync();
a.ReadMode = PipeTransmissionMode.Message;
requestString.WriteString(JsonConvert.SerializeObject(new CredentialRequest()));
var cred = JsonConvert.DeserializeObject<Credential>(credString.ReadString());
var secret = await CreateSecret(cred.Username, cred.Password);
a.Close();
}
Based on that, the side opposite side of the pipe should be pretty obvious.
For the COM solution I am thinking that I would create an object that has the following method
public void ComputeAndStoreSecret(string username, string password)
{
var secret = CreateSecret(username, password);
using (var applicationStorageFileForUser = IsolatedStorageFile.GetUserStoreForAssembly())
{
using (var applicationStorageStreamForUser = new IsolatedStorageFileStream("secret_store.txt", FileMode.Create, applicationStorageFileForUser))
{
using (StreamWriter sw = new StreamWriter(applicationStorageStreamForUser))
{
sw.WriteLine(secret);
}
}
}
}
There would also be a COM component that reads from that storage location and returns the secret.
I am basically looking for any insight as to why I would want to use one of these techniques over the other or maybe consider any other options (RPC, Mailbox, etc.)
Some things that I have discovered/considered
Named Pipe solution is vulnerable to Pipe Squatting
Named Pipes require username/password to leave process space of the first application
Storage Location of secret using COM solution can be seen and edited by everything
Isolated File Storage in COM solution is not intended to be used for secret information
I realize that neither of these solutions are secure, but I am looking for a solution that at least doesn't make it drastically more insecure.
The short question is whether is this possible and if so, how?
Outline
I have a .NET application which currently uses a service account to access information across a Google Apps domain using the Google Drive API. This works fine using the google-api-dotnet-client library and code along the same lines as shown in the samples here - which are currently a very good basic example of what I'm doing.
What I want to do now is extend it so as well as using those APIs provided by the "new" google-api-dotnet-client library, it uses the older "GData" libraries, as provided for via the
older google-gdata library, specifically the Spreadsheets API (and perhaps more to come).
The Problem
This is where the difficulty arises. The former library does exactly what I want, as evidenced by the second link in the first paragraph above - and the fact I have it doing it myself. HOWEVER... although the second library has been updated to support OAuth 2.0 in addition to OAuth 1.0 and the other older auth techniques, it does not - as far as I can tell from extensive Googling and trail-and-error - allow the "service account on behalf of all my users" operation which I need.
My question is whether I'm missing something (possibly a hard to find or undocumented something) which would allow me to do what I want. Failing that, is there any way I could force this behaviour and make these two libraries operate side by side?
The ideal solution
Ideally I would love some way of having the Google.GData.Spreadsheets.SpreadsheetsService instance be able to take advantage of the Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> instance I'm already using... somehow. Is such witchcraft possible? I'm I missing the obvious?
Failing that, I'm happy to do the whole OAuth2 "assertion flow client" dance again if I have to, in some way that the older library can handle.
Help?
Other Thoughts
I have considered - and rejected for the time being - the option of starting from scratch and writing my own library to make this happen. This is for two reasons:
The gdata library already exists, and has been developed by many people likely cleverer than myself. I'm not so arrogant that I believe I can do better.
I'm not certain the OAuth2 with service account approach is even supported/allowed on these older APIs.
An alternate approach which I've been hoping to avoid but may have to fall back to depending on the answers here will be to use 2-legged OAuth 1.0 for portions of this. I'd prefer not to, as having parts of the app rely on one old auth method whilst other parts do it the nice new way just feels wrong to me. And there's that much more to go wrong...
Updates
I have considered the possibility of subclassing GDataRequestFactory and GDataRequest so I can make my own request factory and have that take the instance of Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> (well, an instance of Google.Apis.Authentication.IAuthenticator anyway) which could step in to authenticate the request just before it's called. However... the constructor for GDataRequest is internal, which has stopped me.
It's really looking like this isn't meant to be.
For the sake of other folks coming across this question (now that the solution linked to in the accepted answer uses deprecated code), here's how I solved it:
First, start in "new API" land (use the Google.Apis.Auth nuget package) by setting up a ServiceAccountCredential following Google's Service Account example:
//In the old api, this accessed the main api accounts' sheets, not anymore
//** Important ** share spreadsheets with the Service Account by inviting the "serviceAccountEmail" address to the sheet
string serviceAccountEmail = "12345697-abcdefghijklmnop#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" }
}.FromCertificate(certificate));
Tell the credential to request an Access Token:
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();
Now it's time to switch back to "old API" land (use the Google.GData.Spreadsheets nuget package). Start by constructing the SpreadsheetsService (similar to Google's example):
SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");
To use Service Account authentication, we'll create an instance of the GDataRequestFactory and set a custom Authorization header:
var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
Finally, set the SpreadsheetsService's RequestFactory property to this new factory:
service.RequestFactory = requestFactory;
And go ahead and use the SpreadsheetsService as you would had you authenticated using any other technique. (Tip: share spreadsheets with the Service Account by inviting the serviceAccountEmail address to the sheet)
I managed to solve this by subclassing GDataRequestFactory and creating my own implementation of the interfaces implemented by GDataRequest. This implementation wraps an instance of GDataRequest instantiated via reflection, and adds in the necessary code to perform authentication using an instance of IAuthenticator (in my case, Auth2Authenticator).
I wrote a blog post on it and added an example as a Gist:
Blog: Using Google's Spreadsheet API using .NET, OAuth 2.0 and a Service Account
Gist 4244834
Feel free to use this if it helps you (BSD licence).
Hey just stumbled accross the same problem and produced a different solution:
Has anybody ever concidered of writing the parameters from the credentials-object directly to an OAuth2Parameters-Object?
I did this and it worked nicely:
public class OAuthTest
{
OAuth2Parameters param = new OAuth2Parameters();
public OAuthTest()
{
Debug.WriteLine("Calling: AuthGoogleDataInterface()");
bool init = AuthGoogleDataInterface();
if (init)
{
GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "My App User Agent", this.param);
//requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
var service = new SpreadsheetsService("MyService");
service.RequestFactory = requestFactory;
SpreadsheetQuery query = new SpreadsheetQuery();
// Make a request to the API and get all spreadsheets.
SpreadsheetFeed feed = service.Query(query);
// Iterate through all of the spreadsheets returned
foreach (SpreadsheetEntry entry in feed.Entries)
{
// Print the title of this spreadsheet to the screen
Debug.WriteLine(entry.Title.Text);
}
}
Debug.WriteLine(m_Init);
}
private bool AuthGoogleDataInterface()
{
bool b_success;
try
{
Console.WriteLine("New User Credential");
// New User Credential
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
GoogleClientSecrets GCSecrets = GoogleClientSecrets.Load(stream);
string[] ArrScope = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" };
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GCSecrets.Secrets,
ArrScope,
"user", CancellationToken.None,
new FileDataStore("My.cal")).Result;
// put the Information generated for the credentials object into the OAuth2Parameters-Object to access the Spreadsheets
this.param.ClientId = GCSecrets.Secrets.ClientId; //CLIENT_ID;
this.param.ClientSecret = GCSecrets.Secrets.ClientSecret; //CLIENT_SECRET;
this.param.RedirectUri = "urn:ietf:wg:oauth:2.0:oob"; //REDIRECT_URI;
this.param.Scope = ArrScope.ToString();
this.param.AccessToken = credential.Token.AccessToken;
this.param.RefreshToken = credential.Token.RefreshToken;
}
Debug.WriteLine("AuthGoogleDataInterface: Success");
b_success = true;
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
b_success = false;
}
return b_success;
}
}
I have a WCF 4.0 REST service on server side (hosted in IIS) and an Android client. The Android client sends an encrypted security token in a custom HTTP header in order to authenticate the user. I have implemented a custom ServiceAuthorizationManager which extracts the security token from the header. The token contains the username which I can read from the token:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
var requestMessage = operationContext.RequestContext.RequestMessage;
var requestProperty = (HttpRequestMessageProperty)requestMessage
.Properties[HttpRequestMessageProperty.Name];
var token = requestProperty.Headers["X-MyCustomHeader"];
if (!string.IsNullOrEmpty(token))
{
var userName = GetUserNameFromToken(token);
if (!string.IsNullOrEmpty(userName))
{
// How to save userName now so that I can
// retrieve it in the service operations?
return true;
}
}
return false;
}
}
Now, my problem is that I also need the name of the authenticated user in various service operations (mainly to access user profile data) and I was planning to retrieve it this way:
public void MyServiceOperation()
{
string userName = OperationContext.Current
.ServiceSecurityContext.PrimaryIdentity.Name;
// check profile store for that userName and do something depending on
// profile settings
}
How can I set this username in CheckAccessCore?
A very naive trial like this...
operationContext.ServiceSecurityContext.PrimaryIdentity.Name = userName;
...doesn't work because PrimaryIdentity.Name is readonly. I assume that more sophisticated code is required.
After some research I didn't find a way to set the identity in ServiceAuthorizationManager.CheckAccessCore. This method seems to be called too late in the processing pipeline when the user's identity is already set (possibly to "Anonymous" (IsAuthenticated is false)) and cannot be changed anymore. The ServiceAuthorizationManager is made for authorization, not authentication, therefore it's the wrong place to implement a custom authentication.
I've finally found three possible ways for my problem:
As explained in the article linked in #TheCodeKing's answer, using the WCF REST Starter Kit provides the option to write a custom RequestInterceptor which hooks in early enough into the pipeline, allows access to the incoming request and allows to set the user's identity based on, for example, custom HTTP headers. Unfortunately the WCF REST Starter Kit is old (based on WCF 3.5) and the development has apparently been abandoned. Some of its features have been incorporated into WCF 4.0, but some have not, and the RequestInterceptor is among them. Nonetheless I've used this solution now and mixed the Microsoft.ServiceModel.Web assembly from the Starter Kit into my WCF 4.0 solution. It seems to work so far after a few simple tests.
If the identity is not really necessary but only the user name, a simple "trick"/"hack" to write the user name into a new request header works (also in CheckAccessCore):
// ...
var userName = GetUserNameFromToken(token);
if (!string.IsNullOrEmpty(userName))
{
requestProperty.Headers["X-UserName"] = userName;
return true;
}
// ...
And then in the service methods:
public void MyServiceOperation()
{
string userName = WebOperationContext.Current.IncomingRequest
.Headers["X-UserName"];
// ...
}
Another and more low level option would be to write a custom HttpModule which intercepts the incoming request and sets the identity. An example how this could look like is here from Microsoft Pattern & Practices team (see the "HTTP Module Code" example in the mid of the article).
Take a look at this article. It has examples of setting up the IAuthorizationPolicy instance. By creating your own implementation you can control the creation of the IPrincipal and IIdentity instances which are passed around in the context. It's all hooked in from a service interceptor.
internal class AuthorizationPolicyFactory
{
public virtual IAuthorizationPolicy Create(Credentials credentials)
{
var genericIdentity = new GenericIdentity(credentials.UserName);
var genericPrincipal = new GenericPrincipal(genericIdentity,
new string[] { });
return new PrincipalAuthorizationPolicy(genericPrincipal);
}
}
I created a simple .NET WebService (it just passes back a string). How do I modify the server side (and possibly the client side) so that it also uses a username/password to validate before sending a response?
Client Code:
static void Main(string[] args)
{
UpdateClient client = new UpdateClient("UpdateSOAPIIS");
client.ClientCredentials.UserName.UserName = "Michael";
client.ClientCredentials.UserName.Password = "testpassword";
String response = client.GetString("New York, NY");
Console.WriteLine(response);
if (client != null) client.Close();
}
Server Code:
public virtual GetStringResponse GetString(GetStringRequest request)
{
return new GetStringResponse("Search Location: " + request.location);
}
I recommend reading Juval Lowy's excellent article Declarative WCF Security. He describes five common scenarios (intranet, internet, b2b, anonymous, no security at all) and shows what that means, how to accomplish that etc.
He even goes as far as creating declarative attributes that you can basically just put on your service declaration and be done with it.
Those security scenario should really cover at least 80%, if not 95% of your typical cases. Study them and use them! Highly recommended
It really depends on what kind of security you want. Should the protocol be encrypted, should the data be encrypted, or do you just want to authenticate a user. In the last case you can just go ahead and use whatever technology you want to verify that the user has permissions to use the API. For other options and some code, check out this MSDN article http://msdn.microsoft.com/en-us/library/ms731925.aspx