OAuth 2.0 (RFC 6749) is a great authorization framework but it leaves much up to the imagination. Luckily, there are numerous extensions that expand, explain, and clarify the basic capabilities to build a robust and powerful suite of standards. That said, there’s one unobviously complex area which gets little attention: Scopes.

What is an OAuth Scope?

The simplest definition of “scope” is that it’s a permission or authorization granted by the authorization server to perform certain actions on the resource server. According to Section 3.3 of the OAuth specification, the scopes are a case-sensitive, space-delimited, unordered list of strings.

And that’s it.

There’s no guidance on specific formats, naming conventions, or even patterns. That’s great from a flexibility standpoint but we also end up with a mishmash of inconsistent schemes that often don’t make sense. It’s even worse when the pattern varies within an API.

Scopes in the Real World

Github is an example of what not to do. Here are the scopes for repositories:

  • repo
  • repo:status
  • repo_deployment
  • public_repo
  • repo:invite
  • delete_repo
  • admin:repo_hook
  • write:repo_hook
  • read:repo_hook

Unfortunately, they’re not predictably well named, consistently structured or even use the same punctuation. If you know any one of those scopes, you’re unlikely to figure out any of the others. At best, it’s annoying. At worse, it’s absurd and frustrating.

On the other end of the spectrum are the OAuth scopes for the Google APIs. Despite having  dozens of APIs and over 350 scopes, they have a simple, direct naming convention. Here are a few for the Analytics API:

  • https://www.googleapis.com/auth/analytics
  • https://www.googleapis.com/auth/analytics.edit
  • https://www.googleapis.com/auth/analytics.manage.users
  • https://www.googleapis.com/auth/analytics.readonly
  • https://www.googleapis.com/auth/analytics.user.deletion

Without even looking at the docs, we know this is probably Google Analytics. Further, we can guess what how/which each scope maps to a function. This is great, but could we do more?

URLs as OAuth Scopes

The Google scopes share another obvious aspect: they’re urls.

At present, if you follow any of the scopes – such as https://www.googleapis.com/auth/analytics.manage.users – you get a simple message like:

analytics.manage.users

and nothing else. I believe it’s a missed opportunity.

What if that url resolved to a more descriptive page such as the corresponding OpenAPI specification – or even the documentation on the scope itself? What if there was a JSON snippet to describe the scope?

Anyone who saw them could simply click and follow the rabbit hole into better understanding. I see two immediate benefits and two long term benefits:

  • First, the Documentation and API teams have another reason to work together to release components and documentation together.
  • Next, new users are more likely to understand the scopes and choose the appropriate ones for their use case.
  • API can communicate policy changes and deprecations in the places where impacted users are likely to find them.
  • Finally, it’s easy for a developer or even your security team to audit what an application does.

Those are first order benefits but if we add structure to this pattern, what happens?

URLs as OAuth Scopes — The Next Step

First, let’s take the OAuth 2.0 Authorization Server Metadata specification (or more generally the ‘discovery document’) which allows an OAuth client application to understand the authorization server capabilities. While it is a draft specification, it’s late stage so it’s stable and starting to see implementations in the wild.

In terms of disclosure, my employer implements the required well-known endpoint and follows the specification. In fact, here is the discovery document for one of my authorization servers.

Next, instead of just linking those scopes to documentation, let’s replace it with some JSON:

{
    "href":"https://api.caseysoftware.com/scopes/users.delete",
    "issuer":"https://login.caseysoftware.com/oauth2/ausf795y5zQJKmCaG0h7",
    "required":false,
    "consent_required":false,
    "name":"User.Delete",
    "description":"This scope lets you delete users in the API.",
    "documentation_href":"https://docs.caseysoftware.com/users#user-delete",
    "iat":1514764800,
    "exp":1546300800
}

The first two fields let us keep the context of the scope and describe the authorization server we would expect to get this scope from. Further, by appending the well-known suffix to the issuer, we can retrieve the overarching discovery document.

The next two fields – required and consent_required – help us understand the context and what may happen when we request these scopes. Outside of OpenID Connect, required scopes are uncommon but in a post-GDPR world, user consent will be common.

The next three fields are more powerful to developers and end users. The documentation_href addresses the initial use case but the name and description give us human-readable summaries for quick review.

The final two fields – iat and exp – describe the issued at and expiration time. While those are normally used to describe token lifetimes, we could use it for caching.

Putting it all Together

Now think of what building and launching our API looks like:

  1. First, we create our endpoints with the corresponding scopes.
  2. Next, we configure an authorization server with those scopes and the corresponding discovery document.
  3. Next, developers load our OpenAPI specification and our OAuth discovery document into their tools. This gives them insight into the endpoints, how to use them, and the required permissions for each.
  4. Finally, as developers plan and build their applications, they can query the two documents to implement code completion in their IDEs, generate helper libraries, or just know what’s going on.
  5. Finally, as developers build their application, they query the discovery document and corresponding scopes to implement code completion, generate helper libraries, or just pursue to get a better understanding.

At a higher level, imagine a large organization with dozens of teams or hundreds of APIs, each with separate endpoints, authorization servers, and scopes. This gives an API governance team a central place to aggregate, query, and validate or review processes.

As a result, the well-established tools we have for checking website links can be applied to our API design and authorization documentation.

So my questions for you:

What am I missing? What did I forget?

What would you do differently or better?

Have you seen an API doing anything like this? If so, where?

Would this be useful for your API/users? Would you implement it? If not, why not?

2 comments to “Leveraging OAuth 2.0 Scopes for Fun & Profit”

You can leave a reply or Trackback this post.
  1. Agent Zero

    rfc6749 does show the spec of allowed characters in a scope in the section-3.3 you link to – but it’s all of printable ascii *except* for a space, ! or double-quotes, and some people would also add a comma, to erroneously separate the scope items from each other in the string.

    With those minor caveats though – it’s a very interesting idea, that I hope to use for future projects.

  2. Agent Brand

    You have presented a very good idea. I would like to use this. At the same time I would also keep in mind that this descriptive way of defining scope also increases the size of the token. Think about mobility devices and mobile apps.

Write a Reply or Comment

Your email address will not be published.