Wednesday 2 September 2020

Token Management in Client Credentials Grant Flow - OAuth 2.0

 There are 4 types of Authorization grant in OAuth 2.0

1)Authorization Code

2)Implicit

3)Resource Owner Password Credentials

4)Client Credentials

In this blog post, I would like to give information on "Client Credentials" authorization grant

This type of grant flow is preferable when there is back-end system to another back-end system communication. Ex: In your server side code, you are trying to invoke OIDC based API to get data and process it.

So the overall Authorization flow at high level is would be

Step 1 : Get "x-api-key" from your authorization provider

Step 2: Get Access Token by passing Client ID & Client Credentials

Step 3: Invoke the API by passing the x-api-key & Access Token (By appending "Bearer")


Once you received the Access Token invoke the API by passing the x-api-key and bearer token as shown below



Now lets see about token management.

In other authorization code grant we will get refresh token and by using this, we can do token management. But in client credentials we do not get any refresh token and we only get access token, token type and expires_in value as shown below



It is advisable to get fresh access token in Client Credentials grant. But in case your requirement is  not get fresh access token for every call, then we can use below mentioned approach.

The attribute expires_in is in seconds, that means the received token will be valid for almost 30 min.

So approach will be, we will use same token till the time is less than the expires_in value and after that get fresh token and use it for another 30 min.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package practice;

public class ClientCredentialsTokenManagement {
  private static final String AUTHORIZATION = "Authorization";
  private static final String BASIC = "Basic";
  private static final String SPACE = " ";
  private static final String GRANT_TYPE = "grant_type";
  private static final String X_API_KEY = "x-api-key";
  private static final String X_API_KEY_VAL = "<< x-api-key-val>>";
  private static final String CONTENT_TYPE = "Content-type";
  private static final String ACCESS_TOKEN_URL = "<<access token url>>";
  private static final String CLIENT_ID = "<<client id>>";
  private static final String CLIENT_SECRET = "<<Client secret value >>";
  private static final String CLIENT_CREDENTIALS = "client_credentials";

  // JWT fields
  private static final String EXPIRES_IN = "expires_in";
  private static final String ACCESS_TOKEN = "access_token";

  private long expiresAt;
  private String accessToken;

  public String getAccessToken() {
    return accessToken;
  }

  public void setAccessToken(String accessToken) {
    this.accessToken = accessToken;
  }

  public long getExpiresAt() {
    return expiresAt;
  }

  public void setExpiresAt(long expiresAt) {
    this.expiresAt = expiresAt;
  }

  // Invoking API with token management

  private void invokeAPI() {
    try {
      long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
      if (currentTime < this.getExpiresAt()) {
        LOGGER.info("Using Existing Token");
        result = invokeQueryApi(paramVal, this.getAccessToken());
        getAccessToken();
      } else {
        LOGGER.info("Requesting new token");
        fetchAccessToken();
        result = invokeQueryApi(paramVal, this.getAccessToken());
      }
    } catch (IOException e) {
      LOGGER.error("Exception while invoking Sma4u Service");
    }
  }

  // Method used to get Access Token using Client Credentials grant Flow

  private void fetchAccessToken() throws IOException {
    HttpPost request = new HttpPost(ACCESS_TOKEN_URL);
    String auth = CLIENT_ID + ":" + CLIENT_SECRET;
    Header header = new BasicHeader(AUTHORIZATION,
        BASIC + SPACE + base64UrlEncodeToString(auth.getBytes(StandardCharsets.UTF_8)));
    request.addHeader(header);
    List<NameValuePair> nameValuePairs = new ArrayList<>();
    nameValuePairs.add(new BasicNameValuePair(GRANT_TYPE, CLIENT_CREDENTIALS));
    request.setEntity(new UrlEncodedFormEntity(nameValuePairs, StandardCharsets.UTF_8));
    HttpResponse response = client.execute(request);
    if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
      LOGGER.info("OIDC Access Token obtained in exchange for OIDC Authorization Code !!!");
      BufferedReader reader =
          new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
      String line = null;
      StringBuilder sb = new StringBuilder();
      while ((line = reader.readLine()) != null) {
        sb.append(line);
      }
      JSONObject responsJson = new JSONObject(sb.toString());
      LOGGER.info(responsJson.getString(ACCESS_TOKEN));
      accessToken = responsJson.getString(ACCESS_TOKEN);
      this.setAccessToken(responsJson.getString(ACCESS_TOKEN));
      int val = Integer.parseInt(responsJson.get(EXPIRES_IN).toString());
      Long issuedAt = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + val;
      this.setExpiresAt(issuedAt);
    }
  }

  private String base64UrlEncodeToString(byte[] input) {
    return Base64.getUrlEncoder().encodeToString(input);
  }

}

Hope this helps. Let me know if any concerns

References

https://tools.ietf.org/html/rfc6749#section-1.3.4