Please for the love of God do not create a SecurityUtils
class!
Your class will become 10k lines of spaghetti code in a matter of months! You would need to have an Action
type (create, read, update, destroy, list, etc.) passed into your isUserAuthorized()
method, which would quickly become a thousand-line-long switch
statement with increasingly complex logic that would be difficult to unit test. Don't do it.
Generally what I do, at least in Ruby on Rails, is have each domain object be responsible for its own access privileges by having a policy class for each model. Then, the controller asks the policy class if the current user for the request has access to the resource or not. Here's an example in Ruby, since I've never implemented anything like it in Java, but the idea should come across cleanly:
class OrderPolicy
class Scope < Struct.new(:user, :scope)
def resolve
# A user must be logged in to interact with this resource at all
raise NotAuthorizedException unless user
# Admin/moderator can see all orders
if (user.admin? || user.moderator?)
scope.all
else
# Only allow the user to see their own orders
scope.where(orderer_id: user.id)
end
end
end
# Constructor, if you don't know Ruby
def initialize(user, order)
raise NotAuthorizedException unless user
@user = user
@order= order
end
# Whitelist what data can be manipulated by each type of user
def valid_attributes
if @user.admin?
[:probably, :want, :to, :let, :admin, :update, :everything]
elsif @user.moderator?
[:fewer, :attributes, :but, :still, :most]
else
[:regualar, :user, :attributes]
end
end
# Maybe restrict updatable attributes further
def valid_update_attributes
end
# Who can create new orders
def create?
true # anyone, and they would have been authenticated already by #initialize
end
# Read operation
def show?
@user.admin? || @user.moderator? || owns_order
end
# Only superusers can update resources
def update?
@user.admin? || @user.moderator?
end
# Only admins can delete, because it's extremely destructive or whatever
def destroy?
@user.admin?
end
private
# A user 'owns' an order if they were the person who submitted the order
# E.g. superusers can access the order, but they didn't create it
def owns_order
@order.orderer_id == @user.id
end
end
Even if you have complex nested resources, some resource must 'own' the nested resources, so the top-level logic inherently bubbles down. However, those nested resources need their own policy classes in the event that they can be updated independently of the 'parent' resource.
In my application, which is for my university's department, everything revolves around the Course
object. It's not a User
-centric application. However, User
s are enrolled in Course
, so I can simply ensure that:
@course.users.include? current_user && (whatever_other_logic_I_need)
for any resource that a particular User
needs to modify, since almost all resources are tied to a Course
. This is done in the policy class in the owns_whatever
method.
I haven't done much Java architecture, but it seems that you could make a Policy
interface, where the different resources that need to be authenticated must implement the interface. Then, you have all the necessary methods which can become as complex as you need them to be per domain object. The important thing is to tie the logic to the model itself, but at the same time keep it in a separate class (single responsibility principle (SRP)).
Your controller actions could look something like:
public List<Order> index(OrderQuery query) {
authorize(Order.class)
// you should be auto-rescuing the NotAuthorizedException thrown by
//the policy class at the controller level (or application level)
// if the authorization didn't fail/rescue from exception, just render the resource
List<Order> orders = db.search(query);
return renderJSON(orders);
}
public Order show(int orderId) {
authorize(Order.class)
Order order = db.find(orderId);
return renderJSON(order);
}
JWT (Intro to JSON Web Token) is just a token format, authentication it's something completely out of scope for that specification. It is indeed commonly used within authentication systems, but you could also use it for completely different scenarios so it makes sense to not include authentication specific constraints within that specification.
If you're looking for guidance on authentication you should refer to the OpenID Connect family of specifications. Additionally, if your system is comprised of HTTP API's and you're interested in providing delegated access to those API to your own or to third-party client application then you should refer to the OAuth 2.0 specification.
There are additional authentication related protocols like SAML and WS-Federation that are still widely used in enterprise scenarios, but they are significantly more complex to implement.
About your specific open points:
- The bearer token authentication scheme is defined in RFC 6750 which contains instructions on how to perform requests and the possible error codes.
- OAuth2 and OpenID Connect both contemplate the possibility and define the way to exchange a username/password with a token.
- The problem of extending a self-contained/by-value token (JWT) lifetime is addressed within OpenID Connect and OAuth2 through the means of refresh tokens.
Even though OAuth2 and OpenID Connect can be seen as easier to implement than some of its predecessors, they are still complex enough to warrant some caution and only implement them yourself if you're willing to spend a considerable amount of time and resources. It's generally a better option to use third-party libraries or services that do that heavy-lifting for you.
Finally, these protocols cover a lot of scenarios so they may be overkill in some situations.
Best Answer
For the first question, your approach seems correct to me. You consume a token in the
sendsms
entry that can only be generated by thelogin
endpoint. You could also use a generic token created by login, and not easily guessable (it would be equivalent to a cryptographically generated session ID), used as primary key to a database tuple. There you can store whatever you want (for example the number of SMS still allowed to be sent).You can also store the "state" in a variable and protect it using, say, HMAC, continuously exchanging it between the server and the client, but you still require some server state to defend against replay attacks on non-idempotent endpoints; at minimum, you need a per-session incremental counter.
The second problem is trickier, and you need first to ask yourself what the difference in behaviour (as seen by the server) would be between the app with its logic, and a malicious user sending repeated requests. You might enforce a minimum delay between requests that is comparable to the maximum rate at which the app can be driven, so that there's no longer an advantage in using tricks rather than going through the app.