It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I want to, without using the built in WCF/c# components for it,
Authenticate clients to a RESTful service
Handle authentication failures on an API call in the client
This is a pedagogical exercise: I realize there are built in methods for authentication, I want to do this from scratch to understand how it all works.
I have the password hashing and checking logic and an exposed REST call that validates the password, but I am unsure how to procede from there.
Background
Im struggling on creating an authentication method for my rest service.
So far I have managed to create a hash of a password, salt and stored the salt and I have managed to authenticate the user. However I am not sure how you would encapsulate all of my wcf REST requests so that if any are requested (GET,POST) it asks you to login and if your logged in does not.
Because I roled my own authentication technique and I am new to web services and C# I really dont know where to begin?
So I am going to offer 300 rep to anyone who could provide an approach to this.
Code
This is my rest service:
[ServiceContract(Namespace = "http://tempuri.org")]
[XmlSerializerFormat]
public interface IService
{
.... all of my GET, POST, PUT and DELETE requests
{
[DataContract(Name="Student")]
[Serializable]
public class Student
{
[DataMember(Name = "StudentID")]
public string StudentID { get; set; }
[DataMember(Name = "FirstName")]
public string FirstName { get; set; }
[DataMember(Name = "LastName")]
public string LastName { get; set; }
[DataMember(Name = "Password")]
public string Password;
[DataMember(Name = "Salt")]
public byte[] Salt;
//note the use of public datamembers for password and salt, not sure how to implement private for this.
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
[Serializable]
public class Service: IService
{
#region Authentication, hash and salt
protected RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
public byte[] GenerateSalt() //Generate random salt for each password
{
byte[] salt = new byte[10000];
random.GetNonZeroBytes(salt);
return salt;
}
public static byte[] Hash(string value, byte[] salt) //hash and salt the password
{
return Hash(Encoding.UTF8.GetBytes(value), salt);
}
public static byte[] Hash(byte[] value, byte[] salt) // create hash of password
{
byte[] saltedValue = value.Concat(salt).ToArray();
return new SHA256Managed().ComputeHash(saltedValue); //initialise new isntance of the crypto class using SHA-256/32-byte (256 bits) words
}
public string AuthenticateUser(string studentID, string password) //Authentication should always be done server side
{
var result = students.FirstOrDefault(n => n.StudentID == studentID);
//find the StudentID that matches the string studentID
if (result != null)
//if result matches then do this
{
byte[] passwordHash = Hash(password, result.Salt);
string HashedPassword = Convert.ToBase64String(passwordHash);
//hash salt the string password
if (HashedPassword == result.Password)
//check if the HashedPassword (string password) matches the stored student.Password
{
return result.StudentID;
// if it does return the Students ID
}
}
return "Login Failed";
//if it doesnt return login failed
}
#endregion
I am hosting from a console app aswell and I have no web.config files or app.config files. And because I did my own authentication method I am not sure if basic authentication would work.
I also do not want to keep a session in order to keep the service SOA and Stateless.
Console app:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.Transport;
host.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
Console.ReadLine();
}
}
}
Note that on my client side I do something very basic in order to authenticate:
private void Login_Click(object sender, RoutedEventArgs e)
{
//Authenticate user (GET Request)
string uri = string.Format("http://localhost:8000/Service/AuthenticateUser/{0}/{1}", textBox1.Text, passwordBox1.Password);
XDocument xDoc = XDocument.Load(uri);
string UserAuthenticationID = xDoc.Element("string").Value;
Int32 value;
if (Int32.TryParse(UserAuthenticationID, out value))
{
MainWindow authenticatedidentification = new MainWindow();
authenticatedidentification.SetLabel(UserAuthenticationID);
authenticatedidentification.Show();
this.Close();
}
else
{
label1.Content = UserAuthenticationID;
}
}
So I am not sure what else would have to be carryed to the main application if anything for the above mentioned, in order for the main app to access those rest requests.
So the way this is typically done is
the client provides some credentials via an authenticate service call
the service validates those credentials and hands back some auth-token.
Subsequent calls have use that token to authenticate.
This is done either by sending the token along (e.g. http digest authentication) or way more securely, the token is a key that is used to compute a message authentication code on the on the paramaters. This prevents anyone from tampering with the requests.
There is a decent though long discussion on how to do this in WCF here. See the section on "Security Considerations" and the section on "Implementing Authentication and Authorization"
So lets say you've done this ( or your sending the username and password with every request -- a bad idea but hey, this is just for educational purposes) and you have a AuthenticateUser method that returns false if the users is not authenticated. Now in every exposed REST method you add this call ( with the parameters either being the user name and passwords, or an auth token)
if (!AuthenticateUser(/* auth params here */))
{
WebOperationContext.Current.OutgoingResponse.StatusCode =
HttpStatusCode.Unauthorized;
return;
}
This causes the request to fail and the client will get an HTTP 403 Forbiden response.
I assume you are using HttpWebRequest to make the calls to the REST API.
So in your client program, after your have prepared request,added whatever paramaters you need, do this
try
{
var wResp = (HttpWebResponse)wReq.GetResponse();
var wRespStatusCode = wResp.StatusCode;
}
catch (WebException we)
{
var wRespStatusCode = ((HttpWebResponse)we.Response).StatusCode;
if( wRespStatusCode == HttpStatusCode. Unauthorized)
{
// call to your sign in / login logic here
} else{
throw we;
}
}
You need to include the authentication token somehow in the request, either as a get or post paramater or in the header. Post or Get is simply a matter of adding the paramater to the request data. The header is a little bit more difficult, I believe its outlined in the MSDN link I refrenced above.
Why not to use OAuth or OpenID for your REST service?! There is OAuth 2.0 or prior versions. There are also implementations for client and server. OAuth pass good for REST services
You do not need to create your own mechanism.
Main site for OAuth - http://oauth.net/code/
There you can find description on how OAuth works, flows etc. Also there are links to implementations, e.g. DotnetOpenAuth
Latest specification - https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2.
You can find a lot of samples for DotNetOAuth's OAuth implementation on their Github repo
https://github.com/AArnott/dotnetopenid/tree/master/samples
#jbtule and #Damien_The_Unbeliever make excellent points about storing salt with the hashed password.
As for your question of how to implement it, I would not do it as a separate service method, but instead make the authentication part of the method call itself. It will then be up to the client to pass credentials with the service call.
This link describes in a lot of detail how to accomplish that, what it looks like from the server and client, etc.
Edit: Instead of passing the username and password in the message credentials like in the above link, you can pass the login token and just check that it's valid on the web service before executing the request.
The way I recently (last couple of weeks) did it is via an IDispatchMessageInspector. In the message inspector class I used securityContext.AuthorizationContext.ClaimSets to check the client's (caller) certificate, but you can use a custom header (User,Password) and look at OperationContext.Current.IncomingMessageHeaders. And in the AfterReceiveRequest( ) I would either throw a fault if the user was not a valid user or simply return null to indicate success.
Then I created an attribute that would add my inspector (MessageInspector) to the service class:
[AttributeUsage(AttributeTargets.Class)]
public class AuthorizeAttribute : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase dispatcher in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = dispatcher as ChannelDispatcher;
if (channelDispatcher != null)
{
foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
{
var inspector = new MessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
}
}
//var config = new ServiceLayerConfiguration();
//config.RequestProcessorImplementation = typeof(PassThruRequestProcessor);
//config.Initialize();
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
And finally in the service class I would simply add the attribute.
[AuthorizeAttribute]
public class OperaService : IMyService
I can give more details if necessary. I still have the client/service app on my box. :)
Related
My need is very specific. I need to access a directory on Google Drive that is a shared folder. The only thing in it will be empty form documents and spreadsheet templates. There is nothing of value in the folder, and it is used internally only. So, I can be very optimistic WRT security concerns. I just need access.
I am extending an existing ERP system that runs as an IIS application.
My customization is .NET/C# project that extends the ERP's .NET classes. I cannot implement a login/auth system because one already exists for the ERP.
I did the .NET quickstart, but of course that is a console app, and will not work when I move it to IIS. The suggestion to follow the standard MVC model doesn't work for me -- adding a second web site/page is needlessly complicated for my needs.
My question is: How can I authorize access to a Google Drive that
A) Runs within IIS
B) Does not require a separate ASP Web Application to implement MVC for authorization.
=============================
Similar to issues in:
Google API Fails after hosting it to IIS
you could use OAuth authorization with your asp.net application:
Create Web Server client_secret.json.by using GetAuthorizationUrl() create url for get toke temporary token.Redirect to GoogleCallback() and get refresh and access tokens using ExchangeAuthorizationCode().Save them to the file "~/Resources/driveApiCredentials/drive-credentials.json/Google.Apis.Auth.OAuth2.Responses.TokenResponse-{account}".Use this saved tokens.
you could refer to the below link for more detail:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
Google Drive API upload Fails after hosting it to IIS. showing the error as Failed to launch the Browser with
Google Drive API not uploading file from IIS
Google Data API Authorization Redirect URI Mismatch
Jalpa's answer was not what I was looking for, nor was anything referenced in any of the links.
I'm going to put my answer here, because it is what I needed, and it might be useful to others.
First the overview
Google's QuickStart for .NET only shows the console based solution. As many have discovered, this does not work when you switch to an IIS based solution. It is almost as if the API deliberately defeats your attempts to do so. It simply will not allow you to use a token created for a local application using GoogleWebAuthorizationBroker.AuthorizeAsync -- it will error even if a browser isn't needed. (ie the token hasn't expired, so it won't need the browser to authenticate anything.)
Trying to run a refresh authorization gives you a token, but not a service. And even if the token is valid, you still can't use AuthorizeAsync to get your service from an IIS application (see above)
This is how I handle this:
Do the quick start and run the authorization that pops up the local browser and allows you to login and authenticate.
It creates a local folder(token.json), where it puts a token file (Google.Apis.Auth.OAuth2.Responses.TokenResponse-user) It's just a json file. Open it in notepad++ and you will find the fields:
"access_token": "token_type": "expires_in": "refresh_token":
"scope": "Issued": "IssuedUtc":
You need the refresh_token. I simply combined that with the initial credentials file I downloaded from the Google API Console (i.e. "credentials.json") and named it "skeleton_key.json"
This file is all you will need to generate valid tokens forever.
I have 2 classes I use for this. First the class that creates the Drive Service:
public class GDriveClass
{
public String LastErrorMessage { get; set; }
static string[] Scopes = { DriveService.Scope.Drive }; // could pull this from skeleton_key
static string ApplicationName = "GDrive Access"; // this is functionally irrelevant
internal UserCredential usrCredentials;
internal Google.Apis.Drive.v3.DriveService CurrentGDriveService = null;
internal String basePath = "."; // this comes in from calling program
// which uses HttpContext.Current.Server.MapPath("~");
public GDriveClass(string logFileBasePath)
{
basePath = logFileBasePath;
LastErrorMessage = "";
}
#region Google Drive Authenticate Code
public bool AuthenticateUser(string FullTokenAccessFileName)
{
UserCredential credential;
String JsonCredentialsonFile = System.IO.File.ReadAllText(FullTokenAccessFileName);
string credPath = basePath + #"\Credentials\token.json";
// Force a Refresh of the Token
RefreshTokenClass RTC = new RefreshTokenClass();
// Set field values in RefreshTokenClass:
var jObject = Newtonsoft.Json.Linq.JObject.Parse(JsonCredentialsonFile);
var fieldStrings = jObject.GetValue("installed").ToString();
var fields = Newtonsoft.Json.Linq.JObject.Parse(fieldStrings);
RTC.client_id = fields.GetValue("client_id").ToString();
RTC.client_secret = fields.GetValue("client_secret").ToString();
RTC.refresh_token = fields.GetValue("refresh_token").ToString();
RTC.ExecuteRefresh(); // this gets us a valid token every time
try
{
GoogleCredential gCredentials = GoogleCredential.FromAccessToken(RTC.access_token);
CurrentGDriveService = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = gCredentials,
ApplicationName = ApplicationName,
});
return true;
}
catch (Exception ex)
{
LastErrorMessage = "Error: Authenticating - " + ex.Message;
return false;
}
}
Usage is pretty straight forward:
string TokenFile = #basePath + #"\skeleton_key.json";
GDRIVER.AuthenticateUser(TokenFile);
var rslt = GDRIVER.LastErrorMessage;
if (!String.IsNullOrEmpty(rslt))
{
WriteToLogFile("ERROR in Google AuthenticateUser() ");
AlertMessage("Unable To Connect to Google Drive - Authorization Failed");
return;
}
And this is the class that refreshes the token via REST API as needed:
public class RefreshTokenClass
{
public string application_name { get; set; }
public string token_source { get; set; }
public string client_id { get; set; }
public string client_secret { get; set; }
public string scope { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public RefreshTokenClass()
{
}
public bool ExecuteRefresh()
{
try
{
RestClient restClient = new RestClient();
RestRequest request = new RestRequest();
request.AddQueryParameter("client_id", this.client_id);
request.AddQueryParameter("client_secret", this.client_secret);
request.AddQueryParameter("grant_type", "refresh_token");
request.AddQueryParameter("refresh_token", this.refresh_token);
restClient.BaseUrl = new System.Uri("https://oauth2.googleapis.com/token");
var restResponse = restClient.Post(request);
// Extracting output data from received response
string response = restResponse.Content.ToLower(); // make sure case isn't an issue
// Parsing JSON content into element-node JObject
var jObject = Newtonsoft.Json.Linq.JObject.Parse(restResponse.Content);
//Extracting Node element using Getvalue method
string _access_token = jObject.GetValue("access_token").ToString();
this.access_token = _access_token;
return true;
}
catch (Exception ex)
{
//Console.WriteLine("Error on Token Refresh" + ex.Message);
return false;
}
}
Note: This makes use of Newtonsoft.Json and RestSharp.
Thanks to user: "OL." who gave me the way of creating a service from a token (that somehow I missed in the docs!)
How to create Service from Access Token
And to user:"purshotam sah" for a clean REST API approach
Generate Access Token Using Refresh Token
Over the last few days I've been playing with the micro service pattern and all is going well but security seems to baffle me.
So If I may ask a question:
How do I handle user authentication on an individual service? At the moment I pass a request to the Gateway API which in turns connects to the service.
Question Edited Please See Below
Bearing in mind that the individual services should not know about each other. The Gateway is the aggregator as such.
Current architecture.
A little code to simulate the request:
Frontend - Client App
public class EntityRepository<T>
{
private IGateway _gateway = null;
public EntityRepository(IGateway gateway)
{
this._gateway = gateway;
}
public IEnumerable<T> FindAll()
{
return this._gateway.Get(typeof(T)).Content.ReadAsAsync<IEnumerable<T>>().Result;
}
public T FindById(int id)
{
return this._gateway.Get(typeof(T)).Content.ReadAsAsync<T>().Result;
}
public void Add(T obj)
{
this._gateway.Post(typeof(T), obj);
}
public void Update(T obj)
{
this._gateway.Post(typeof(T), obj);
}
public void Save(T obj)
{
this._gateway.Post(typeof(T), obj);
}
}
//Logic lives elsewhere
public HttpResponseMessage Get(Type type)
{
return Connect().GetAsync(Path(type)).Result;
}
public HttpResponseMessage Post(Type type, dynamic obj)
{
return Connect().PostAsync(Path(type), obj);
}
private string Path(Type type)
{
var className = type.Name;
return "api/service/" + Application.Key + "/" + className;
}
private HttpClient Connect()
{
var client = new HttpClient();
client.BaseAddress = new Uri("X");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
I use generics to determine where it needs to fire once it hit's the gateway.
So if the Type is Category it will fire the Category service thus calling:
public IEnumerable<dynamic> FindAll(string appKey, string cls)
{
var response = ConnectTo.Service(appKey, cls);
return (appKey == Application.Key) ? (response.IsSuccessStatusCode) ? response.Content.ReadAsAsync<IEnumerable<dynamic>>().Result : null : null;
}
The Gateway does not contain the physical files/Class's of the types.
After a little code, I was hoping someone could give me a little demonstration or the best approach to handle security/user authentication with the current architecture.
Case Scenario 1
User hits the web app and logs in, at that point the users encrypted email and password is sent to the Gateway API which is then passed to the User Service and decides whether the user is authenticated - all well and good but now I want to fetch all Messages from the Message Service that the user has received. I cannot really say in the Gateway if the user is authenticated, fetch the messages because that does not solve the issue of calling the Message Service outside of the Gateway API
I also cannot add authentication to each individual service because that would require all respective services talking to the User Service and that defeats the purpose of the pattern.
Fixes:
Only allow the Gateway to call the Services. Requests to services outside of the Gateway should be blocked.
I know security is a broad topic but within the current context, I'm hoping someone could direct me with the best course of action to resolve the issue.
Currently I have Hardcoded a Guid in all off the applications, which in turn fetches data if the app is equal.
Edit
This answer is about the Gateway <-> Micro service communication. The user should of course be properly authenticated when the App talks with the gateway
end edit
First of all, the micro services should not be reachable from internet. They should only be accessible from the gateway (which can be clustered).
Second, you do need to be able to identify the current user. You can do it by passing the UserId as a HTTP header. Create a WebApi filter which takes that header and creates a custom IPrincipal from it.
Finally you need some way to make sure that the request comes from the gateway or another micro service. An easy way to do that is to use HMAC authentication on a token.
Store the key in the web.config for each service and the gateway. Then just send a token with each request (which you can authenticate using a WebApi authentication filter)
To generate a hash, use the HMACSHA256 class in .NET:
private static string CreateToken(string message, string secret)
{
secret = secret ?? "";
var keyByte = Encoding.ASCII.GetBytes(secret);
var messageBytes = Encoding.ASCII.GetBytes(message);
using (var hasher = new HMACSHA256(keyByte))
{
var hashmessage = hasher.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
So in your MicroServiceClient you would do something like this:
var hash = CreateToken(userId.ToString(), mySharedSecret);
var myHttpRequest = HttpRequest.Create("yourUrl");
myHttpRequest.AddHeader("UserId", userId);
myHttpRequest.AddHeader("UserIdToken", hash);
//send request..
And in the micro service you create a filter like:
public class TokenAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
protected string SharedSecret
{
get { return ConfigurationManager.AppSettings["SharedSecret"]; }
}
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
await Task.Run(() =>
{
var userId = context.Request.Headers.GetValues("UserId").FirstOrDefault();
if (userId == null)
{
context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
return;
}
var userIdToken = context.Request.Headers.GetValues("UserIdToken").FirstOrDefault();
if (userIdToken == null)
{
context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
return;
}
var token = CreateToken(userId, SharedSecret);
if (token != userIdToken)
{
context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
return;
}
var principal = new GenericPrincipal(new GenericIdentity(userId, "CustomIdentification"),
new[] {"ServiceRole"});
context.Principal = principal;
});
}
public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
}
public bool AllowMultiple
{
get { return false; }
}
private static string CreateToken(string message, string secret)
{
secret = secret ?? "";
var keyByte = Encoding.ASCII.GetBytes(secret);
var messageBytes = Encoding.ASCII.GetBytes(message);
using (var hasher = new HMACSHA256(keyByte))
{
var hashmessage = hasher.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
}
Option 1 (Preferred)
The easy way is the micro services should be behind the gateway, hence you would whitelist services to connect to them, meaning only authorized and trusted parties have access (i.e. the gateway only). Clients shouldn't have direct access to them. The Gateway is your night club bouncer.
Option 2
You can use a JWT or some form of token and share the secret key between the services. I use JWT Authorization Bearer tokens.
The other services don't need to query the user service, they just need to know that the token is valid, then they have authorization to use the API. I get the JWT passed from the client to the gateway and inject it into the request that is sent to the other service behind, just a straight pass through.
The micro service behind needs to have the same JWT consumption as the gateway for authorization but as I mentioned that is just determining a valid token, not querying a valid user.
But this has an issue that once someone is authorized they can jump call upon other users data unless you include something like a claim in the token.
My Thoughts
The part that I found a challenge from Monolithic to Micro Services was that you needed to switch where you place your trust. In Monolithic you control everything you are in charge. The point of Micro Services is that other services are in complete control of their domain. You have to place your trust in that other service to fulfill its obligations and not want to recheck and reauthorize everything at every level beyond what is necessary.
I was tasked with adding logging via external service (using SAML 2.0) to an MVC app (.Net 4.5) that uses SimpleMembership. To be honest I'm not even sure where to start. From what I found on the internet there are few points to the problem. Most of the materials I found dealt with communication with the SAML identity provider (frequently written from scratch). However before I can reach that point I need to make sure I can actually integrate it with the SimpleMembership which we are using.
I suspect for starters I would need something like SAMLWebSecurity (akin to OAuthWebSecurity which we also use). I have found no such thing* on the internet which makes me believe it does not exist (though I wouldn't mind being wrong here). This makes me believe I would have to write it myself, but can I do that without have to write my own membership provider?
*I'm not sure what would be a correct way to call this static class.
I'd recommend that you upgrade to ASP.NET Identity and the OWIN Based authentication middleware. Then you can use Kentor.AuthServices middleware that works with ASP.NET Identity (except that the XSRF-guard has to be commented out until bug #127 has been resolved).
You could also use the SAML classes from Kentor.AuthServices if you have to stick with SimpleMembership, so that you don't have to implement SAML from scratch.
Disclaimer: I'm the author of Kentor.AuthServices, but since it's open source, I'm not making money on people using it.
After discussing it with a colleague I think I figured out the course of actions. Both OAuthWebSecurity and WebSecurity appear to be a part of SimpleMembership, so what I wrote in the question would indicate I want to write a custom membership or reverse engineer SimpleMembership to copy OAuthWebSecurity (which doesn't sound like a fun activity to have).
My best bet here is hijacking the OAuthWebSecurity, by writing a custom client (one which implements the IAuthenticationClient interface). Normally one registers various OAuth clients using OAuthWebSecurity's built in methods (like RegisterFacebookClient). But it is also possible to register those clients using OAuthWebSecurity.RegisterClient which accepts IAuthenticationClient. This way I should be able to add this SAML login without writing a custom membership provider and keep using SimpleMembership.
I managed to do this. Thankfully the identity provider wasn't extremely complicated so all I had to do was redirect to a certain address (I didn't even need to request assertion). After a successful login, the IDP "redirects" the user using POST to my site with the base64 encoded SAMLResponse attached. So all I had to do was to parse and validate the response. I placed the code for this in my custom client (implementing IAuthenticationClient interface).
public class mySAMLClient : IAuthenticationClient
{
// I store the IDP certificate in App_Data
// This can by actually skipped. See VerifyAuthentication for more details
private static X509Certificate2 certificate = null;
private X509Certificate2 Certificate
{
get
{
if (certificate == null)
{
certificate = new X509Certificate2(Path.Combine(HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data"), "idp.cer"));
}
return certificate;
}
}
private string providerName;
public string ProviderName
{
get
{
return providerName;
}
}
public mySAMLClient()
{
// This probably should be provided as a parameter for the constructor, but in my case this is enough
providerName = "mySAML";
}
public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
// Normally you would need to request assertion here, but in my case redirecting to certain address was enough
context.Response.Redirect("IDP login address");
}
public AuthenticationResult VerifyAuthentication(HttpContextBase context)
{
// For one reason or another I had to redirect my SAML callback (POST) to my OAUTH callback (GET)
// Since I needed to retain the POST data, I temporarily copied it to session
var response = context.Session["SAMLResponse"].ToString();
context.Session.Remove("SAMLResponse");
if (response == null)
{
throw new Exception("Missing SAML response!");
}
// Decode the response
response = Encoding.UTF8.GetString(Convert.FromBase64String(response));
// Parse the response
var assertion = new XmlDocument { PreserveWhitespace = true };
assertion.LoadXml(response);
//Validating signature based on: http://stackoverflow.com/a/6139044
// adding namespaces
var ns = new XmlNamespaceManager(assertion.NameTable);
ns.AddNamespace("samlp", #"urn:oasis:names:tc:SAML:2.0:protocol");
ns.AddNamespace("saml", #"urn:oasis:names:tc:SAML:2.0:assertion");
ns.AddNamespace("ds", #"http://www.w3.org/2000/09/xmldsig#");
// extracting necessary nodes
var responseNode = assertion.SelectSingleNode("/samlp:Response", ns);
var assertionNode = responseNode.SelectSingleNode("saml:Assertion", ns);
var signNode = responseNode.SelectSingleNode("ds:Signature", ns);
// loading the signature node
var signedXml = new SignedXml(assertion.DocumentElement);
signedXml.LoadXml(signNode as XmlElement);
// You can extract the certificate from the response, but then you would have to check if the issuer is correct
// Here we only check if the signature is valid. Since I have a copy of the certificate, I know who the issuer is
// So if the signature is valid I then it was sent from the right place (probably).
//var certificateNode = signNode.SelectSingleNode(".//ds:X509Certificate", ns);
//var Certificate = new X509Certificate2(System.Text.Encoding.UTF8.GetBytes(certificateNode.InnerText));
// checking signature
bool isSigned = signedXml.CheckSignature(Certificate, true);
if (!isSigned)
{
throw new Exception("Certificate and signature mismatch!");
}
// If you extracted the signature, you would check the issuer here
// Here is the validation of the response
// Some of this might be unnecessary in your case, or might not be enough (especially if you plan to use SAML for more than just SSO)
var statusNode = responseNode.SelectSingleNode("samlp:Status/samlp:StatusCode", ns);
if (statusNode.Attributes["Value"].Value != "urn:oasis:names:tc:SAML:2.0:status:Success")
{
throw new Exception("Incorrect status code!");
}
var conditionsNode = assertionNode.SelectSingleNode("saml:Conditions", ns);
var audienceNode = conditionsNode.SelectSingleNode("//saml:Audience", ns);
if (audienceNode.InnerText != "Name of your app on the IDP")
{
throw new Exception("Incorrect audience!");
}
var startDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotBefore"].Value, XmlDateTimeSerializationMode.Utc);
var endDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotOnOrAfter"].Value, XmlDateTimeSerializationMode.Utc);
if (DateTime.UtcNow < startDate || DateTime.UtcNow > endDate)
{
throw new Exception("Conditions are not met!");
}
var fields = new Dictionary<string, string>();
var userId = assertionNode.SelectSingleNode("//saml:NameID", ns).InnerText;
var userName = assertionNode.SelectSingleNode("//saml:Attribute[#Name=\"urn:oid:1.2.840.113549.1.9.1\"]/saml:AttributeValue", ns).InnerText;
// you can also extract some of the other fields in similar fashion
var result = new AuthenticationResult(true, ProviderName, userId, userName, fields);
return result;
}
}
Then I just registered my client in App_Start\AuthConfig.cs using OAuthWebSecurity.RegisterClient and then I could reuse my existing external login code (which was originally made for OAUTH). For various reasons my SAML callback was a different action than my OAUTH callback. The code for this action was more or less this:
[AllowAnonymous]
public ActionResult Saml(string returnUrl)
{
Session["SAMLResponse"] = Request.Form["SAMLResponse"];
return Redirect(Url.Action("ExternalLoginCallback") + "?__provider__=mySAML");
}
Additionally OAuthWebSecurity.VerifyAuthentication didn't work with my client too well, so I had to conditionally run my own verification in the OAUTH callback.
AuthenticationResult result = null;
if (Request.QueryString["__provider__"] == "mySAML")
{
result = new mySAMLClient().VerifyAuthentication(HttpContext);
}
else
{
// use OAuthWebSecurity.VerifyAuthentication
}
This probably all looks very weird and might differ greatly in case of your IDP, but thanks to this I was able to reuse most of the existing code for handling external accounts.
How can I log onto Microsoft Live (with .NET WebClient?) and automate the OAuth process to get a token to make Bing Ads API calls?
My question is similar to How do I get an OAuth request_token from live.com?. However, I am building (C#, .NET 4.5.2) a headless Windows Service using the context of a Bing Ads super admin account that is linked to multiple other Bing Ads accounts. The idea is to authenticate, get the auth bits, and then make calls using the bits at 3:00am. Some of the accounts "compete" so for example group A should not see data from group B, so having an application get data for everyone and filter it and distribute it overnight solves many business problems.
I am concerned that if Live experiences problems, or our application is down for an extended time for any reason, we will have to re-authenticate manually to get data again. Maintenance and management of the credentials is now additional overhead (this is for an enterprise environment) that will have to take the form of an intranet web site/page to allow junior/uninitiated folks to do the work if needed (lets not forget testing and documentation). To contrast, Google provides an option to use key pairs for groups that need to work in a fully automated manner. It appears that Twitter's OAuth2 implementation can be automated without a GUI logon. It appears that other Bing services (eg Translation) can also be automated with WebClient.
I have the Microsoft account name and password already, and have a callback URL of "local-mydomain.com" set in the Bing Ads app GUI (and have a HOSTS entry for local-mydomain.com).
The Microsoft sample appears to work, but it automates the MS Web Browser Control, expects a user to input credentials in the GUI, and then the token is given. Giving the super admin account to users to do this is not an option. Expecting a user to get up at 3:00am to authenticate to upload/download data is not an option. Expecting a user to get desktop access to a server in the farm to "run something" is not an option.
All OAuth ideas appreciated.
Thanks.
Here is the launching code:
partial class OAuthForm : Form
{
private static OAuthForm _form;
private static WebBrowser _browser;
private static string _code;
private static string _error;
// When you register your application, the Client ID is provisioned.
private const string ClientId = "000redacted000";
// Request-related URIs that you use to get an authorization code,
// access token, and refresh token.
private const string AuthorizeUri = "https://login.live.com/oauth20_authorize.srf";
private const string TokenUri = "https://login.live.com/oauth20_token.srf";
private const string DesktopUri = "https://login.live.com/oauth20_desktop.srf";
private const string RedirectPath = "/oauth20_desktop.srf";
private const string ConsentUriFormatter = "{0}?client_id={1}&scope=bingads.manage&response_type=code&redirect_uri={2}";
private const string AccessUriFormatter = "{0}?client_id={1}&code={2}&grant_type=authorization_code&redirect_uri={3}";
private const string RefreshUriFormatter = "{0}?client_id={1}&grant_type=refresh_token&redirect_uri={2}&refresh_token={3}";
// Constructor
public OAuthForm(string uri)
{
InitializeForm(uri);
}
[STAThread]
static void Main()
{
// Create the URI to get user consent. Returns the authorization
// code that is used to get an access token and refresh token.
var uri = string.Format(ConsentUriFormatter, AuthorizeUri, ClientId, DesktopUri);
_form = new OAuthForm(uri);
// The value for "uri" is
// https://login.live.com/oauth20_authorize.srf?client_id=000redacted000&scope=bingads.manage&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf
_form.FormClosing += form_FormClosing;
_form.Size = new Size(420, 580);
Application.EnableVisualStyles();
// Launch the form and make an initial request for user consent.
// For example POST /oauth20_authorize.srf?
// client_id=<ClientId>
// &scope=bingads.manage
// &response_type=code
// &redirect_uri=https://login.live.com/oauth20_desktop.srf HTTP/1.1
Application.Run(_form); // <!---------- Problem is here.
// I do not want a web browser window to show,
// I need to automate the part of the process where
// a user enters their name/password and are
// redirected.
// While the application is running, browser_Navigated filters traffic to identify
// the redirect URI. The redirect's query string will contain either the authorization
// code if the user consented or an error if the user declined.
// For example https://login.live.com/oauth20_desktop.srf?code=<code>
// If the user did not give consent or the application was
// not registered, the authorization code will be null.
if (string.IsNullOrEmpty(_code))
{
Console.WriteLine(_error);
return;
}
Whatever you do, the "super admin" will have to log on at least once, using a browser. You can do that by hosting a simple web page in your service, or you could do it as part of the setup process. The Live samples show you how to do that.
Once the "super admin" has logged on using the code grant, you receive an access token and a refresh token. I'm not sure how long the Live access token is valid, but it is probably log enough for one nightly run. Save the refresh token in a safe place. The following night, you start by exchanging that refresh token by a new access token and a new refresh token. Again, you save this new refresh token for the following night.
You can keep this process running forever, as long as the "super admin" does not revoke the authorization he gave to your app.
UPDATE:
Some OAuth 2.0 servers support the "Resource Owner Password Credentials Grant", see the RFC at https://www.rfc-editor.org/rfc/rfc6749. If the Live server supports that, it would be an alternative to the Code Grant that does not require a browser. However, even of the server supports it, I would recommend against it for security reasons, as it requires storing your "super admin" password on the server. If someone grabs the password, they have full access to the account, and all resources protected by it. It will also break down if you change the password. The code grant does not have these problems.
Your question states that you want or need to run as this "super admin". An other option might be to use the "Client Credentials Grant". However, this also requires a client secret to be stored on the server (as with the password credentials grant). Furthermore, it still requires the super admin to authorize the client, and that in itself requires a code grant using a browser.
You ask why the code grant requires a browser, why you can not use some kind of screen scraping to simulate a browser interaction. First of all, you cannot predict the screens that will be shown to the user. These screens change without notice. More importantly, depending on user options and history, the server shows different screens. For example, the user may have turned on two-factor authentication. Last but not least, why do you object to opening a browser? It will probably be easier than trying to emulate it.
Finally, these "super admin" users might object to giving their password to your application, as they don't really know what you are doing with it (you might be sending to a server of your own, as far as they know). Using the Code Grant with a browser, they know your application never gets to see their password (kind of - you could listen in on browser events or something, unless the browser control is run in a separate process not under your control, such as the Windows 8 WebAuthenticationBroker). Your application only gets a token with the scopes they authorize.
After spending a few hours on this problem for myself and finding absolutely no solution to automate connecting to Bing from a service. Here is what will work using the wonderful WatiN
First grab WatiN and add it to your solution via Nuget.
Then use the following code (my example works in a console application as an Example) to automate the whole grabbing of a token from Microsoft. It's not perfect as this is a sample but it will work.
You should double check the element ID's I'm using in case they changed, they are hard coded - generally remove all the hard coding if your going to use this in a production environment.
I didn't want anyone else to have to go through this.
First it grabs a Code that is then used to grab a Token, just like the OAuth 2.0 specification requires.
using System;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using WatiN.Core.Native;
using WatiN.Core;
namespace LouiesOAuthCodeGrantFlow
{
// Using access tokens requires that you register your application and that
// the user gives consent to your application to access their data. This
// example uses a form and WebBrowser control to get the user's consent.
// The control and form require a single-threaded apartment.
partial class LouiesBingOAuthAutomation
{
private static LouiesBingOAuthAutomation _form;
private static string _code;
private static string _error;
//your going to want to put these in a secure place this is for the sample
public const string UserName = "your microsoft user name";
public const string Password = "<your microsoft account password";
// When you register your application, the Client ID is provisioned.
//get your clientid https://developers.bingads.microsoft.com/Account
private const string ClientId = "<your client id>";
// Request-related URIs that you use to get an authorization code,
// access token, and refresh token.
private const string AuthorizeUri = "https://login.live.com/oauth20_authorize.srf";
private const string TokenUri = "https://login.live.com/oauth20_token.srf";
private const string DesktopUri = "https://login.live.com/oauth20_desktop.srf";
private const string RedirectPath = "/oauth20_desktop.srf";
private const string ConsentUriFormatter = "{0}?client_id={1}&scope=bingads.manage&response_type=code&redirect_uri={2}";//&displayNone
private const string AccessUriFormatter = "{0}?client_id={1}&code={2}&grant_type=authorization_code&redirect_uri={3}";
private const string RefreshUriFormatter = "{0}?client_id={1}&grant_type=refresh_token&redirect_uri={2}&refresh_token={3}";
// Constructor
public LouiesBingOAuthAutomation(string uri)
{
InitializeForm(uri);
}
[STAThread]
static void Main()
{
var uri = string.Format(ConsentUriFormatter, AuthorizeUri, ClientId, DesktopUri);
_form = new LouiesBingOAuthAutomation(uri);
if (string.IsNullOrEmpty(_code))
{
Console.WriteLine(_error);
return;
}
uri = string.Format(AccessUriFormatter, TokenUri, ClientId, _code, DesktopUri);
AccessTokens tokens = GetAccessTokens(uri);
Console.WriteLine("Access token expires in {0} minutes: ", tokens.ExpiresIn / 60);
Console.WriteLine("\nAccess token: " + tokens.AccessToken);
Console.WriteLine("\nRefresh token: " + tokens.RefreshToken);
uri = string.Format(RefreshUriFormatter, TokenUri, ClientId, DesktopUri, tokens.RefreshToken);
tokens = GetAccessTokens(uri);
Console.WriteLine("Access token expires in {0} minutes: ", tokens.ExpiresIn / 60);
Console.WriteLine("\nAccess token: " + tokens.AccessToken);
Console.WriteLine("\nRefresh token: " + tokens.RefreshToken);
}
private void InitializeForm(string uri)
{
using (var browser = new IE(uri))
{
var page = browser.Page<MyPage>();
page.PasswordField.TypeText(Password);
try
{
StringBuilder js = new StringBuilder();
js.Append(#"var myTextField = document.getElementById('i0116');");
js.Append(#"myTextField.setAttribute('value', '"+ UserName + "');");
browser.RunScript(js.ToString());
var field = browser.ElementOfType<TextFieldExtended>("i0116");
field.TypeText(UserName);
}
catch (Exception ex)
{
Console.Write(ex.Message + ex.StackTrace);
}
page.LoginButton.Click();
browser.WaitForComplete();
browser.Button(Find.ById("idBtn_Accept")).Click();
var len = browser.Url.Length - 43;
string query = browser.Url.Substring(43, len);
if (query.Length == 50)
{
if (!string.IsNullOrEmpty(query))
{
Dictionary<string, string> parameters = ParseQueryString(query, new[] { '&', '?' });
if (parameters.ContainsKey("code"))
{
_code = parameters["code"];
}
else
{
_error = Uri.UnescapeDataString(parameters["error_description"]);
}
}
}
}
}
// Parses the URI query string. The query string contains a list of name-value pairs
// following the '?'. Each name-value pair is separated by an '&'.
private static Dictionary<string, string> ParseQueryString(string query, char[] delimiters)
{
var parameters = new Dictionary<string, string>();
string[] pairs = query.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
foreach (string pair in pairs)
{
string[] nameValue = pair.Split(new[] { '=' });
parameters.Add(nameValue[0], nameValue[1]);
}
return parameters;
}
// Gets an access token. Returns the access token, access token
// expiration, and refresh token.
private static AccessTokens GetAccessTokens(string uri)
{
var responseSerializer = new DataContractJsonSerializer(typeof(AccessTokens));
AccessTokens tokenResponse = null;
try
{
var realUri = new Uri(uri, UriKind.Absolute);
var addy = realUri.AbsoluteUri.Substring(0, realUri.AbsoluteUri.Length - realUri.Query.Length);
var request = (HttpWebRequest)WebRequest.Create(addy);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (var writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(realUri.Query.Substring(1));
}
var response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
tokenResponse = (AccessTokens)responseSerializer.ReadObject(responseStream);
}
}
catch (WebException e)
{
var response = (HttpWebResponse)e.Response;
Console.WriteLine("HTTP status code: " + response.StatusCode);
}
return tokenResponse;
}
}
public class MyPage : WatiN.Core.Page
{
public TextField PasswordField
{
get { return Document.TextField(Find.ByName("passwd")); }
}
public WatiN.Core.Button LoginButton
{
get { return Document.Button(Find.ById("idSIButton9")); }
}
}
[ElementTag("input", InputType = "text", Index = 0)]
[ElementTag("input", InputType = "password", Index = 1)]
[ElementTag("input", InputType = "textarea", Index = 2)]
[ElementTag("input", InputType = "hidden", Index = 3)]
[ElementTag("textarea", Index = 4)]
[ElementTag("input", InputType = "email", Index = 5)]
[ElementTag("input", InputType = "url", Index = 6)]
[ElementTag("input", InputType = "number", Index = 7)]
[ElementTag("input", InputType = "range", Index = 8)]
[ElementTag("input", InputType = "search", Index = 9)]
[ElementTag("input", InputType = "color", Index = 10)]
public class TextFieldExtended : TextField
{
public TextFieldExtended(DomContainer domContainer, INativeElement element)
: base(domContainer, element)
{
}
public TextFieldExtended(DomContainer domContainer, ElementFinder finder)
: base(domContainer, finder)
{
}
public static void Register()
{
Type typeToRegister = typeof(TextFieldExtended);
ElementFactory.RegisterElementType(typeToRegister);
}
}
// The grant flow returns more fields than captured in this sample.
// Additional fields are not relevant for calling Bing Ads APIs or refreshing the token.
[DataContract]
class AccessTokens
{
[DataMember]
// Indicates the duration in seconds until the access token will expire.
internal int expires_in = 0;
[DataMember]
// When calling Bing Ads service operations, the access token is used as
// the AuthenticationToken header element.
internal string access_token = null;
[DataMember]
// May be used to get a new access token with a fresh expiration duration.
internal string refresh_token = null;
public string AccessToken { get { return access_token; } }
public int ExpiresIn { get { return expires_in; } }
public string RefreshToken { get { return refresh_token; } }
}
}
I have several WCF services hosted in IIS6 (should not affect this issue) on the same host, and I want, for Performance/ Maintanance and other reasons to combine several requests into 1 request using a Facade Service,
All done with special Service Contract / Service that has an operation that calls other services for several operations.
I'm using WSHTTP (probably BasicHttp in the near future) with Message security and UserName client credential type.
I want the Facade Service to use the credentials from the client. Meaning the call to the back-end service will get the credentials as if the client would call it directly.
For example:
Client calls FacadeService.CompositeOperation with UserName "A" and password "B".
Now the FacadeService.CompositeOperation needs to call BackEndService.BackendOperation setting the Credentials.UserName.UserName to "A" and Credentials.UserName.Password to "B" just like what the client done when calling to this operation. I have no way to extract this information in WCF (and it should be, because it is sensitive information) but i neither found a way to take "a token" of these and pass it forward to the backend service (I have no need to know this information in the FacadeService, only to pass them over).
In FacadeService, as in BackEndService, the authentication is made through ASP.NET provider, the authorization is a custom Role-based authorization taking the UserName from the PrimaryIdentity, so the PrimaryIdentity on the BackEndService should be set to what the client send.
How should i do it?
I read your post yesterday but wasn't sure of an answer, but seeing as you've had no replies i thought i'd add something and maybe provide some food for thought.
Firstly, would making the additonal service calls be overly intensive on resources? If not, there is an argument for code clarity, to seperate them out so in the future developers will know exactly what's happening rather than 1 service call performing multiple operations.
Are you not able to make calls to other services from your server side code from within the method you're hitting? As once, you're server side, the security context should hold the identity of the user that you're after so calls to other services would use the same identity.
Finally, I was wondering whether WCF Impersonation (MSDN LINK) might be something you can use on the server to achieve what you're after. I've not used it myself so can't advise as much as i'd like.
Hope that's of some help - good luck!
Once i tried to Store Password along with UserName in PrimaryIdentity.
To achieve this What we need to do is to provide a New UserNameSecurityTokenAuthenticator Which will authenticate UserName and Password and then can store in the Identity and then it will Store the Identity in SecurityContext of WCF.
Steps to Do
Classes
1.) TestServiceHost : ServiceHost
2.) UserNamePasswordSecurityTokenManager : ServiceCredentialsSecurityTokenManager
3.) TestUserNameSecurityTokenAuthenticator : UserNameSecurityTokenAuthenticator
4.) MyIdentity : IIdentity
5.) MyAuthorizatoinPolicy : IAuthorizationPolicy
1.) Create New ServiceHost class TestServiceHost
2.) In TestServiceHost Override OnOpening and provide a new Class UserNamePasswordServiceCredentials
protected override void OnOpening()
{
base.OnOpening();
this.Description.Behaviors.Add(new UserNamePasswordServiceCredentials());
}
3.) Then in UserNamePasswordServiceCredentials, provide new UserNamePasswordSecurityTokenManager
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new UserNamePasswordSecurityTokenManager(this);
}
4.) Then in UserNamePasswordSecurityTokenManager provide new TestUserNameSecurityTokenAuthenticator
public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
{
if (tokenRequirement.TokenType == SecurityTokenTypes.UserName)
{
outOfBandTokenResolver = null;
return new TestUserNameSecurityTokenAuthenticator();
}
return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
}
5.) Then Inside TestUserNameSecurityTokenAuthenticator you can Authenticate UseraName and Password and can create your own Identity. In this function you will return a list of IAuthorization policies to be evaluated. I created my own authorization Policy and passed my new identity to it, so as to set the Identity in context.
protected override System.Collections.ObjectModel.ReadOnlyCollection<System.IdentityModel.Policy.IAuthorizationPolicy> ValidateUserNamePasswordCore(string userName, string password)
{
ClaimSet claimSet = new DefaultClaimSet(ClaimSet.System, new Claim(ClaimTypes.Name, userName, Rights.PossessProperty));
List<IIdentity> identities = new List<IIdentity>(1);
identities.Add(new MyIdentity(userName,password));
List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>(1);
policies.Add(new MyAuthorizationPolicy(ClaimSet.System, identities));
return policies.AsReadOnly();
}
public class MyAuthorizationPolicy : IAuthorizationPolicy
{
String id = Guid.NewGuid().ToString();
ClaimSet issuer;
private IList<IIdentity> identities;
#region IAuthorizationPolicy Members
public MyAuthorizationPolicy(ClaimSet issuer, IList<IIdentity> identities)
{
if (issuer == null)
throw new ArgumentNullException("issuer");
this.issuer = issuer;
this.identities = identities;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
if (this.identities != null)
{
object value;
IList<IIdentity> contextIdentities;
if (!evaluationContext.Properties.TryGetValue("Identities", out value))
{
contextIdentities = new List<IIdentity>(this.identities.Count);
evaluationContext.Properties.Add("Identities", contextIdentities);
}
else
{
contextIdentities = value as IList<IIdentity>;
}
foreach (IIdentity identity in this.identities)
{
contextIdentities.Add(identity);
}
}
return true;
}
public ClaimSet Issuer
{
get { return this.issuer; }
}
#endregion
#region IAuthorizationComponent Members
public string Id
{
get { return this.id; }
}
#endregion
}
So this example shows how you can override Security in WCF:
Now in your problem:
1.) Implement this Technique and Set UserName and Password in your identity. Now when ever you have call child service, get Identity extract Username and password from it and pass on to child service.
2.) Authenticate UserName and Password and generate a token for that (should create a new token service for that). Save this Token in your Identity along with Username and pass these two to your child services. Now for this approach to work, child service has to validate your new generated token, for which you should have a token Service which can create token by validating username and password and also which can validate token along with username.
Personally I would go for approach 2, but it will introduce new overheads.