authguidance.com

API Authorization Design – OAuth Architecture Guidance

Background

In our previous post we discussed User Data Management and in this post we will focus on data protection in APIs. Any real world system needs to apply business rules before allowing access to resources.

OAuth Capabilities

The OAuth family of specifications has many finer details, but I think of these as the two main capabilities:

Capability Description
Authentication Dealing with users and authentication, while externalizing this complexity from applications
Data Protection Design patterns that enable APIs to authorize access to resources based on tokens

API clients may use different authentication flows, but APIs always protect data in the same way, by receiving access tokens:

This post will focus on OAuth data protection and how it maps to complex business rules, which is an area that is often not properly understood.

APIs and Business Rules

A request to get data from a UI might look like this, where a token containing the subject claim is sent and then business rules are applied:

The above example might include handling business rules like these, and we will show how to manage these types of rule in an OAuth secured API:

API Authorization Steps

Implementing authorization in done via three phases but the main work is always done in step 3:

Step Description
Token Validation JWT Access Token Validation, to ensure integrity of the message credential
Scope Checks Sanity checks to ensure that an access token is allowed to be used for a particular business area
Claims Based Authorization Detailed permission checks against resources, using domain specific data

Step 1: Token Validation

The JWT Access Token Validation blog post described how an API verifies the JWT’s digital signature and if the token is expired. If the token is not valid a 401 error is returned:

"code": "unauthorized",
"message": "Missing, invalid or expired access token"

Think of token validation as an entry level check to authenticate the request to the API, after which the API can trust data in the access token’s payload.

Step 2: Scopes

Scopes can be included in access tokens to represent an Area of Data and Permissions on that Data.

Examples Usage
orders Indicates that an access token grants access to orders placed by a user
orders_read Indicates that an access token cannot be used to make data changes to orders

When personal assets are involved, it is usual to show a consent screen that displays scopes, so that the user knows which data they are granting access to, and whether they are granting read or write access. 

Built-In Scopes

OAuth also uses some built in scopes for Personally Identifiable Information (PII) that is stored by the Authorization Server:

Examples Usage Scenario
openid Indicates that the user’s identity is being used, via the OpenID Connect protocol
profile Indicates that the user’s name and possibly other information is being used

Scope Limitations

Scopes are fixed at design time and you cannot use them for dynamic purposes, such as different scopes for different types of user. Whenever you need to perform dynamic authorization, claims must be used.

Audience Checks

APIs should also check the audience of received access tokens, and the most common setup is for a set of related APIs to use the same audience. This enables JWT access tokens to be forwarded between microservices:

API Audience
Orders api.mycompany.com
Customers api.mycompany.com
Products api.mycompany.com

If you deal with different subdivisions of a large company, then it is usually recommended to use a different audience per subdivision.

Step 3: Claims

Our next code sample will authorize using the following custom claims. Note that none of this data is stored in the Authorization Server:

Claim Represents
User ID The user id with which transactions are stored in the domain specific data
User Role Two user roles are involved, for a normal user and an administrative user
User Regions An administrator grants users access to data for one or more regions, represented as an array

The third of these is an array claim, to represent the type of authorization that must be done in many real world business systems:

Claims Requirements

There are two main requirements that you need to consider when working with claims, and we will show two main ways to achieve this:

Requirement Description
API Data The API must receive the data it needs in order to implement its domain specific authorization
Confidentiality Access tokens returned to internet clients must not reveal all of this information

Claims Architecture Option 1

The ideal way to meet the above requirements is for the Authorization Server to reach out to the API at the time of token issuance to get custom claims, then to include the custom claims in the access token.

This state is then stored in the Authorization Server, and the access token returned to clients uses a confidential reference token format, which is typically a UUID or something similar:

When a client calls the API, the reference token is introspected to get a JWT access token, which is then forwarded to the API. The introspection is usually done in an API gateway that is placed in front of the API:

This is a great solution when supported, since all claims issued are audited by the Authorization Server and it scales very well if the JWT needs to be forwarded between microservices.

Claims Architecture Option 2

Option 1 may require a specialist Authorization Server, whereas this blog is using AWS Cognito by default, which does not support the above features.

We will therefore also demonstrate an alternative approach, where custom claims are looked up when an access token is first received, then cached: