Authentication vs. authorization: Which one should you use, and when?
What's the difference between authentication and authorization? Does it matter which you use — or do you need both? Are they the secure app's chicken and egg conundrum? Let's dive in.
What is authentication vs. authorization?
Generally, programmers seem to have subconscious reasoning for applying security and access controls to their applications. They'll describe a simple set of auth codes that protect the app if questioned.
But, I've found that if you keep pushing, things can get a little murkier.
Are you using authentication, authorization or both? What's the difference? Do these get mixed up because they both begin with "auth" and are sometimes referred to in the same way as short-hand? Maybe some of this mix comes from the fact that each seems to allow you to address the other's concerns if you stretch it a bit.
But, authentication and authorization are two separate things. Even if they go hand-in-hand for many applications, it's important to know the difference. Once you fully grasp this, it's clearer how to implement each securely and effectively.
Authentication vs. authorization example
I will be using the terms application and actor as I discuss authentication and authorization. My background is web applications, so I tend to think of people using my web application as visitors. But actor refers to any human or computer accessing your application in any manner. The term application means any piece of software. So, we could be referring to a visitor of your website, a user using a desktop application, or a consumer querying your API.
Authentication refers to identifying the actor using your application. That's it. All authentication is responsible for is saying "I can identify this actor" or "this actor is unidentified." The meaning and description of authentication are simple — that doesn't mean the implementation is. You might have a username and password, tokens, cookies, JWTs, or any number of other ways to authenticate a user. But, after the process is complete, we either can identify the actor or we can't.
Authorization refers to the permission to accomplish a task in your application. Tasks can be simple like listing resources or seeing the details of an object. Advanced ones may be creating or updating data, or moving data through a complex business workflow. Now, this is important: authorization does not require an authenticated actor. It just so happens that most authorization decisions are based on the specific permissions of an identified actor, but that isn't a requirement. We'll touch on this more later. But, the point is authorization is the mechanism that answers the question: "does this actor have permission to do this action."
Both authentication and authorization are necessary
It can be easy to assume that a very simple application does not need layers of complex authorization. That may seem true, but an authorization layer should be applied nevertheless. Let's talk about why.
I think most programmers recognize easily they will need authentication. How do we determine who an actor is? That's authentication. But decisions in the application don't always require an identified user.
Authorization programming can make decisions based on the fact that the actor is unknown or anonymous. Other times, authorization can be tied to outside conditions like location, time of day, business patterns, or anything else that doesn't necessarily require we know which identity is using the application.
Remember, a secure application is configured to give the least amount of access and information. In my opinion, that means that my application should always come from a default state of denying everything. And because of this, some of my authorization will apply to a non-authenticated user.
Let me break this down into a practical example. I have a page that displays a resource from my database to the actor. The business rule is simple: if the user is logged in, show them the resource's phone number and email address. If they're not, show a masked phone and placeholder email address.
So, this means that we need authentication because the decision is based on whether the user is logged in or not. However, the way I see it, I need two authorization requests.
I bet you thought I need one: something like
Simple — have this authorization return true if the visitor is authenticated. But, because I've decided to come from a root of denying everything, I have two:
ShouldVisitorSeeMaskedContactInformation. If we have an authenticated user,
ShouldVisitorSeeFullContactInformation returns true. If we don't have an authenticated user, the other returns true. Then, instead of an
if/else, I have two separate
if statements. This is how I build in a deny-by-default secure authorization layer coupled with authentication. (That is to say, two independent truth checks, treating both outputs as a 'feature', allow for a deny-by-default configuration.)
Why would I do this, though? Sure it sounds theoretically more secure — but in practice, isn't it a lot of extra work? I'm glad you asked.
Using authorization correctly allows easy wholistic application changes
Let me share a real-world example from my career.
A client had requested that their web application list the resources for any visitor. Multiple pages had this listing shown and an API. They then requested that if visitors click through to the detail page or request details over the API, the visitor must be authenticated. Seems pretty simple.
Now, you might consider just checking for authenticated users at the top of the code that does the details retrieval. The other code needs no checks because it doesn't matter if they're logged in or not. (That is to say, whether the user is authenticated or not, either situation allows access.) There are only two states in this application: user is authenticated or not.
I can tell you, though, that only two states are rarely the case. There's hardly ever a time that an application matures without expanding its access controls beyond these two states. So, with this knowledge in place, I decided to implement authorization on all endpoints.
I implemented an authorization layer that simply returned true all the time on the endpoints that displayed lists of resources (including the API). A simple class that was reused on each endpoint always authorized access to the list of resources. Then, on the endpoint with the details, I created another class that returned
true only when I had the visitor's identity. Simple.
Then it happened: business requirements changed! About a week after the web page was launched, the business decided they now wanted the lists of resources to be locked behind authentication.
Guess what? Because I had used authentication correctly, to begin with, it was super simple. I just modified the authorization class to list resources to check for an identified visitor. Done!
I can imagine a scenario where lists require an authenticated user, but details require a user who has a subscription. If that happens, I'm ready! I'm ready for the future changes in the business, too.
Is using authorization over simple authentication checks premature optimization?
I'd be remiss if I didn't address the complexity that is properly implementing both authentication and authorization can inject into a simple project. If my application only needs to check for an authenticated user, am I gaining anything by adding authorization? Am I prematurely optimizing code? Or, is this a case of YAGNI (you ain't gonna need it)?
No, it's not. Rarely does an application last any amount of time with just two simple states. There's always something that gets added on. An admin interface? Now you need levels of authorization. Subscription tiers? Authorization! Even what an authenticated user is allowed to do can change. You can easily unify this by checking in an authorization layer. So, no, it's not premature optimization.
TLDR; What's the point?
Authentication determines an identity of an actor. Authorization decides if an actor has permission to take action. Your application, no matter how simple, should use both hand-in-hand. Authenticate actors when you must. Authorize actors, regardless of their identity, for every action.