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.
Related
I am working on a cross platform web app using angular and webapi. The problem is when the angular app runs in a cordova container. To play nice with the rest of the applications on the device, I am required to use a plugin for SSO.. This plugin is what is causing me issues, because it does a few things. It intercepts all the http requests and adds a bearer token to the header, which is generated by a 3rd party Token provider, so I can't decode it, and overwrites any bearer token I have set in the header.It also seems to block cookies..
So it makes it a bit tricky when you can't send you own local credentials.
So I started with https://coding.abel.nu/2014/06/writing-an-owin-authentication-middleware/ and http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.OAuth/OAuthBearerAuthenticationHandler.cs
So I figured I should write my own middleware to take care of this; I thought since the standard oauth middleware can work without cookies, I should not have too hard a time getting my slightly different bearer token middleware to do it.. But that has not been the case... Writing my own middleware.. so I'm able to get the header, validate with the external token provider, but I can't actually sign in.
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
try
{
// Find token in default location
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorization))
{
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
}
}
.... Take the Request token call other Server, verify token...
Also
public override async Task<bool> InvokeAsync()
{
var ticket = await this.AuthenticateAsync();
if(ticket != null)
{
this.Context.Authentication.SignIn(new AuthenticationProperties(), grantIdentity);
return false;
}
}
So in the end the SignIn does not cause a error or anything, but does not actually signin. As soon as I get to a controller action with an [Authorize] attribute, I get a 401. I not have any external cookies enabled. There is a high probability that I am on the wrong track or I am making it way too hard.
You are doing it way too hard.
Instead of creating your own bearer authentication middleware you should change the default OAuthBearerAuthenticationProvider.
Here is a sample for sending the token in the query string.
//in Startup class
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new QueryStringOAuthBearerProvider(),
//your settings
});
//implementation
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
private const string AccessTokenQueryKey = "access_token";
public override Task RequestToken(OAuthRequestTokenContext context)
{
//check if token found in the default location - "Authorization: Bearer <token>" header
if (string.IsNullOrEmpty(context.Token))
{
var token = context.Request.Query.Get(AccessTokenQueryKey);
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
}
return Task.FromResult<object>(null);
}
}
So… I ment to answer it earlier, but I was able to figure it out, without override the authorize attribute. I ended up looking at the source for the OWIN security code. The trick is, you really need 2 OWIN middleware components. One is the what I call (and I stole this from the owin source) the server middleware. The server middleware responds to the challenge and/or if you are feeling crazy generate local credentials for you. This middleware is also a PASSIVE middleware component. I won’t get in to generating the local credentials unless someone asks , because it’s a bit off point, but if someone thinks it will be helpful, I can update.
public class LowCalorieAuthenticationServerHandler : AuthenticationHandler<LowCalorieAuthenticationServerOptions>
{
//Important this needs to be overriden, but just calls the base.
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
return Task.FromResult<AuthenticationTicket>(null);
}
/// <summary>The apply response challenge async.</summary>
/// <returns>The <see cref="Task"/>.</returns>
protected override async Task ApplyResponseChallengeAsync()
{
if (this.Response.StatusCode != 401)
{
Task.FromResult<object>(null);
return;
}
var challenge = this.Helper.LookupChallenge(
this.Options.AuthenticationType,
this.Options.AuthenticationMode);
if (challenge != null)
{
//OK in here you call the rediret to the 3rd party
//return a redirect to some endpoint
}
Task.FromResult<object>(null);
return;
}
}
Anyway notice how the override AuthenticateCoreAsync() just returns
return Task.FromResult(null);
This is because we don’t want this middleware to modify the request. ApplyResponseChallengeAsync will wait for a Challenge and redirect you to the 3rd party login. IF you want to create a local token of some sort you would override the InvokeAsync method
The second middle ware you need is the token/external credentials validator. This will then authenticate the user somehow. In the case of the local bearer token that is built into the OWIN security, it simple deserializes the token and if it can, and the token is not expired it authenticates the user. So in the case that you want to verify the token with a 3rd part sso, such as google or anything, you insert you logic here. In my case I not only wanted to call the 3rd party provider to get the user info, but to check if they token was still valid for single sign out, and to prevent multiple sessions.
public class LowCalorieAuthenticationHandler : AuthenticationHandler<LowCalorieAuthenticationOptions>
{
//Going to give you the user for the request.. You Need to do 3 things here
//1. Get the user claim from teh request somehow, either froma header, request string, or cookie what ever you want
//2. validate the user with whatever user store or 3rd party SSO you want
//3. Generate a AuthenticationTicket to send to on to the request, you can use that to see if the user is valid in any Identity collection you want.
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
//Good to throw in a point of override here.. but to keep it simple-ish
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
//TOTAL FAKEOUT.. I am going to add a bearer token just so the simple sample works, but your client would have to provide this
authorization = "Bearer 1234567869";
//STEP 1
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
return await FakeExternalBearer(requestToken);
}
return null;
}
private async Task<AuthenticationTicket> FakeExternalBearer(string token)
{
var authenticationType = Options.AuthenticationType;
//pretend to call extenal Resource server to get user //STEP 2
//CallExternal(token)
//Create the AuthTicket from the return.. I will fake it out
var identity = new ClaimsIdentity(
authenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier,"user1", null, authenticationType));
identity.AddClaim(new Claim(ClaimTypes.Name, "Jon",null, authenticationType));
var properties = new AuthenticationProperties();
properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(1);
properties.IssuedUtc = DateTime.UtcNow;
var ticket = new AuthenticationTicket(identity, properties);
return ticket;
}
}
Ok here we override AuthenticateCoreAsync, but we actually do something now. This this were your do you user authentication. This is the ACTIVE part of the middleware. Note it needs to return a valid AuthenticationTicket. This will run on each request so be careful what you call and how often.
So I have a very simple example here https://github.com/jzoss/LowCalorieOwin If anyone is interested in more detail, please ask. I can add more. I did make it too hard, because now that I understand it, it’s pretty easy, but there is really no good examples on how to do this.
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.
I created several POCO then created a DbContext (FooDbContext) - I then created a DataService class device from DataService< FooDbContext > calll FooDatService. I can access all my data in my silverlight app and if I start a Web Browser I can access it through the URL as expected. Now I want to allow to the DataService only after a successful login.
I've blogged on that like 3 years ago
http://netpl.blogspot.com/2010/04/aspnet-forms-authentication-sharing-for.html
The idea is to reuse the forms cookie to guard your invocations so that only logged in users are allowed to call the service.
You can add a service authorization manager to your WCF service to put all methods and endpoints of that service under access control, without modifying any of the implementation of the service.
Creating and starting your WCF service:
Uri[] restUris = new Uri[] { new Uri(baseUri, "Api/v1/") };
// substitute your service host type here. I'm using WCF OData DataServiceHost
restAPIServiceHost = new DataServiceHost(typeof(API.RestAPIService), restUris);
var saz = restAPIServiceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
if (saz == null)
{
saz = new ServiceAuthorizationBehavior();
restAPIServiceHost.Description.Behaviors.Add(saz);
}
saz.ServiceAuthorizationManager = new MyServiceAuthorizationManager();
restAPIServiceHost.Open();
The above can also be done via web.config magic.
In your MyServiceAuthorizationManager implementation:
public class MyServiceAuthorizationManager: System.ServiceModel.ServiceAuthorizationManager
{
public override bool CheckAccess(OperationContext operationContext, ref Message message)
{
var reqProp = message.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
var authHeader = new AuthorizationHeader(reqProp.Headers[HttpRequestHeader.Authorization]);
bool authorized = // your code to decide if caller is authorized;
if (!authorized)
{
var webContext = new WebOperationContext(operationContext);
webContext.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
// optional: give caller hints where to go to login
webContext.OutgoingResponse.Headers.Add( HttpResponseHeader.WwwAuthenticate, String.Format("Bearer realm=\"{0}\"", baseUri.AbsoluteUri));
}
return authorized;
}
}
This CheckAccess method will be called for every request received by your WCF service, before the request is dispatched to the WCF implementation methods.
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. :)
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.