05 How to Securely and Quickly Integrate Oauth 2.0

05 How to Securely and Quickly Integrate OAuth 2 #

Hello, I am Wang Xindong.

In [Lesson 3], I have already explained the authorization service flow. If you remember, I specifically emphasized that the authorization service handles all the complexity of OAuth 2.0, which is one of the reasons why the authorization service is at the core of the OAuth 2.0 framework.

Although the authorization service does most of the work, within the OAuth 2.0 framework, there are two other system roles besides the resource owner, which are the third-party software and the protected resource service. In today’s lesson, let’s take a look at what these two roles should do in order to be integrated into the OAuth 2.0 framework.

Now, let’s see what specific tasks need to be addressed by Little Rabbit as the third-party software and JD as the protected resource service.

Note: Additionally, for the purpose of data protection, in the following explanation, I will only use JD Open Platform as a role to provide a sense of scenario to help you understand.

Building Third-Party Software Applications #

Let’s first think about what work the developers of the Little Rabbit software should do if they want to build an application based on the JD Seller Open Platform.

First, they need to apply to register as a developer on the JD Seller Open Platform. After becoming a developer, they can create an application and start the development process.

During the development of a third-party software application, what should we focus on?

Let me summarize these points for you, which include 4 parts: registration information, authorization guidance, using access tokens, and using refresh tokens.

image

Figure 1: Contents to pay attention to when developing third-party software applications

Firstly, registration information.

Firstly, the Little Rabbit software needs to have its own identity in order to participate in the OAuth 2.0 process. In other words, the Little Rabbit software needs to have its own app_id, app_serect, and other information, as well as fill in its callback address redirect_uri and apply for permissions.

This type of registration is often referred to as static registration. It means that the developers of the Little Rabbit software manually register on the JD Seller Open Platform in advance, so that they can later use the registered information to request access tokens.

Secondly, authorization guidance.

When users need to use third-party software to operate their data on protected resources, they need the third-party software to guide them through the authorization process. For example, if Xiaoming wants to use the Little Rabbit software to process and print orders in his store, Xiaoming must first access the Little Rabbit software (in principle, he should directly access the third-party software, but when we talk about scenarios like the service market later, there will be slight differences), not the authorization service, let alone the protected resource service.

However, the Little Rabbit software needs Xiaoming’s authorization, and only the authorization service can allow Xiaoming to do so. Therefore, the first thing the Little Rabbit software needs to do, in “collaboration” with Xiaoming, is to guide Xiaoming to the authorization service, as shown in the code below.

What does it do? Essentially, it prompts the user to authorize the third-party software. Once authorization is obtained, the third-party software can represent the user in accessing data. In other words, after the Little Rabbit software obtains authorization, it can act on behalf of Xiaoming to process his order data on JD Seller.

String oauthUrl = "http://localhost:8081/OauthServlet-ch03?reqType=oauth";
response.sendRedirect(toOauthUrl);

Thirdly, using access tokens.

Obtaining tokens and using them is the ultimate goal of third-party software. Now let’s see how to use the access token. Currently, OAuth 2.0 tokens only support one type, which is the bearer token, as I mentioned before, it can be any string format token.

The official specification provides three ways to request and use an access token, they are:

  • Form-Encoded Body Parameter

  • POST /resource HTTP/1.1

  • Host: server.example.com

  • Content-Type: application/x-www-form-urlencoded access_token=b1a64d5c-5e0c-4a70-9711-7af6568a61fb

    URI Query Parameter GET /resource?access_token=b1a64d5c-5e0c-4a70-9711-7af6568a61fb HTTP/1.1 Host: server.example.com

    Authorization Request Header Field GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer b1a64d5c-5e0c-4a70-9711-7af6568a61fb

    This means that any of these three methods can be used to request the protected resource. So, which method is the most suitable?

    According to the official recommendation of OAuth 2.0, the request payload before integrating OAuth 2.0 should be in JSON format. If the form parameter submission method is continued, the token cannot be added because it does not match the format. If the parameter transmission method is used during this time, the entire URI will be copied together, which is the least secure. On the other hand, using the authorization header field does not have the above “trouble”, so the official recommendation is to use the Authorization method to pass the token.

    However, I suggest that you use form submission, that is, the POST method, to submit the token, as shown in the code below. The reason is this, which can also be seen from the official recommendation: it refers to the situation before integrating OAuth 2.0. If you have already adopted the JSON data format request body, it is not recommended to use form submission. However, at the beginning, as long as the three-party software and platform have agreed to use form submission, there is no problem at all. Because the form submission method guarantees secure transmission without the need to process additional Authorization header information.

    String protectedURl = “http://localhost:8082/ProtectedServlet-ch03”; Map<String, String> paramsMap = new HashMap<String, String>(); paramsMap.put(“app_id”, “APPID_RABBIT”); paramsMap.put(“app_secret”, “APPSECRET_RABBIT”); paramsMap.put(“token”, accessToken); String result = HttpURLClient.doPost(protectedURl, HttpURLClient.mapToStr(paramsMap));

    The fourth point is to use a refresh token.

    As I mentioned when talking about the authorization service, if the access token expires, the Rabbit ordering software cannot immediately prompt and ask Rabbit to authorize again, otherwise Rabbit’s experience will be very bad. To solve this problem, we use refresh tokens.

    The use of refresh tokens is the same as using access tokens. You can refer to the method we discussed above for access tokens. Regarding the use of refresh tokens, what you need to focus on is when you will decide to use the refresh token.

    When the Rabbit Ordering Software receives the access token, it also receives the expiration time of the access token (expires_in). A well-designed third-party application should save the expires_in value and periodically check it. If it is found that expires_in is about to expire, you need to use the refresh_token to re-request the authorization service in order to obtain a new and valid access token.

    This periodic checking method can detect whether the access token is about to expire in advance. In addition, another method is “on-site” discovery. That is, when the Rabbit Ordering Software is accessing Rabbit’s store orders, it suddenly receives a response stating that the access token has expired. At this time, the Rabbit Ordering Software immediately uses the refresh_token to request a new access token in order to continue accessing Rabbit’s data on behalf of Rabbit.

    Taking both methods into account, the periodic checking method requires us to develop an additional timer task. On the other hand, “on-site” discovery does not require this additional workload. You can choose which method to use based on your actual situation. However, I still recommend using the periodic checking method because it can provide an “advance” so that we have better initiative, while on-site discovery is a bit passive.

    Now, I need to remind you once again that refresh tokens are one-time use and become invalid after being used, but their validity period is longer than that of access tokens. At this time, you may think of what if the refresh token also expires? In this case, we need to abandon both the refresh token and the access token, which is equivalent to returning to the initial state of the system and requiring Rabbit to authorize again.

    In conclusion, when building a third-party application, you need to focus on registration, authorization, access tokens, and refresh tokens. As long as you master these four parts, developing Rabbit Ordering Software on an open platform like Jingdong is no longer a difficult task.

Third-party applications in the service marketplace #

When guiding the authorization of third-party applications, we often say that the first time the user “touches” is definitely the third-party software. However, this is not always the case, especially in the context of a service marketplace.

So what is a service marketplace? To put it simply, it is a “market” where software you develop, such as the Little Rabbit Shipping Software or Store Decoration Software, is published for sale. After purchasing these software, users can see a “Use Now” button in the service marketplace. By clicking this button, users can directly access the third-party software they have purchased.

For example, the “My Services” section in JD’s Jingmai Service Marketplace stores the shipping software I have purchased. Little Ming can simply click “Use Now” and enter the Little Rabbit Shipping Software, as shown in the following image.

Figure 2: My Services in JD’s Jingmai Service Marketplace

It is important to note that as a third-party developer building third-party software, when going through the authorization process, in addition to receiving the authorization code, you also need to collect relevant information about the user’s subscription, such as the version number of the service and the service code identifier.

Alright, these are some details to pay attention to when building third-party software. Next, let’s discuss what key tasks need to be handled when building protected resource services.

Building Protected Resource Services #

First of all, let’s think about it. In the entire environment of open authorization, protected resources ultimately refer to Web APIs, such as the API for accessing avatars and the API for accessing nicknames. In the context of our shipping software, protected resources refer to APIs such as order query API and batch query API.

Communication between systems on the Internet is mostly carried out in the form of Web APIs. Therefore, when we say that protected resources are protected by authorization services, we are actually referring to the fact that these Web APIs are protected by the authorization service. When building protected resource services, besides checking the legitimacy of tokens, what else do we need to do? I believe that the most important aspect is the scope of permissions.

When processing the logic in protected resource services, verifying permissions will account for a significant portion. Think about it, when a token is passed to you, you definitely want to see what functionalities the token can access and what data it can retrieve. Now, let’s summarize these permission categories. The most common ones are as follows.

Figure 3 - Three categories of permissions

Next, let me explain how these permissions are used.

Here, operations correspond to Web APIs. For example, the JD Seller Open Platform currently provides three APIs: product query API, product creation API, and product deletion API. If the scope of the access_token associated with the access request from the Little Rabbit software only includes the product query API and product creation API, then the request containing this access_token cannot perform the product deletion API operation.

String[] scope = OauthServlet.tokenScopeMap.get(accessToken);
StringBuffer sbuf = new StringBuffer();
for(int i=0;i<scope.length;i++){
    sbuf.append(scope[i]).append("|");
}
if(sbuf.toString().indexOf("query")>0){
    queryGoods("");
}
if(sbuf.toString().indexOf("add")>0){
    addGoods("");
}
if(sbuf.toString().indexOf("del")>0){
    delGoods("");
}

Here, data refers to the attribute fields included in an API. For example, if there is an API that queries information about a person named Xiao Ming, the returned information includes the Contact (email, phone, qq), Like (Basketball, Swimming), and Personal Data (sex, age, nickname). If the scope of the access_token associated with the access request from the Little Rabbit software only corresponds to the Personal Data, then the request containing this access_token cannot retrieve the Contact and Like information. The code related to this part is similar to the code that maps different permissions to different operations.

By now, you may have realized that the granularity of these permissions in terms of scope is smaller compared to the granularity of “different permissions correspond to different operations”. This follows the principle of least privilege.

What does this type of permission mean? In fact, this type of permission is just another dimension that is positioned on the user.

Some basic information, such as obtaining locations or weather forecasts, does not have a user attribute, meaning this information does not belong to any specific user and is public information. The APIs provided by platforms for this type of information are “neutral”, without any user-specific attributes.

However, more often, scenarios are based on user attributes. Let’s take the Little Rabbit shipping software as an example. When a merchant prints a logistics label, the Little Rabbit software needs to know which merchant the order belongs to. In this case, the merchant authorizes the Little Rabbit software, and the access_token obtained by the Little Rabbit software contains the user attribute of the merchant.

When the JD Seller Open Platform’s protected resource service receives a request from the Little Rabbit software, it will find the corresponding merchant ID based on the value of the access_token in the request, and then query the merchant’s order information based on the merchant ID. This means different merchants correspond to different order data.

String user = OauthServlet.tokenMap.get(accessToken);
queryOrders(user);

While discussing the three types of permissions above, the examples I gave actually belong to a system that provides services such as querying, adding, and deleting. At this point, you may wonder. The current system is already in a distributed system environment. If there are many protected resource services, such as a user resource service that provides user information query, a product resource service that provides product query, and an order resource service that provides order query, doesn’t each protected resource service need to perform the permission scope verification described above, resulting in a lot of redundant work?

I’m glad you thought of this point. To cope with this situation, we should have a unified gateway layer to handle such verifications. All requests will go through the API GATEWAY to reach different protected resource services. With this approach, we don’t need to perform permission verification in every protected resource service. Instead, we only need to perform it at the API GATEWAY layer. The system architecture is as shown in the following diagram.

Figure 4 - Permission verification handled by a unified gateway layer

Summary #

By the end of this lecture, we have covered all the relevant content related to the authorization code process in OAuth 2.0. Through lectures 02 to 05, you should have a clear understanding of the core principles of the authorization code flow and how to use and integrate this authorization process.

At the beginning of this lecture, I mentioned that the complexity of OAuth 2.0 is actually handled by the authorization service. Then, from the perspective of third-party software and protected resources, I introduced the aspects that these two systems should pay attention to when integrating OAuth 2.0. To summarize, I actually hope you can remember the following two points:

For third-party software, such as the Little Rabbit Order Software, its main purpose is to obtain an access token and use the access token. This is, of course, the purpose of OAuth 2.0 as a whole, to let third-party software perform these two tasks. In this process, it is important to note that third-party software can use the access token in three ways, and we recommend using the Post form submission method as a priority, after agreeing with the platform and third-party software.

For the protected resource system, such as the Little Rabbit Software that needs to access the order data service of the platform, it needs to pay attention to the issue of permissions. This permission scope mainly includes that different permissions allow different operations, different permissions correspond to different data, and different users correspond to different data.

Thought question #

If using a refresh token refresh_token to request a new access token access_token, according to general rules, the old access token should be invalidated immediately. However, what if there are requests made using the old access token before this happens? Wouldn’t these requests be affected? How should this situation be handled?

Feel free to share your thoughts in the comment section below. You are also welcome to share today’s content with other friends so that we can exchange ideas together.