03 What Is the Authorization Service Authorization Code and Access Token Issuance Process

03 What is the Authorization Service Authorization Code and Access Token Issuance Process #

Hello, I am Wang Xindong.

In the previous lecture, I started with the question of why we need an authorization code and went through the overall communication process of the authorization code licensing process for you. In the next three lectures, I will focus on explaining the workflow of the authorization service, the tokens involved in the authorization process, and how to integrate with OAuth 2.0. With this, you will have a thorough understanding of the most classic, comprehensive, and commonly used authorization process, making it easier for you to handle authorization-related logic in the future. Now, let’s start this lecture.

When introducing the authorization code grant type, I mentioned the “authorization service” many times. In short, the authorization service is the service responsible for issuing access tokens. Furthermore, the core of OAuth 2.0 is the authorization service, and the core of the authorization service is tokens.

Why do I say this? When a third-party software, like Little Rabbit, wants to access little Ming’s orders on the JD store, it must first obtain an access token from the authorization service provided by the JD Merchant Open Platform. Then, it uses the access token to “represent” little Ming to request his order data. Isn’t this the core of the entire OAuth 2.0 authorization system?

So, how does the authorization service generate access tokens and what operations are involved? Another question is, in case the access token expires and the user is not present, how can a new access token be generated?

With these two questions in mind, let’s explore the core component of the authorization service using the authorization code grant type as an example.

Working Process of Authorization Service #

Before we start, let’s go back and recap the entire process of small Ming granting authorization data to Little Rabbit Software.

We mentioned that Little Rabbit Software needs to authorize data from small Ming on the JD.com Merchant Open Platform. Doesn’t this sound strange to you? You can’t just say, “Hey, JD.com, give the data to Little Rabbit,” because JD.com will definitely reply, “Who is Little Ming? We have not filed any documents for him. I can’t give him the data just in case he’s a scammer.”

Right, think about it. This logic must be correct. Therefore, before this significant action of authorization, Little Rabbit must first “file” with the platform, also known as registration. After registration, the JD.com Merchant Open Platform will provide Little Rabbit Software with app_id, app_secret, and other information for various identity verifications when authorizing in the future.

At the same time, during the registration, the third-party software will request the accessible scope of protected resources. For example, whether Little Rabbit can access orders from small Ming’s store that occurred more than three months ago, or whether it can access all field information of each order, etc. This permission range is called scope. Later, I will explain scope control in detail.

It may sound a bit abstract in words, so let’s just look at the code. To simulate the storage of data after registration, we will use the following Java code:

Map<String,String> appMap = new HashMap<String, String>();
appMap.put("app_id","APPID_RABBIT");
appMap.put("app_secret","APPSECRET_RABBIT");
appMap.put("redirect_uri","http://localhost:8080/AppServlet-ch03");
appMap.put("scope","nickname address pic");

After filing, let’s move forward. Small Ming comes over to the platform to request his order data to be given to Little Rabbit. The platform checks the secret code, confirms it, and realizes that Little Rabbit is legitimate, so it can proceed to the next step.

In the previous lesson, we discussed that in the authorization code grant type, the work of the authorization service can be divided into two parts: issuing the authorization code and issuing the access_token. To better express the existence of the authorization code and access_token, I have marked them in dark colors in the following diagram:

Let’s first take a look at the process of issuing the authorization code.

Step One: Issuing the Authorization Code #

In this process, the authorization service needs to complete two parts of work: preparation and generating the authorization code.

You may wonder, what does “preparation” include? We can imagine that when small Ming authorizes the third-party software, Little Rabbit, for printing orders, he will see an authorization button on the authorization page. However, before small Ming sees this authorization button, the authorization service has actually performed a series of actions.

These actions are called preparation work, including verifying the legality of Little Rabbit as a third-party software and the legality of the callback URL.

In the web browser environment, the entire process of issuing the code is completed through front-end communication by the browser. This means that all information is at risk of being intercepted. Therefore, the authorization service must determine the existence of the third-party software.

Similarly, the callback URL can also be forged. For example, criminals can disguise it as a phishing page or a software download page with malicious attacks. Therefore, for security reasons, the authorization service needs to perform basic verification of the callback URL.

if(!appMap.get("redirect_uri").equals(redirectUri)){
}

In the program of the authorization service, after passing these two verifications, a page (belonging to the authorization server) will be generated or responded to prompt small Ming to authorize.

Step Two: Verifying the Scope (First Time). Since it’s authorization, it will involve scope. For example, when we use WeChat to log in to a third-party app, we will see that WeChat prompts us that the app can access our nickname, avatar, gender, location, etc. If you don’t want the app to access certain information, you can choose not to grant that permission. The same applies to Xiaotu. When Xiaoming authorizes Xiaotu, he can also choose the scope of permissions to grant Xiaotu, such as whether to grant Xiaotu access to orders older than 3 months.

This means that we need to compare the scope parameter passed by Xiaotu with the permission scope requested when Xiaotu registered. If the requested permission scope is greater than the registered scope, a warning of unauthorized access needs to be displayed. Remember, this is the first permission verification process.

String scope = request.getParameter("scope");
if(!checkScope(scope)){
}

Step 3, Generating the Authorization Request Page

This authorization request page is a page on the authorization service, as shown in the following image:

The page displays the two permissions requested by Xiaotu during registration: “today” and “history”. Xiaoming can choose to narrow down the scope of permissions, such as granting permission only to access the “today” information.

With this, the preparation work for issuing the authorization code code is completed. You should note that I have always emphasized that this is only the preparation work because the authorization code code and access token access_token values are generated only when the user clicks the “approve” button, and “everything truly begins”.

Here, I need to explain: in the above preparation process, we have ignored the login process of Xiaoming. However, only when the user is logged in can they authorize third-party apps, and the authorization service can obtain user information and eventually establish the correspondence between code and app_id (the identifier of the third-party app) + user (the identifier of the resource owner). You can treat the code for the login part as an additional exercise.

After Xiaoming clicks the “approve” button, the process of generating the authorization code code officially begins. It mainly includes verifying the permission scope (second time), processing the authorization request to generate the authorization code code, and redirecting to the third-party app. Next, let’s analyze these three steps together.

Step 4, Verifying the Permission Scope (Second Time)

In step 2, before generating the authorization page, the authorization service performs the first verification by comparing the permission scope scope requested by Xiaotu with the permissions registered. The second verification of the permission scope here is to compare the permissions granted by Xiaoming after authorization with the permissions registered by Xiaotu.

Why do we need to verify it again here? Because this is equivalent to the user inputting the permissions. Xiaoming has selected a certain scope of permissions to grant to the authorization service, and we need to take the verification of permissions seriously. Any input data will involve validity checks. In addition, this also requires us to develop a good habit of performing validity checks on input data requests on the server side as much as possible.

String[] rscope = request.getParameterValues("rscope");
if(!checkScope(rscope)){
}

Step 5, Processing the Authorization Request and Generating the Authorization Code code

After Xiaoming agrees to the authorization, the authorization service verifies the value of the response type response_type. The response_type has two types of values: code and token. Here, we are using the authorization code flow as an example, so the code needs to verify that the value of response_type is code.

String responseType = request.getParameter("response_type");
if("code".equals(responseType)){
}

In the authorization service, the generated authorization code code needs to be mapped to app_id and user. In other words, an authorization code code represents an authorization given by a user to a third-party app, such as Xiaoming granting authorization to Xiaotu app. At the same time, we need to save the code value and this mapping relationship to use when generating the access token access_token.

String code = generateCode(appId,"USERTEST");
private String generateCode(String appId,String user) {
  	...
    String code = strb.toString();
    codeMap.put(code, appId + "|" + user + "|" + System.currentTimeMillis());
    return code;
}

// After generating the authorization code `code`, we bind the corresponding mapping relationship as mentioned above. Do you remember that I mentioned earlier that the authorization code is temporary and one-time credentials? Therefore, we also need to set an expiration time for the code.

// The OAuth 2.0 specification recommends that the authorization code `code` be valid for 10 minutes and that **an authorization code `code` can only be used once**. However, based on experience, the validity period of the code in a production environment is generally no more than 5 minutes. I will explain more about the security aspects of the authorization code `code` in detail in lecture 8.

// At the same time, the authorization server also needs to **bind and store the generated authorization code `code` with the authorized scope `rscope`**, so that when issuing the access token later, we can retrieve the authorization scope by the code value and bind it with the access token. Because the third-party software ultimately requests protected resources through access tokens.

Map<String, String[]> codeScopeMap = new HashMap<>();
codeScopeMap.put(code, rscope);

// Step 6, redirect to the third-party software.

After generating the authorization code `code`, the authorization server needs to inform the third-party software, Little Rabbit, of that code. As mentioned earlier, issuing the authorization code `code` is done through front-end communication, so we use the method of redirection here. This redirection step is also the second redirection I mentioned in the previous lecture.

Map<String, String> params = new HashMap<>();
params.put("code", code);
String toAppUrl = URLParamsUtil.appendParams(redirectUri, params);
response.sendRedirect(toAppUrl);

So far, the process of issuing the authorization code `code` is complete. When Little Rabbit obtains the authorization code `code`, it can start requesting the access token `access_token`, which is the second process we are about to begin.

### Process 2: Issuing the Access Token `access_token`

In process 1, we introduced the process of generating the authorization code `code`. However, Little Rabbit ultimately needs to obtain the access token `access_token` in order to request protected resources. The authorization code, as I mentioned in the previous lecture, is only a temporary credential for exchanging the access token `access_token`.

When Little Rabbit comes to make a request with the authorization code `code`, the authorization server needs to generate the final request access token. This process mainly includes verifying the existence of the third-party software, validating the legality of the code value, and generating the access token value. Next, let's analyze each step together.

Step 1: Verify the existence of the third-party software.

At this time, the received `grant_type` type is `authorization_code`.

String grantType = request.getParameter("grant_type");
if ("authorization_code".equals(grantType)) {

Since issuing the access token is done through backend communication, here we need to verify the existence of not only `app_id` but also `app_secret`.

if (!appMap.get("app_id").equals(appId)) {
if (!appMap.get("app_secret").equals(appSecret)) {
}

Step 2: Verify the legality of the authorization code code.

During the issuance of the authorization code code, the authorization server has already stored the code value. At this point, we compare the code value received from the request with the code value retrieved from the storage. In the related code provided in our course, the key corresponding to the code value is a combination of app_id and user.

String code = request.getParameter("code");

if (!isExistCode(code)) {
    return;
}
codeMap.remove(code);

Here, we must remember that after confirming the validity of the authorization code code, the current code value should be immediately removed from storage to prevent a third-party software from maliciously using a stolen authorization code code to request the authorization server.

Step 3: Generate the access token access_token.

The OAuth 2.0 specification does not explicitly specify the rules for generating the access token access_token, but it must comply with three principles: uniqueness, non-continuity, and unguessability. In our provided demo, we use UUID as an example.

Like the authorization code code, we need to store the access token access_token, and associate it with the application identifier app_id and resource owner identifier user of the third-party software. In other words, an access token access_token represents an authorization given by a user to a third-party software.

At the same time, the authorization server also needs to bind the authorization scope to the access token access_token. Finally, the access token needs to be given an expiration time expires_in, such as 1 day.

Map<String,String[]> tokenScopeMap = new HashMap<String, String[]>();
String accessToken = generateAccessToken(appId, "USERTEST");
tokenScopeMap.put(accessToken, codeScopeMap.get(code));

private String generateAccessToken(String appId, String user) {
    String accessToken = UUID.randomUUID().toString();
    String expires_in = "1";
    tokenMap.put(accessToken, appId + "|" + user + "|" + System.currentTimeMillis() + "|" + expires_in);
    return accessToken;
}

Because the OAuth 2.0 specification does not restrict the generation rules for the access token content, we have more freedom. We can generate data in the form of a UUID, as shown in the demo, and share it between the authorization server and the protected resource. We can also put some necessary information into the token itself through structured processing. We call tokens that contain some information structured tokens, or JWT for short. In the next lesson, I will explain JWT in more detail.

So far, I have finished explaining the two main processes of the authorization server in the authorization code grant type, which are the issuance of authorization code and the issuance of access token.

When you read other people’s authorization flow code or use third-party software that uses platforms like WeChat for login, you will understand the underlying principles. At the same time, when you build your own authorization server flow, you will also be more proficient. All of this is because the issuance of authorization code and the issuance of access token are the core of the authorization server.

At this point, you may have noticed a question. When generating an access token, we also attach an expiration time expires_in to it, which means that the access token will become invalid after a certain period of time. When the access token becomes invalid, it means that the authorization given by the resource owner to the third-party software is also invalid, and the third-party software cannot continue to access the resource owner’s protected resources.

At this point, if you still want to continue using the third-party software, you can only click the authorization button again. For example, after Xiao Ming granted authorization to the Little Rabbit software and was happily processing the order data for his store, suddenly the Little Rabbit software asked Xiao Ming to authorize again. At this moment, let’s imagine how Xiao Ming feels.

Obviously, this kind of user experience is very poor. To address this, OAuth 2.0 introduces the concept of refresh tokens, which refresh the value of the access token access_token. This means that with a refresh token, users can continue using the third-party software without having to click the authorization button again within a certain period of time.

Next, let’s take a look at how the refresh token works together.

Refresh Token #

Refresh tokens are also used by third-party software and follow the principle of “grant before use”. Therefore, we will learn about refresh tokens from the perspective of issuing and using them. However, the process of issuing and using refresh tokens is similar to that of access tokens, so I will focus on explaining the differences.

Issuing Refresh Tokens #

In fact, issuing refresh tokens is done together with issuing access tokens, both of which are generated in step 3 of process 2 to generate the access token access_token. In other words, when the third-party software obtains an access token, it will also receive a refresh token:

Map<String, String> refreshTokenMap = new HashMap<String, String>();
String refreshToken = generateRefreshToken(appId, "USERTEST");
private String generateRefreshToken(String appId, String user) {
    String refreshToken = UUID.randomUUID().toString();
    refreshTokenMap.put(refreshToken, appId + "|" + user + "|" + System.currentTimeMillis());
    return refreshToken;
}

You may wonder why we generate access tokens and refresh tokens together?

In fact, this brings us to the purpose of refresh tokens. Refresh tokens are intended to be used when the access token expires, in order to generate a new access token through the system without requiring the user to manually authorize again. Therefore, if the access token expires and there is no available refresh token, the user would be inconvenienced by having to manually authorize again. Therefore, the refresh token must be generated together with the access token.

So far, we have solved the issuance of refresh tokens.

Using Refresh Tokens #

When it comes to using refresh tokens, we need to understand one thing. In the OAuth 2.0 specification, a refresh token is a special type of authorization grant, which is a special type embedded under the authorization code grant type. In the code of the authorization service, when we receive a request for this type of authorization grant, we first compare the values of grant_type and refresh_token, and then proceed to the next step.

The process mainly includes the following two steps.

Step 1: Receiving the refresh token request and verifying basic information.

At this time, the value of grant_type in the request is refresh_token.

String grantType = request.getParameter("grant_type");
if ("refresh_token".equals(grantType)) {
}

Similar to the verification process before issuing access tokens, we also need to verify the existence of the third-party software. Note that we also need to verify the existence of the refresh token, in order to ensure the legitimacy of the passed refresh token.

String refresh_token = request.getParameter("refresh_token");
if (!refreshTokenMap.containsKey(refresh_token)) {
}

In addition, we also need to verify whether the refresh token belongs to the third-party software. The authorization service binds the issued refresh token with the third-party software and the authorized user at that time, so we need to check the legitimacy of the refresh token in terms of ownership.

String appStr = refreshTokenMap.get("refresh_token");
if (!appStr.startsWith(appId + "|" + "USERTEST")) {
}

Note that once a refresh token is used, the authorization service needs to invalidate it and issue a new refresh token.

Step 2: Generating a new access token.

The process of generating an access token is the same as in the step of issuing access tokens. The authorization service will return the new access token and refresh token together to the third-party software. I won’t go into details here.

Summary #

Today’s class is about to end, and I have explained to you the working principle of the authorization service under the authorization code grant type. The authorization service can be said to be the “soul” component of the entire OAuth 2.0 system, and no authorization type can do without its support. It is also the most complex component.

This is because it tries to “handle the complexity as much as possible”, making it more convenient for third-party software like Little Rabbit to access OAuth 2.0. As for how to quickly integrate OAuth 2.0, I will elaborate on it in Lesson 5.

The authorization service has several steps, so I have put the code corresponding to this lesson on GitHub, which can help you better understand the process of the authorization service.

In summary, regarding this lesson, I hope you remember the following 3 points.

The core of the authorization service is to “issue the authorization code value first, and then issue the access token value.”

When issuing the access token, “a refresh token value will also be issued at the same time. This mechanism can be used to generate a new access token without user participation.” Just like the example of Xiao Ming using Little Rabbit software, when the access token expires, the presence of the refresh token can greatly improve Xiao Ming’s experience of using Little Rabbit software.

Authorization also needs to have authorization scope, “it should not allow third-party software to obtain more authorization than the scope specified at registration, nor should it obtain authorization beyond the scope authorized by the user, always ensuring the principle of least privilege security.” For example, if Xiao Ming only grants Little Rabbit software the permission to access orders for the current day, then Little Rabbit software cannot access historical order data in Xiao Ming’s store.

Reflection Questions #

Do refresh tokens have an expiration time? Will they remain valid indefinitely? Please share your thoughts.

Feel free to share your opinions in the comment section and also share today’s content with your friends so that we can engage in a discussion together.