Reputation: 16352
We have a service that handles authorization based on a User Name and Password. Instead of making the username and password part of the call, we place it in the SOAP header.
In a typical scenario, a Web Service calls the Authorization service at the start of execution to check that the caller is allowed to call it. The problem is though that some of these Web services call each other, and it would mean that on every sub-call the user's permissions are checked, and that can be very expensive.
What I thought of doing was to have the Authorization service return a Security Token after the first call. Then, instead of having to call the Authorization service each time, the Web Service can validate the Security Header locally.
The Security Header looks something like this (C# code - trimmed to illustrate the essential concept):
public sealed class SecurityHeader : SoapHeader
{
public string UserId; // Encrypted
public string Password; // Encrypted; Just realized this field isn't necessary [thanks CJP]
public DateTime TimeStamp; // Used for calculating header Expiry
public string SecurityToken;
}
The general idea is that the SecurityHeader gets checked with every call. If it exists, hasn't expired, and the SecurityToken is valid, then the Web Method proceeds as normal. Otherwise it will either return an error, or it will attempt to re-authorize and generate a new SecurityHeader
The SecurityToken is based on a salted hash of the UserId, Password, and TimeStamp. The salt is changed every day to prevent replays.
The one problem I do see is that a user might have permission to access Web Service A, but not Web Service B. If he calls A and receives a security token, as it stands now it means that B will let him through if he uses that same token. I have to change it so that the security token is only valid from Web Service to Web Service, rather than User to Web Service ie. It should be OK if the user calls A which calls B, but not OK if the user calls Service A and then Service D. A way around that is to assign a common key (or a set of keys) to logically related services. (ie. if client can do A then logically he can do B as well).
Alternatively, I'd have to encode the user's entire permission set as part of the security header. I'll have to investigate what the overhead will be.
Edit:
Several people have mentioned looking at other security schemes like WS-Security and SAML etc. I already have. In fact, I got the idea from WS-Security. The problem is that other schemes don't provide the functionality I need (caching authorization information and protecting against replays without an intemediary database). If someone knows of a scheme that does then I will glady use it instead. Also, this is not about authentication. That is handled by another mechanism beyond my control.
If it turns out that there is no way to cache authorization data, then it means that I'll just have to incur the overhead of authorization at each level.
Upvotes: 5
Views: 637
Reputation:
The fundamental problem with your scheme is that you're not using a standard framework, or implementation. This is regardless of any particular merits of your scheme itself.
The reason is simple, security (cryptography in particular) is very, very complicated and pretty much impossible to get right. Use a common tool that is robust, well understood and proven.
See: http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=wss for more information on WS-Security.
Most frameworks (.NET/JavaEE etc) will have in-built support (to some degree) for WS-Security.
If you beleive your scheme to be better in some way than the standards, I suggest you write it up as a paper and submit it for peer-review (along with a reference implementation), but DO NOT use it to secure an application.
EDIT to respond to OP Edit: I think you're confusing the roles of Authentication and Authorization a little, which is easy to do...
The roll of the Security Token (or similar) in schemes is to Authenticate the sender of the message - basically, is the sender who they should be. As you rightly pointed out, Authentication does not imply anything about which underlying resources the sender is to be granted access to.
Authorization is the process whereby you take an authenticated sender and apply some set of permissions so that you can restrict scope of access. Generally the frameworks won't do authorization by default, you either have to enable it by creating some form of ACL, or by extending some kind of "Security Manager" type interface.
In effect, the idea is that the Authentication layer tells you who is trying to access Page A, and leaves it up to you to decide if that person is authorized to access Page A.
You should never store information about the rights and permissions in the message itself - the receiver should verify rights against its ACL (or database or whatever) for each message. This limits your exposure should someone figure out how to modify the message.
Upvotes: 4
Reputation: 13112
First of all, read @CJP 's post, he makes an excellent and valid point.
If you want to go ahead and roll it yourself anyway (maybe you do have a good reason for it), I would make the following points:
Again, I urge you to reconsider rolling your own, but if you have to go out on your own...
Upvotes: 0
Reputation: 14964
Ok, replacing my older answers with hopefully a better one.
What you describe should work if you have a way to securely share data between your services. For example, if your services share a secret key with the Authorization Service, you can use this key to get the salt.
BTW, I don't know enough cryptography to say whether it's safe enough to add secret salt + hash (although seems fine); I'm pretty sure it's safe to HMAC with a secret or private key. Rotating keys is a good idea, so you would still have a master key and propagate a new signing key.
Other issues with your approach are that (a) you're hardcoding the hashing logic in every service, and (b) the services might want to get more detailed data from the Authorization Service than just a yes/no answer. For example, you may want the Authorization Service to insert into the header that this user belongs to roles A and B but not C.
As an alternative, you can let the Authorization Service create a new header with whatever interesting information it has, and sign that block.
At this point, we're discussing a Single Sign-On implementation. You already know about WS-Security specs. This header I described sounds a lot like a SAML assertion.
Here's an article about using WS-Security and SAML for Single Sign-On.
Now, I don't know whether you need all this... there are in-between solutions too. For example, the Authorization Service could sign the original Username block; if you worry about public/private crypto performance, and you're ok sharing secret keys, you could also use a secret key to sign instead of public/private keys.
Upvotes: 2
Reputation: 63126
Personally I don't see you having any issues there as long as you have a centralized underlying framework to support the validation of the SecurityToken values.
Upvotes: 1