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
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)
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
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.