Let's keep this very simple :-) My REST style Web API has methods which require authorization (and therefore authentication to begin with) and others don't.
I need to authenticate against an existing ASP membership provider. For that purpose I planned on using basic http and SSL for simplicity reasons. This way I can secure all the methods that need authorization.
Now I need a way to restrict access to all the other methods that don't require a user authorization. I thought of an API key. Basically some customer dependent string value I add to the query string. /some/action?key=12345
See http://www.infoq.com/news/2010/01/rest-api-authentication-schemes --> #2
And here come the questions..
First of all I DO NOT want to send the actual shared key over the wire because that way it is publicly visible.
So I thought I'd better off with some kind of (H)MAC. The idea is, that both sender and receiver (the service) have a shared secret key which is never going over the wire but is used for building the hash that is send to the server.
Now my questions is... given the fact that my "client" is a HTML5 mobile application and using jquery do talk to my REST API - how am I supposed to implement HMAC properly? I'd need to build the hash using javascript and therefore the shared secret would be
known to javascript and therefore not really be a secret anymore at all.
So somebody could simply open up his or her firebug and step throught the javascript to get the shared secret and use it to build his or her own application using the other customer's shared key.
Edit: Someone able to read javascript would also see what the hash contains (e.g. which http headers etc.). Although this doesn't expose a great risk it's still something that makes me hesitate.
I understand where you are coming from; I've had a similar problem as to how to secure your ASP.NET Web API before. Hopefully this helps you.
Ok, first thing's first: I assume that you already have a javascript library for HMAC-ing your strings (if not, I suggest you take a look at some, specifically CryptoJS, it already has SHA-3 implemented by the way
).
Next step is that you will want to have something called an API key. This is a key that you assign to your client's app (in your case, your HTML5 app with jQuery). The purpose of an API key is so that you can easily identify what app your
user was using to access your API and if it was an app approved by you (technically, if an app has an assigned API key, that means you approve of it); you can assign whatever value you want, just make sure that your app has an assigned key which you [and your
code] can look up.
Ok, the next steps are the tricky ones, so bear with me as I try to explain things.
Once you assign an API key to the app, that means you trust it, and the user trusts it. So when a user logs in, he inputs a username-password combination. So go ahead, use CryptoJS (or whatever library you would like to calculate HMAC) and
hash the password using HMAC, with the key being the user's password. In short, hash the password using the password. Let's give this calculated value the variable name "hmacPassword".
Next, your app should have the client key at hand. using hmacPassword, hash the client key using HMAC as well. Let's call this
hmacClientKey.
Next, let's join the username, hmacPassword and hmacClientKey into one string so that it can be sent as one string to your API. You should have a format similar to this (the colon acts as a separator so you would recognize which parameter is which):
username:apiKey=apiKey:H{pwd=password}
where
H{pwd=password} equals the hashed password.
Now, send this to your API. Remember, the user is logging in
at this point.
At this point, we are in server-side code. In your API, you should have a class that intercepts all incoming requests. In that class, you will receive the request of your user with the username, hashed password and hashed client key. Parse the username from
the data that you just passed in from the client and look up his credentials; this contains his password and a list of client apps that he is allowed to use (if not all). Now,
redo the hashing in the server side. Calculate the variables. If they match, that means that the user typed in the correct password.
Now, here's the tricky part - subsequent calls after logging in. We want to protect your user so that his password is not hashed everytime he makes a request. Enter
nOnce. This is a cryptographically strong key that you store in the database
with a time limit and pass it back to the client as a response. The RNGCryptoServiceProvider will be able to help you out in making an nOnce. In your class, you should handle as well subsequent calls.
The format will change. It will become:
where
H{nOnce=nOnce} equals the hashed nOnce and H{data=hashedRequestUri} equals the hashed request Uri.
On subsequent calls, calculate these. If they match, these are legitimate requests.
Here's some code to help you out:
#region Serialization Format
// On login: username:apiKey=apiKey:H{pwd=password}
// After login: username:apiKey=apiKey:H{nOnce=nOnce}:H{data=hashedRequestUri}
#endregion
/// <summary>
/// Provides a base implementation for securing a web api by implementing a custom authentication scheme.
/// </summary>
public class ApiHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
string nOnceString = null;
// Step 1: Check that the scheme and the parameter is not null
if (request.Headers.Authorization != null)
{
if (request.Headers.Authorization.Scheme.ToLowerInvariant() == "supertyphoon" && !string.IsNullOrWhiteSpace(request.Headers.Authorization.Parameter))
{
// Step 2: Examine the request; save the parameter as an array and get the username.
var credentials = request.Headers.Authorization.Parameter.Split(':');
// We know that in the scheme (check "Serialization Format" region above), the username is the leftmost
// and first part of the string and has the 0th index. It is not hashed, so we can grab it and check
// that user's credentials in the data store.
var username = credentials[0];
// Step 3: Check that the user and the api key exists in the database. If they exist, fetch them.
User user;
string apiKey = null;
// Split the raw key so that the value (the string after the "=" sign is passed)
var rawKey = credentials[1].Split('=');
string key = rawKey[1];
using (var context = new MoviesContext())
{
user = context.Users.FirstOrDefault(p => p.Username == username);
// Check that the key exists before assigning to the variable.
var keyChecker = context.ClientApis.FirstOrDefault(p => p.ApiKey == key);
if (keyChecker != null)
apiKey = keyChecker.ApiKey;
}
if (user != null && apiKey != null)
{
// Step 4: Valid user exists. Find out the parameters stored in credentials.
// If the credentials has a valid nOnce, we know that the user has ALREADY LOGGED IN.
// If no nOnce, then the user is LOGGING IN.
// We will be able to know that the nOnce is provided by checking the number of segments
// in the credentials. If there's 3, there's no nOnce. If there's 4, there's an nOnce,
// as specified in the "Serialization Format" above.
switch (credentials.Length)
{
case 3:
// User is logging in.
// compute the hash of the data and prepend the username.
var finalData = string.Format("{0}:apiKey={1}:pwd={2}",username, apiKey, user.Password);
// Now, compare the computed data with the authorization value which was sent
// in the authorization header
if (String.CompareOrdinal(finalData, request.Headers.Authorization.Parameter) == 0)
{
// user is legit. Let the process continue and assign an nOnce to the user
using (var context = new MoviesContext())
{
var newNOnce = new NOnce
{
IssuedOn = DateTime.UtcNow,
Api =
new ClientApi
{ApiKey = apiKey, ClientType = "Website"}
};
var generator = new RNGCryptoServiceProvider();
var nOnceValue = new byte[512];
generator.GetBytes(nOnceValue);
newNOnce.NOnceValue = Convert.ToBase64String(nOnceValue);
nOnceString = newNOnce.NOnceValue;
context.NOnces.Add(newNOnce);
context.SaveChanges();
}
}
else
{
// user is not legit. Throw an error.
var message = request.CreateErrorResponse(HttpStatusCode.BadRequest,
"Invalid message.");
throw new HttpResponseException(message);
}
break;
case 4:
// User is making subsequent calls after logging in.
// Get the set of NOnces that the user has; it is possible for a user to have multiple
// NOnces if the user is logging in multiple clients
List<NOnce> availablenOnces;
using (var context = new MoviesContext())
{
availablenOnces = context.NOnces.Where(p => p.Api.ApiKey == apiKey && p.HasExpired == false).ToList();
}
// check that there is at least one available NOnce
if (!availablenOnces.Any())
{
// No NOnces available; user hasn't logged in. Throw an error
var message = request.CreateErrorResponse(HttpStatusCode.BadRequest,
"User does not have any valid NOnce.");
throw new HttpResponseException(message);
}
// If there is at least one available NOnce in the collection, loop through them
foreach (var thisNOnce in availablenOnces)
{
//username:apiKey=apiKey:H{nOnce=nOnce}:H{data=hashedRequestUri}
var computedHashData = string.Format("{0}:apiKey={1}:{2}:{3}", username,apiKey, "nOnce=" + thisNOnce.NOnceValue, "data=" + request.RequestUri);
// Get the hashed parameter
if (String.CompareOrdinal(computedHashData, request.Headers.Authorization.Parameter) == 0)
{
// user is legit. Let the process continue and assign a new nOnce to the user
using (var context = new MoviesContext())
{
var newNOnce = new NOnce
{
IssuedOn = DateTime.UtcNow,
Api =
new ClientApi { ApiKey = apiKey, ClientType = "Website" }
};
var generator = new RNGCryptoServiceProvider();
var nOnceValue = new byte[512];
generator.GetBytes(nOnceValue);
newNOnce.NOnceValue = Convert.ToBase64String(nOnceValue);
nOnceString = newNOnce.NOnceValue;
context.NOnces.Add(newNOnce);
context.SaveChanges();
}
}
else
{
// user is not legit. Throw an error.
var message = request.CreateErrorResponse(HttpStatusCode.BadRequest,
"Invalid message.");
throw new HttpResponseException(message);
}
}
break;
default:
{
// Not valid number of sequences. Throw an error.
}
break;
}
}
else
{
// user does not exist. Throw an exception
var message = request.CreateErrorResponse(HttpStatusCode.Unauthorized,
"Unknown user or user does not exist.");
throw new HttpResponseException(message);
}
}
else
{
var message = request.CreateErrorResponse(HttpStatusCode.Forbidden,
"Invalid, unknown or empty scheme.");
throw new HttpResponseException(message);
}
}
var returnThis = base.SendAsync(request, cancellationToken).Result;
returnThis.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("supertyphoon", nOnceString));
return Task<HttpResponseMessage>.Factory.StartNew(() => returnThis);
}
}
}
Thank you for your efforts. I had very similiar ideas :)
However three questions remain...
1. Using the API key to hash the user's password exposes the API key to javascript - which kind of makes it publicly visible on the client side. But ok... I guess there's always a way to retrieve the API key out of an application.
2. What about anonymous calls? I'd completely have to rely on the API key to build my HMAC on right? It's the only shared secret.
3. Most importantly... to verify the signature on the server side at some point I need the user's password, right? Or did I get it all wrong? The problem is, the user's password is usually not exactly stored as plain text. Furthermore I would like to use
my existing MembershipProvider infrastructure. A MembershipProvider has a method GetPassword() but a call to that method should result in an exception because usually you cannot "get" passwords because they are encrypted - and for verification only hashes
are compared (same thing with hmac).
1. It's OK for the API key to be made public. The API key serves two purposes:
Act as a piece of data that can be used to distinguish the app that your client used to access data, and
Act as "dummy data" which will be used for hashing.
What is NOT OK is if an attacker knows the API key AND what the user input as the password.
2. For anonymous calls, you can go two ways.
The first is that in your class, filter out the request being made by
extracting the controller and the action being called and determine whether or not it is safe to be called anonymously (take a look at
this webpage to give you an idea of how you might do it: ). If it is safe to call it, let the request continue.
The second one is to implement what is known as Authorization Filters. The concept is that the decision to let the request pass is taken one extensibility level down the request pipeline and then from there, you control whether or not to
let it proceed. (For your reference, you can download the Web API HTTP Message Lifecycle from here; notice that the Delegating Handler is the first extensibility point, then Authorization
Filters.)
3. It's good that the password is not stored in plaintext nor should it be for that matter
. Nope, you don't need the user's password (just to be clear, by
password I mean what is typed in, not what is already modified by code). What you need is to
reproduce that hash client side, and using that hash, HMAC-ify the data I described in the Serialization Format.
Given that you have a MembershipProvider, is it possible to retrieve the hashed password from the database?
1. I agree with you that as long as any attacker only knows the API Key he can't do much harm. It was my primary goal to use an API key to authenticate clients ("apps", not persons) (e.g. allow anonymous calls by authorized clients). So I'd still be inclined
to keep the API key close to my chest (or more practical: burried somewhere deep down within the client).
2. I know about Filters and DelegatingHandlers. To implement HMAC I'm using a DelegatingHandler. The problem is not the lifecycle of a Web API message but rather the strategy to (for example) limit the number of anonymous requests. I don't want my public
and anonymous methods to be consumed by 3rd party applications. So it's ok to have Handlers and Filters... but I need them to do something incredibly clever :) I know Twitter (or Facebook) has some HTTP Rate Limiting in place... maybe that's something I can
look into, but it doesn't sound like the right thing to do. I doesn't solve the problem, it minimizes it at best.
3. The passwords I was refering to live inside an ldap directory. To my regret this ldap solution seems to be a high security prison for data :P The passwords are somehow protected with special certificates and... well nobody knows exactly. At least not
the guys who are responsible :) They couldn't tell me. I'm not sure if I can get the hashed passwords out of there somehow - but even if I could... I'd still need to know how exactly the password was hashed, meaning: which algorithm, which salt, ...otherwise
I cannot create the exact same hash on the client side. And that's what you were saying right?
That doesn't sound like a good idea either. The "clients" I am refering to are usually web applications. These applications use JavaScript (JQuery really) to talk to my REST service. So I'd need to bother CryptoJS or something to take my password, use the
exact same salt and algorithm as I have on my password storage system and generate the correct hash. That's a lot of sensitive information I'm exposing there...
Edit: Thanks for the hint with the posters... I needed some new posters to decorate my work space :>
On number 2, Ok I didn't know that you wanted to limit the usage; I thought that it would be something similar to a public-facing website where unlimited requests can pour in. My mistake.
On number 3, yep that is exactly what I'm saying... and I do hope that you find they keys to that high security prison, so to speak.
1. I think it's a bad design. I wouldn't want to publish neither algorithm nor salt at any cost.
Twitter for example
just reset the passwords of all compromised accounts although the attacker only got their hands on hashed passwords.
2. I do infact have a public facing REST API. And I do *not* want to limit usage, I just want to prevent unauthorized apps/users to use it.
E.g. to prevent dDoS scenarios.
lapsus
0 Points
26 Posts
Securing your public API...
Jan 16, 2013 06:54 PM|LINK
Let's keep this very simple :-) My REST style Web API has methods which require authorization (and therefore authentication to begin with) and others don't.
I need to authenticate against an existing ASP membership provider. For that purpose I planned on using basic http and SSL for simplicity reasons. This way I can secure all the methods that need authorization.
Now I need a way to restrict access to all the other methods that don't require a user authorization. I thought of an API key. Basically some customer dependent string value I add to the query string. /some/action?key=12345
See http://www.infoq.com/news/2010/01/rest-api-authentication-schemes --> #2
And here come the questions..
First of all I DO NOT want to send the actual shared key over the wire because that way it is publicly visible.
So I thought I'd better off with some kind of (H)MAC. The idea is, that both sender and receiver (the service) have a shared secret key which is never going over the wire but is used for building the hash that is send to the server.
Now my questions is... given the fact that my "client" is a HTML5 mobile application and using jquery do talk to my REST API - how am I supposed to implement HMAC properly? I'd need to build the hash using javascript and therefore the shared secret would be known to javascript and therefore not really be a secret anymore at all.
So somebody could simply open up his or her firebug and step throught the javascript to get the shared secret and use it to build his or her own application using the other customer's shared key.
Edit: Someone able to read javascript would also see what the hash contains (e.g. which http headers etc.). Although this doesn't expose a great risk it's still something that makes me hesitate.
Am I correct? Any solutions to this?
Melech7
Member
14 Points
20 Posts
Re: Securing your public API...
Jan 21, 2013 01:25 PM|LINK
Hi lapsus,
I understand where you are coming from; I've had a similar problem as to how to secure your ASP.NET Web API before. Hopefully this helps you.
Ok, first thing's first: I assume that you already have a javascript library for HMAC-ing your strings (if not, I suggest you take a look at some, specifically CryptoJS, it already has SHA-3 implemented by the way
).
Next step is that you will want to have something called an API key. This is a key that you assign to your client's app (in your case, your HTML5 app with jQuery). The purpose of an API key is so that you can easily identify what app your user was using to access your API and if it was an app approved by you (technically, if an app has an assigned API key, that means you approve of it); you can assign whatever value you want, just make sure that your app has an assigned key which you [and your code] can look up.
Ok, the next steps are the tricky ones, so bear with me as I try to explain things.
Once you assign an API key to the app, that means you trust it, and the user trusts it. So when a user logs in, he inputs a username-password combination. So go ahead, use CryptoJS (or whatever library you would like to calculate HMAC) and hash the password using HMAC, with the key being the user's password. In short, hash the password using the password. Let's give this calculated value the variable name "hmacPassword".
Next, your app should have the client key at hand. using hmacPassword, hash the client key using HMAC as well. Let's call this hmacClientKey.
Next, let's join the username, hmacPassword and hmacClientKey into one string so that it can be sent as one string to your API. You should have a format similar to this (the colon acts as a separator so you would recognize which parameter is which):
username:apiKey=apiKey:H{pwd=password}
where H{pwd=password} equals the hashed password.
Now, send this to your API. Remember, the user is logging in at this point.
At this point, we are in server-side code. In your API, you should have a class that intercepts all incoming requests. In that class, you will receive the request of your user with the username, hashed password and hashed client key. Parse the username from the data that you just passed in from the client and look up his credentials; this contains his password and a list of client apps that he is allowed to use (if not all). Now, redo the hashing in the server side. Calculate the variables. If they match, that means that the user typed in the correct password.
Now, here's the tricky part - subsequent calls after logging in. We want to protect your user so that his password is not hashed everytime he makes a request. Enter nOnce. This is a cryptographically strong key that you store in the database with a time limit and pass it back to the client as a response. The RNGCryptoServiceProvider will be able to help you out in making an nOnce. In your class, you should handle as well subsequent calls. The format will change. It will become:
username:apiKey=apiKey:H{nOnce=nOnce}:H{data=hashedRequestUri}
where H{nOnce=nOnce} equals the hashed nOnce and H{data=hashedRequestUri} equals the hashed request Uri.
On subsequent calls, calculate these. If they match, these are legitimate requests.
Here's some code to help you out:
#region Serialization Format // On login: username:apiKey=apiKey:H{pwd=password} // After login: username:apiKey=apiKey:H{nOnce=nOnce}:H{data=hashedRequestUri} #endregion /// <summary> /// Provides a base implementation for securing a web api by implementing a custom authentication scheme. /// </summary> public class ApiHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { string nOnceString = null; // Step 1: Check that the scheme and the parameter is not null if (request.Headers.Authorization != null) { if (request.Headers.Authorization.Scheme.ToLowerInvariant() == "supertyphoon" && !string.IsNullOrWhiteSpace(request.Headers.Authorization.Parameter)) { // Step 2: Examine the request; save the parameter as an array and get the username. var credentials = request.Headers.Authorization.Parameter.Split(':'); // We know that in the scheme (check "Serialization Format" region above), the username is the leftmost // and first part of the string and has the 0th index. It is not hashed, so we can grab it and check // that user's credentials in the data store. var username = credentials[0]; // Step 3: Check that the user and the api key exists in the database. If they exist, fetch them. User user; string apiKey = null; // Split the raw key so that the value (the string after the "=" sign is passed) var rawKey = credentials[1].Split('='); string key = rawKey[1]; using (var context = new MoviesContext()) { user = context.Users.FirstOrDefault(p => p.Username == username); // Check that the key exists before assigning to the variable. var keyChecker = context.ClientApis.FirstOrDefault(p => p.ApiKey == key); if (keyChecker != null) apiKey = keyChecker.ApiKey; } if (user != null && apiKey != null) { // Step 4: Valid user exists. Find out the parameters stored in credentials. // If the credentials has a valid nOnce, we know that the user has ALREADY LOGGED IN. // If no nOnce, then the user is LOGGING IN. // We will be able to know that the nOnce is provided by checking the number of segments // in the credentials. If there's 3, there's no nOnce. If there's 4, there's an nOnce, // as specified in the "Serialization Format" above. switch (credentials.Length) { case 3: // User is logging in. // compute the hash of the data and prepend the username. var finalData = string.Format("{0}:apiKey={1}:pwd={2}",username, apiKey, user.Password); // Now, compare the computed data with the authorization value which was sent // in the authorization header if (String.CompareOrdinal(finalData, request.Headers.Authorization.Parameter) == 0) { // user is legit. Let the process continue and assign an nOnce to the user using (var context = new MoviesContext()) { var newNOnce = new NOnce { IssuedOn = DateTime.UtcNow, Api = new ClientApi {ApiKey = apiKey, ClientType = "Website"} }; var generator = new RNGCryptoServiceProvider(); var nOnceValue = new byte[512]; generator.GetBytes(nOnceValue); newNOnce.NOnceValue = Convert.ToBase64String(nOnceValue); nOnceString = newNOnce.NOnceValue; context.NOnces.Add(newNOnce); context.SaveChanges(); } } else { // user is not legit. Throw an error. var message = request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid message."); throw new HttpResponseException(message); } break; case 4: // User is making subsequent calls after logging in. // Get the set of NOnces that the user has; it is possible for a user to have multiple // NOnces if the user is logging in multiple clients List<NOnce> availablenOnces; using (var context = new MoviesContext()) { availablenOnces = context.NOnces.Where(p => p.Api.ApiKey == apiKey && p.HasExpired == false).ToList(); } // check that there is at least one available NOnce if (!availablenOnces.Any()) { // No NOnces available; user hasn't logged in. Throw an error var message = request.CreateErrorResponse(HttpStatusCode.BadRequest, "User does not have any valid NOnce."); throw new HttpResponseException(message); } // If there is at least one available NOnce in the collection, loop through them foreach (var thisNOnce in availablenOnces) { //username:apiKey=apiKey:H{nOnce=nOnce}:H{data=hashedRequestUri} var computedHashData = string.Format("{0}:apiKey={1}:{2}:{3}", username,apiKey, "nOnce=" + thisNOnce.NOnceValue, "data=" + request.RequestUri); // Get the hashed parameter if (String.CompareOrdinal(computedHashData, request.Headers.Authorization.Parameter) == 0) { // user is legit. Let the process continue and assign a new nOnce to the user using (var context = new MoviesContext()) { var newNOnce = new NOnce { IssuedOn = DateTime.UtcNow, Api = new ClientApi { ApiKey = apiKey, ClientType = "Website" } }; var generator = new RNGCryptoServiceProvider(); var nOnceValue = new byte[512]; generator.GetBytes(nOnceValue); newNOnce.NOnceValue = Convert.ToBase64String(nOnceValue); nOnceString = newNOnce.NOnceValue; context.NOnces.Add(newNOnce); context.SaveChanges(); } } else { // user is not legit. Throw an error. var message = request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid message."); throw new HttpResponseException(message); } } break; default: { // Not valid number of sequences. Throw an error. } break; } } else { // user does not exist. Throw an exception var message = request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Unknown user or user does not exist."); throw new HttpResponseException(message); } } else { var message = request.CreateErrorResponse(HttpStatusCode.Forbidden, "Invalid, unknown or empty scheme."); throw new HttpResponseException(message); } } var returnThis = base.SendAsync(request, cancellationToken).Result; returnThis.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("supertyphoon", nOnceString)); return Task<HttpResponseMessage>.Factory.StartNew(() => returnThis); } } }I hope this helps in any way.
lapsus
0 Points
26 Posts
Re: Securing your public API...
Jan 29, 2013 09:00 PM|LINK
Thank you for your efforts. I had very similiar ideas :)
However three questions remain...
1. Using the API key to hash the user's password exposes the API key to javascript - which kind of makes it publicly visible on the client side. But ok... I guess there's always a way to retrieve the API key out of an application.
2. What about anonymous calls? I'd completely have to rely on the API key to build my HMAC on right? It's the only shared secret.
3. Most importantly... to verify the signature on the server side at some point I need the user's password, right? Or did I get it all wrong? The problem is, the user's password is usually not exactly stored as plain text. Furthermore I would like to use my existing MembershipProvider infrastructure. A MembershipProvider has a method GetPassword() but a call to that method should result in an exception because usually you cannot "get" passwords because they are encrypted - and for verification only hashes are compared (same thing with hmac).
Melech7
Member
14 Points
20 Posts
Re: Securing your public API...
Jan 30, 2013 03:36 PM|LINK
Hi lapsus,
Here are my answers to your questions:
1. It's OK for the API key to be made public. The API key serves two purposes:
What is NOT OK is if an attacker knows the API key AND what the user input as the password.
2. For anonymous calls, you can go two ways.
The first is that in your class, filter out the request being made by extracting the controller and the action being called and determine whether or not it is safe to be called anonymously (take a look at this webpage to give you an idea of how you might do it: ). If it is safe to call it, let the request continue.
The second one is to implement what is known as Authorization Filters. The concept is that the decision to let the request pass is taken one extensibility level down the request pipeline and then from there, you control whether or not to let it proceed. (For your reference, you can download the Web API HTTP Message Lifecycle from here; notice that the Delegating Handler is the first extensibility point, then Authorization Filters.)
3. It's good that the password is not stored in plaintext nor should it be for that matter
. Nope, you don't need the user's password (just to be clear, by
password I mean what is typed in, not what is already modified by code). What you need is to
reproduce that hash client side, and using that hash, HMAC-ify the data I described in the Serialization Format.
Given that you have a MembershipProvider, is it possible to retrieve the hashed password from the database?
Hope this helps. :)
lapsus
0 Points
26 Posts
Re: Securing your public API...
Jan 30, 2013 05:43 PM|LINK
First of all thank you for your quick reply :)
1. I agree with you that as long as any attacker only knows the API Key he can't do much harm. It was my primary goal to use an API key to authenticate clients ("apps", not persons) (e.g. allow anonymous calls by authorized clients). So I'd still be inclined to keep the API key close to my chest (or more practical: burried somewhere deep down within the client).
2. I know about Filters and DelegatingHandlers. To implement HMAC I'm using a DelegatingHandler. The problem is not the lifecycle of a Web API message but rather the strategy to (for example) limit the number of anonymous requests. I don't want my public and anonymous methods to be consumed by 3rd party applications. So it's ok to have Handlers and Filters... but I need them to do something incredibly clever :) I know Twitter (or Facebook) has some HTTP Rate Limiting in place... maybe that's something I can look into, but it doesn't sound like the right thing to do. I doesn't solve the problem, it minimizes it at best.
3. The passwords I was refering to live inside an ldap directory. To my regret this ldap solution seems to be a high security prison for data :P The passwords are somehow protected with special certificates and... well nobody knows exactly. At least not the guys who are responsible :) They couldn't tell me. I'm not sure if I can get the hashed passwords out of there somehow - but even if I could... I'd still need to know how exactly the password was hashed, meaning: which algorithm, which salt, ...otherwise I cannot create the exact same hash on the client side. And that's what you were saying right?
That doesn't sound like a good idea either. The "clients" I am refering to are usually web applications. These applications use JavaScript (JQuery really) to talk to my REST service. So I'd need to bother CryptoJS or something to take my password, use the exact same salt and algorithm as I have on my password storage system and generate the correct hash. That's a lot of sensitive information I'm exposing there...
Edit: Thanks for the hint with the posters... I needed some new posters to decorate my work space :>
Melech7
Member
14 Points
20 Posts
Re: Securing your public API...
Feb 01, 2013 04:29 AM|LINK
Hi lapsus,
On number 2, Ok I didn't know that you wanted to limit the usage; I thought that it would be something similar to a public-facing website where unlimited requests can pour in. My mistake.
On number 3, yep that is exactly what I'm saying... and I do hope that you find they keys to that high security prison, so to speak.
lapsus
0 Points
26 Posts
Re: Securing your public API...
Feb 02, 2013 10:13 AM|LINK
1. I think it's a bad design. I wouldn't want to publish neither algorithm nor salt at any cost.
Twitter for example just reset the passwords of all compromised accounts although the attacker only got their hands on hashed passwords.
2. I do infact have a public facing REST API. And I do *not* want to limit usage, I just want to prevent unauthorized apps/users to use it.
E.g. to prevent dDoS scenarios.