OAuth is Not User Authorization

Scott Brady
Scott Brady

In the same way that OAuth is not authentication, it also does not tell us what the user is allowed to do or represent that the user can access a protected resource (an API).

Understanding what OAuth is not is just as important as knowing what it is, in order to use it effectively. In this article, I’m going to discuss how OAuth does not include user authorization and why user authorization rules should not live within your OAuth authorization server.

TL;DR: OAuth is not suitable for user authorization. The fact that you have an access token that allows you to act on the user’s behalf does not mean that the user can perform an action.

Client Authorization (Delegation)

OAuth is an authorization protocol, but maybe a better name for it is a delegation protocol. It is a protocol that allows a client application to request permission to access a protected resource (API) on the resource owner’s (the user’s) behalf.

Let’s take a look at the authorization process:

  1. The client application asks the user if they can access a protected resource on their behalf (by redirecting the user to the authorization server’s authorization endpoint, specifying exactly what they would like to access (scopes))
  2. The user identifies themselves to the authorization server (but remember, OAuth is not authentication; you’ll need OpenID Connect to help you with that)
  3. The user authorizes the client application to access the protected resource on their behalf (using the OAuth consent process)
  4. The client application is issued an access token

An access token represents that the client application has been authorized by the user. The protected resource can look at the access token and say, “okay, Alice authorized this application to access these parts of my system on their behalf; I’ll let it through”. However, just because Alice said it was okay does not mean that Alice is authorized to access the protected resource.

Client Authorization vs. User Authorization

Let’s break that down, using a banking system as an example:

  • Client authorization: can this client application read Alice’s transaction history?
  • User authorization: can Alice view the balance of account #123?

OAuth can handle client authorization by issuing an access token that Alice authorized. OAuth cannot handle user authorization.

Open Banking & FAPI introduce Rich Authorization Requests (RAR), where a client application can ask the user to authorize a specific transaction. But again, this is about the user consenting to the transaction, as opposed to what the user is allowed to do.

User Authorization Rules in your OAuth Authorization Server

Hopefully, it’s now clear that an OAuth access token (or an OpenID Connect identity token for that matter) does not represent the user’s permission to do something. But what about putting user-level authorization rules within your authorization server? After all, you already kind of have that with the user authentication process.

User-level authorization during authentication

This is where I am happy to give some ground. Confirming that the user is authorized to use your system when they authenticate is perfectly acceptable. For example, has the user been disabled by an administrator? Do they still qualify to use your services?

If you decide that the authenticating user is not authorized to use your system, you simply do not let them log in. These kinds of checks can also be performed during token generation or maybe as part of token validation (think of the introspection endpoint).

User-level authorization based on request

What breaks this is when you try and add user-level authorization per client application or protected resource. For instance, is the user permitted to use this scope? Are they permitted to use this client application (while OAuth is not authentication, you may see this check when using OpenID Connect)?

This would make your OAuth authorization process look like this:

  1. The client application asks the user if they can access a protected resource on their behalf
  2. The user identifies themselves to the authorization server
  3. The authorization server decides if the user is authorized to access the client or protected resource
  4. The user authorizes the client application to access the protected resource on their behalf
  5. The client application is issued an access token

Adding another Access-Control List (ACL) to your authorization server can’t be that bad, right? After all, it is an authorization server!

In my opinion, this is brittle and prone to error. You are now back to relying on the fact that tokens were issued to represent that the user and the request have been authorized, which is not something OAuth was designed for and is a dangerous assumption to make.

Just as many attempts to use OAuth to represent user authentication have failed, I’ve seen the same happen with user authorization too many times. The protocols just weren’t designed for it, and it’s just too easy to get wrong, especially when your token issuer is also providing SSO, issuing tokens without user interaction.

You are likely using a framework or out-of-the-box solution as your OAuth authorization server, which will want to follow the OAuth specification and issue a token to the authorized client application. You’ll spend your time fighting the design of OAuth, and if you make a single mistake or misunderstand how your framework of choice works, then the authorization server will kick in and issue the token as the user and client have requested, just like it is meant to.

I know I’m blurring the lines between OAuth and OpenID Connect a little here, but here’s a Stack Overflow question that I think highlights the kind of bizarre scenario you could find yourself in:

“I am looking [for] a way for [my authorization server] to invalidate the submitted token if the user doesn’t have access to the application in a single go, even though the challenged token might be valid if it is submitted by other application which the user has access to”

While you initially wanted to have all of your user authorization rules live within your central OAuth authorization server, you will likely end up duplicating these checks into your client applications and APIs to solve these kinds of problems. You are probably going to code yourself into a corner.

User Permissions in your OAuth Authorization Server

This is where most of my customers end up after understanding that user-level authorization is not part of the OAuth protocol and that it is not an easy fit within their OAuth authorization server. But the desire to write all of their authorization logic and user permissions in a single place still exists, which is understandable, especially when they have already achieved that with authentication and API access by using an SSO solution that implements both OAuth and OpenID Connect.

So, what is the solution? Role-Based Access Control (RBAC)? Stuff tokens full of user permissions?

Well, that is a story for another day and well outside of this OAuth story. I recommend checking out Dom’s excellent article on how identity and permissions don’t mix within an SSO solution. For solutions, take a look at how authorization languages are evolving with Zanzibar.

The Random Stuff

This article would not be complete without collating some of the weird and wonderful loaded questions I have received on Stack Overflow or my blog. These are often followed by a “gotcha” or a “checkmate”, or sometimes even a personal insult. But I’ll let your imagination come up with those.

“Then why is it called an authorization code?”

The authorization code represents the user authorizing the client application on their behalf. Again, that is client authorization, not permission to add user authorization.

“Then why do people return 401 Unauthorized when you don’t have a token?”

Naming is hard. In this use case, it would be nice to have named this status code “Unauthenticated”, but at least RFC 7235 requires that you include a WWW-Authenticate header alongside this status code.

“Then why is it called the Authorization header?”

Again, naming is hard. Basic Authentication also uses the Authorization header, but that doesn’t mean the user has full access or is even allowed to use the application. You still have to check if the user is allowed to perform an action.

“Then what are scopes for?”

Scopes represent a level of access that a client application is asking for from the user. For instance, a scope called “email.read” might mean “can I read your emails?”, or “email.send” might mean “can I send emails on your behalf?”. Check out Auth0’s article on the nature of OAuth scopes.

This is a fairly common mistake to make when starting out with OAuth, so I’ll forgive you for this one.

“I still don’t believe you! Everyone does this!”

Well, that’s just, like, your opinion, man. But it isn’t OAuth.