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

Saturday, 13 June 2020

Java Best Practices to write readable and maintainable code

From my understanding and experience following are the few best practices to write readable and maintainable code

Variables:

Methods:

Exceptions:


As shown in the above diagram never ever catch Throwable

We can use Throwable in a catch clause, but we should never do it! If we use Throwable in a catch clause, it will not only catch all exceptions; it will also catch all errors. Errors are thrown by the JVM to indicate serious problems that are not intended to be handled by an application

Also we should avoid using directly Exception  in the catch block. Try to handle specific exception
Ex:

try{
      readFile();
    }
catch(FileNotFoundException) { }

In catch block,
1) Do not print stacktrace ,  instead use any logging framework
    Ex : catch(.... ex){
                               log.error(ex);
                               }
2) Throw the exception using custom exception wherever appropriate

Class

The class should meet following important principle
1. Follow SRP - Single Responsibility Principle
2. Program to an Interface
3. Maintain Strong Encapsulation within Class
4. Maintain high Cohesion
5. Always try to have minimum Coupling
6 . Use Dependency Injection
7.  Principle of Proximity i.e. well organized methods

SOLID Principle
Single Responsibility Principle
Open Closed Principle'
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion

Comments

- Use JavaDoc format if necessary
- Always remove commented out code before pushing to version control system i.e svn or git
- Use comments if necessary, it should not compensate for buggy code. Prefer giving comments only for public methods wherever necessary


Tests(Junit/Mockito)

- Always verify one thing per test
- 1 assertion per test (Except-few cases where it is not applicable)
- No if branching in tests

Test should have basic template like as shown below

 Arrange
   - Setup and initialize the objects and the environment
  Act
    - Exercise the functionality under test
 Assert
    - Verify the result

Test Pattern
AAA - Arrange-Act-Assert
BDD - Given - When - Then

Other Points
- Install Static Code Checker like SonarLint 




=====================Reference=================================
Google Java Style Guide
https://google.github.io/styleguide/javaguide.html

Mr. Andrejs Doronins Java Readability & Maintainability Course