# Part 3 - The OAuth Authorization Server

OAuth Simplified 3 / 3
15 min read
Table of Contents

IntroductionLink to heading

The goal of the authorization server in the OAuth 2.0 protocol is to authenticate users and issue access tokens to client applications after obtaining the proper authorization from the resource owner. The authorization server verifies the identity of the resource owner (usually a user), gets their consent, and issues access tokens that give clients controlled and limited access to protected resources.

This article discusses the impelemntation and use of a simple authorisation server of the authorization code grant type, which supports the following features:

  • Registering clients.
  • Issuing tokens to clients.
  • Assigning a scope to an issued token.
  • Issuing refresh tokens to clients.
  • Authenticating users.

Because of the vast amount of features the authorization server must support, it is arguably the most complex component in the OAuth ecosystem.

In the OAuth protocol, most complexity is pushed to the authorization server because authorization servers are the fewest in number. This means there are many more clients than protected resources, and there are many more protected resources than authorization servers.

Registering ClientsLink to heading

As a first step, the authorization server must register clients on it’s database. The simplest form of managing clients on the authorization server is static registration. This means the authorization server generates all the unique client identifiers (client IDs) needed for all clients that connect to the authorization server. The generated client IDs would then be stored in the authorization server’s database.

Once the client IDs are defined, the authorization server generates client secrets for every client ID. These client secrets are also stored on the authorization server’s database alongside the client ID.

The final step in registering a client on the authorization server is to define a redirect URI for all clients. Unlike the client ID and the client secret, the authorization server does not generate the redirect URI. The redirect URI is defined by the client and will be stored in the authorization server’s database alongside the client ID and client secret of the various clients.

At the end of the client registration process, the authorization server would have a client object like this in it’s database:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
}

There could be many objects like this and all of them would be stored on the database of the authorization server. Notice the redirect_uris value is an array type. This is because a client can have multiple redirect URIs. This will be discussed further down the article.

Authorizing a ClientLink to heading

3.1 As discussed, the authorization server needs to authorize the client on behalf of the user so the client can receive an authorization code. This communication is done over the front-channel, which needs to be reachable by the user’s browser. Since most authorization servers are web servers, the authorization endpoint typically has the /authorize path and is always a GET request.

3.2 Firstly, when the test client calls the /authorize endpoint, the authorization server finds out which client made the request. Typically, the client passes its identifier in the client_id parameter and its redirect URI in the redirect_uri parameter.

3.3 Once the client_id has been parsed by the authorization server, the authorization server must determine if the client exists in its database of predefined clients. If the client does not exist, an error is emitted such as {error: 'Unknown client'}. A classic check is to see if the client_id and redirect_uri that was passed in by the client, match what is already stored in the authorization server’s database. Since only checking the client_id could lead to security gaps. The check for the redirect_uri is highly recommended because all the current communication in this section is being done on the public front-channel, which can be easily tampered with.

The client sends the following payload to the authorization server:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
}

3.4 When the client is authorized, a request_id is randomly generated to keep track of the client’s initial authorization request. As we will see in the next step, this request_id will protect the server from cross-site request forgery. Also, this request_id will get stored in the authorization server’s database alongside the specific client’s information that sent the initial authorization request to receive the authorization code.

The database will have the following data at the end of this client verification flow:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
"request_id": 324
}

Figure 3 - Shows the client authorization flow.

3.5 The client is now authorized, with its request ID safely stored in the authorization server’s database. The next stage prompts the user to authorize the client on the user’s behalf. This is done so the request ID from the form can be validated with the request ID from the initial request by the client.

4 User DecisionLink to heading

4.1 After the client is authorized, the user is prompted to give the client the relevant permissions to access the protected resource and act on the user’s behalf. This can be done through a form using a UI. Here is an example:

Approve this Client?
ID: `test-client`

4.2 The user can approve or reject the client who requested to be authorized and act on behalf of the user. After clicking the Submit button, an API request is typically sent to the endpoint path /approve on the authorization server.

4.3 The request ID from the client in the previous section is embedded into this form in the background. So when the user clicks on Submit, the request ID is included into the API call to/approve in the authorization server. The authorization server will compare the request ID from the form to what is stored in it’s database for added security. Here is the full object that is sent to the /approve endpoint path:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
}

5 Processing the User DecisionLink to heading

5.1 If the user rejects the client, by calling /approve with the reject message, this means the user has denied access to an otherwise valid client. The authorization server now has the responsibility to tell the client that the user has rejected its authorization request. This can be done the same way as the client communicated with the authorization server. Meaning, the authorization server will take a URL hosted by the client, add a few special query parameters, and redirect the user to the endpoint defined by the client’s redirect_uri. The URL that is hosted by the client, which the authorization server can use is known as the redirect_uri. The authorization server sends back an error message to the redirect_uri with the message error: access_denied.

5.2 This is why the client’s redirect_uri is needed. Also, this is why the authorization server validated the client’s redirect_uri against the existing client information in the authorization server’s database when the initial authorization request arrived by the client. So the authorization server knows that it is sending the user to an approved,verified address. And not an address that can be tampered with through the front-channel.

5.3 If the user has approved the client, then this means the user allows the client to act on their behalf.
When the /approve is called with the approve message, the first step is to check what kind of response the client seeks. The HTTP response_type should be code. If not, error is sent to the redirect_uri.
The second step is to generate the authorization code and save it into the database because the authorization code will need to be referenced by the authorization server for later steps as we will see.
Finally, the third step is to send back the authorization code, and the scope (if provided by the client in the initial authorization request) to the client through the client’s redirect_uri and hand back control to the client. The authorization server is now fully prepared for the next step in the OAuth 2.0 authorization code grant type flow.

6 Issuing a TokenLink to heading

6.1 At this point the client is now in control and has the authorization code that was received from the front-channel by the authorization server. The next step is to request an authorization token by sending a POST request to the /authorize endpoint of authorization server. The client can send the authorization code either in the header or the form body. Well behaved authorization servers would accept either methods but not both at the same time.

The client will send the following payload to the /authorize endpoint of the authorization server.

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"authorization_code": 45261,
"grant_type": "authorization_code"
}

6.2 When the /authorize endpoint is called, the authorization server will validate the following in order by comparing the incoming data from the client to what is already stored in the authorization server’s database:

  • The client ID
  • The client secret
  • The authorization code

6.3 When all the relevant data is validated, the next step is to check if the grant_type is set to authorization_code in the body of the POST request sent by the client. Since the authorization server in this case only supports the authorization code flow grant type, it is important to check.

6.4 If the authorization server does find the authorization code grant, the authorization server then needs to generate an authorization token.

For example, you can create a JWT token. But for this case, we will keep things simple and generate a random string, then store it in the database.

6.5 Now the authorization server can finally send the token back to the client in the form of a JSON object that includes the authorization token. The JSON object should also include how the protected resource can use the authorization token. The usage method of the authorization token can be communicated by specifying the type of the authorization token. In this case, the authorization server sends back a Bearer token type:

{
"access_toke": "lRQUChwvWf",
"token_type": "Bearer"
}

6.6 The authorization server will store the authorization token in it’s database alongside the rest of the client information:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
"request_id": 324,
"access_token": "lRQUChwvWf"
}

6.7 At this point, we have stepped through a simple but fully functioning authorization server:

  • Authenticating clients
  • Prompting users for authorization
  • Issuing randomized bearer tokens using the authorization code flow.

Nice! 🚀

The next steps can be seen as optional added features to the authorization server.

7 ScopeLink to heading

Scopes represent a subset of access rights related to a specific OAuth delegation.

Scopes are represented as strings in an OAuth protocol. For example, it was explained in the Registering a Client section that the client object is stored in the database. With the addition of scopes, we will predefine what scopes the client is allowed to access in the protected resource, as you can see in the bottom of the below JSON object.

Object stored in the authorization server database:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
"scope": "inventory cart"
}

This means, when the client requests authorization, the client will only get access to the inventory and the cart section of the protected resource, and will not get access to other sections such as the finance, or admin section. Also, when the client sends the payload for authorization, (as was talked about in the authorizing a client section) the client can ask for a subset of it’s pre-allocated scopes. For example, the client can request for access to just the inventory and not the cart.

Body of the POST request when a client quries the /authorize endpoint to get authorized:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
"scope": "inventory"
}

As you might have noticed this member in the JSON object is a space separated list of strings. Each word in the string represents a specific OAuth scope.

Good question! The OAuth working group thought that it would be much easier to concatenate the scopes into a space separated string because HTTP forms don’t have a good way of representing complex structures such as arrays and objects. The scope needs to pass through as a query parameter through the fornt-channel so the scope needs to be encoded somehow. This is why a string was picked. Also, according to the OAuth spec, it is not necessary to use JSON. Arrays might not be supported through another syntax.

We talked about how the scope effects OAuth data when the client is registered and when the client is requesting authorization. But what aobut when the user decision is being made? Have a look!

Approve this Client?
ID: `test-client`

You can see that the user can give the client fine-grained permissions by providing the client with inventory and/or cart permissions. These permissions are added in the scope section when the token is issued in later steps.

8 Refresh TokenLink to heading

When the authorization token expires, the entire flow must be restarted from the beginning. It can be quite frustrating for the user to always get prompted with the authorization UI whenever the token expires. Like we saw in the User Decision section. Therefore the OAuth protocol gives us a convenient feature called a refresh token. The refresh token is only used on the authorization server, by the client, to request a new authorization token when the current authorization token expires.

When the refresh token feature is enabled, then the authorization server will issue a refresh token alongside the authorization token. The client can use the refresh token to request a new authorization token from the authorization server whenever the authorization token expires.

We saw in the Issuing a Token section that the authorization server will store the authorization token in it’s database alongside the rest of the client information. The refresh token is also stored alongside the authorization token in the authorization server database:

{
"client_id": "oauth-client-1",
"client_secret": "oauth-client-secret-1",
"redirect_uris": ["http://client-server:9000/callback"],
"request_id": 324,
"access_token": "lRQUChwvWf"
"refresh_token": "THQUYhVvPf"
}

According to the OAuth specification, there is no way for the client to know. The client will just have to use the authorization token on the protected resource. If the client receives an error when using the authorization token then the client must request a new authorization token from the authorization server by using the refresh token.

Then the client will have to fallback to the OAuth authorization code grant type and start the entire flow from the start, by also asking the user permission to authorize the client. This is why refresh tokens tend to have a longer lifespan for this not to occur as often.

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


OAuth Simplified Series

Comments