Simple JWToken ACL for Laravel (or anyone else)
Over at Mahana Designs we usually work with stateless session (meaning that there are no server sessions but instead the user information is repeatedly passed back to the server with each request in the token). Most of our work involves single users making requests from mobile apps, and so a full-scale ACL library isn’t normally necessary, but we include a simple option to allow our clients to easily add one should they later need to.
The technique we use is quite simple and relies on a combination of the Laravel Middleware and string comparison. There is absolutely no reason why you couldn’t use this same idea in any other framework. I will try my best to explain this process in framework neutral terms to help you understand how you might modify it to fit your own needs.
To start, we’ll pull in Tymon Design’s JWT-Auth package for Laravel. Remember that Json Web Token’s ( JWT) are a standard, not a specific library, and so you have a great many packages in all languages to choose from. Here’s a fantastic resource to learn more about them and even debug while working: Json Web Tokens.
The first part of our code is the login function:
We are no longer relying on setting server sessions during login; instead, we are building a token that we will return and will subsequently be passed along with all of our requests via the Authorization header.
JWT-Auth allows us to easily add additional payload to our token; we will use that to build “claim strings” that store an array of strings representing the ACL privileges we are going to allow this user to have. I’ll explain in greater detail below, but in this example we are giving them “open” and the ability to access their own profile. We check the user credentials normally and return the token.
This is how the token will be used in Postman:
If you are not familiar with loading tokens dynamically into Postman headers, have a look at my Quick Tip post that gives you a full run-down on the procedure.
Now that we have created the token and learned to send it back to our server, let’s see what our server is actually doing to handle this. We have our routes setup to work with the Laravel middleware such as this:
with a middleware library set up for this called, appropriately, ACLMiddleware:
So finally we get to the actual logic of the system. Let us pretend we are accessing the ‘information/latest’ route. Our middleware sees that it should pass the value “cbj.open” to the “acl” middleware (which is registered in our Kernel, as per the documentation).
The middleware gets the claim strings from the token payload. You remember that that is just an array of string, [‘cbj.open’, ‘cbj.users.profile.self’]. It will do a check that either passes the $request on to the $next closure, or blocks it.
The check could be a simple in_array(), but that would really limit its usefulness to us. Instead, we have a system of claim strings set up in a hierarchical fashion that works like this:
cbj — super admin
cbj.open — a general claim to authorized routes
cbj.users — all rights on all users
cbj.users.profile — all rights on profiles only
cbj.users.profile.self — all rights on user’s own record
Looking more closely at the handle() function, we see that we are checking if the user has a claim string that is a substring of the one required on the route.
For example, a super admin will have the claim string “cbj”. This is found in the string “cbj.users” thereby granting her rights to all user records. A customer, however, will have “cbj.users.profile.self” — not a substring of “cbj.users”- and so they are blocked.
A small point that might have you confused looking at the above is that the “.” is just another character and is only used to help make it human-readable.
It’s also important to understand that this does not serve as tenancy code or help restrict users from seeing other user records. While it is certainly possible to try to do that via the claim strings, a much safer solution is to check each record against the user id that is also contained in the token. (We usually do this via the sql itself with a where clause.)
Hope that helps! Feel free to ask me about any part that isn’t clear about this or other aspects of how we create apis.