4 ApiFest OAuth 2.0 Server

ApiFest OAuth 2.0 Server implements OAuth 2.0 protocol as per the RFC 6749. It is used to secure your API using the OAuth 2.0 protocol, enabling the usage of access tokens in the ApiFest Mapping Server. The ApiFest OAuth 2.0 Server currenlty supports MongoDB, Redis, Cassandra and Hazelcast as a storage.

4.1 Start ApiFest OAuth 2.0 Server

In order to start the ApiFest OAuth 2.0 Server, you should configure several properties. Here is how the configuration file looks like:
oauth20.host=
oauth20.port=
oauth20.database=
db_uri=
redis.sentinels=
redis.master=
apifest-oauth20.nodes=
custom.classes.jar=
user.authenticate.class=
custom.grant_type=
custom.grant_type.class=
hazelcast.password=

In the following table you can find the description of each property, whether it's mandatory and an example value.

property description required example
oauth20.host The host on which the ApiFest OAuth 2.0 Server will run. false, default value localhost oauth20.host=127.0.0.1
oauth20.port The port on which the ApiFest OAuth 2.0 Server will run. false, default value 8080 oauth20.port=8080
oauth20.database The type of the database used by the ApiFest OAuth 2.0 Server - supported values are: redis, mongodb, cassandra and hazelcast false, if not set then Hazelcast will be used as a storage oauth20.database=mongodb
db_uri The URI to the MongoDB. true only if oauth20.database is mongodb db_uri=mongodb://127.0.0.1:27017
redis.sentinels Redis sentinels comma separated list. true only if oauth20.database is redis redis.sentinels=10.32.23.157:26379
redis.master Redis master true only if oauth20.database is redis redis.master=mymaster
apifest-oauth20.nodes A comma-separated list of all ApiFest OAuth 2.0 Server nodes defined by host only. Used to create a Hazelcast cluster in case Hazelcast is used as a storage. false, default value=localhost apifest-oauth20.nodes=10.32.23.157,10.32.23.158
custom.classes.jar The path to the jar with your custom classes - for instance, the class that implements custom user authentication. false* custom.classes.jar=/home/apifest-oauth/custom-classes.jar
user.authenticate.class The fully qualified class that authenticates users. Details about custom user authentication in chapter 4.2 false* user.authenticate.class=com.apifest.example.UserAuthentication
custom.grant_type A custom grant_type (not supported by RFC6749), if needed for any reasons false custom.grant_type=otp
custom.grant_type.class The fully qualified class that authenticates users when custom grant_type is used. false custom.grant_type.class=com.apifest.example.OTPHandler
hazelcast.password Hazelcast password used to secure the Hazelcast nodes (in case Hazelcast is used as a storage).If not defined the default Hazelcast password will be used. false, default Hazelcast password will be used - dev-pass hazelcast.password=securepass
*If no custom.classes.jar and user.authenticate.class is set then user authentication will be successful no matter the username and the password passed, userId returned is 123456. So you are strongly recommended to implement custom user authentication - see more in the next chapter 4.2.

4.2 Create custom authentication

The ApiFest OAuth 2.0 Server is responsible for user authentication and as different applications use different user authentication methods and flows - for instance, some applications may require additional information in order to login the user, the ApiFest OAuth 2.0 Server enables custom authentication. That means you can implement your own user authentication - a class that implements com.apifest.oauth20.api.IUserAuthentication interface. The fully qualified class should be set as user.authenticate.class in the ApiFest OAuth 2.0 Server properties file. The class should be bundled in a jar that should be set as custom.classes.jar in the properties file.
Let's see an example. Let's say we have a user authentication service in the backend application available on http://127.0.0.1:5000/users/authenticate. The service expects a POST request with username and password in JSON format and if the authentication is successful returns JSON response that contains user_id. Then the class that will be used for user authentication will call that service. Note, that the implementation is a dummy one and it is used only for demo purposes.
public class UserAuthentication implements IUserAuthentication {

    @Override
    public UserDetails authenticate(String username, String password, HttpRequest authRequest) throws AuthenticationException {
        UserDetails userDetails = null;
        HttpPost post = new HttpPost("http://127.0.0.1:5000/users/authenticate");
        JsonObject json = new JsonObject();

        json.addProperty("username", username);
        json.addProperty("password", password);

        try {
            post.setEntity(new StringEntity(json.toString(), "UTF-8"));
            post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
            HttpClient client = HttpClientBuilder.create().build();
            HttpResponse httpResponse = client.execute(post);

            if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new AuthenticationException("invalid username/password");
            } else {
                userDetails = createUserDetails(httpResponse);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return userDetails;
    }

    private UserDetails createUserDetails(HttpResponse response) {
        UserDetails details = null;
        InputStream input = null;
        try {
            input = response.getEntity().getContent();
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> json = mapper.readValue(input, Map.class);
            details = new UserDetails(json.get("user_id").toString(), null);
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return details;
    }
}

As you can see the method that should implemented in the custom user authentication is authenticate(String username, String password, HttpRequest authRequest). It accepts as parameters username, password and the authentication request posted to the ApiFest OAuth 2.0 Server. For instance, if some headers are required for the user authentication they could be extracted from the authRequest. The authenticate method returns UserDetails object that contains the id of the user (String userId) and some details (Map<String, String> details) related to the user that can be stored along with the access token that will be issued.
In order to use that class for user authentication, it should be bundled in a jar. Let's say in our example the jar is located in /home/apifest/authentication.jar. Then the following properties should look like:
user.authenticate.class=com.apifest.example.UserAuthentication custom.classes.jar=/home/apifest/authentication.jar

Then every time a user access token is issued by the ApiFest OAuth 2.0 Server the UserAuthentication class will be invoked.

4.3 Register a scope

As described in chapter 3.10, a scope in terms of OAuth 2.0 defines the resources access tokens with the given scope can access. The definition of a scope in ApiFest is done in mappings configuration file on the endpoint level, i.e. each endpoint contains the scope/s that it is/are allowed for.
For instance:
<endpoint external="/v0.1/countries" internal="/common/countries" method="GET" scope="basic extended" authType="client-app"/>

That means the resource /v0.1/countries will be accessible with access tokens that have basic or extended scope. Now let's see how a scope could be registered in the ApiFest OAuth 2.0 Server and what properties it has. Here is an example request for scope registration:
POST /oauth20/scopes
Content-Type: application/json
{
  "scope": "basic",
  "description": "basic scope allows ...",
  "cc_expires_in": "300",
  "pass_expires_in": "120",
  "refresh_expires_in": "300"
}

The meaning of each JSON field of the request body is described in the following table:

field description required
scope The name of the scope. A valid name may contain aplha-numeric, - and _ true
description The description ofthe scope. true
cc_expires_in The expiration time(in seconds) of access tokens with grant_type=client_credentials. true
pass_expires_in The expiration time(in seconds) of access tokens with grant_type=password. true
refresh_expires_in The expiration time(in seconds) of refresh access tokens. Note, refreshtokens are issued only for access tokens with grant_type=password. false, the default value is the value of pass_expires_in

attention sign All scope fields except scope could be updated later - see chapter 6.3.1.
attention sign A scope name cannot contain space because the space is used as a delimiter when several scopes are set to a client application and access tokens.

Here are how the different responses look like:
  • Successful response
    Status Code: 200 OK
    Content-Type: application/json
    {"status":"scope successfully stored"}
  • If the scope has been already registered, then the following response will be returned
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"status":"scope already exists"}
  • If some of the mandatory parameters are missing
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error":"scope, description, cc_expires_in and pass_expires_in are mandatory"}
  • If the scope name is not valid
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error":"scope name not valid - it may contain aplha-numeric, - and _"}

4.4 Register a client application

In order to register a client application in the ApiFest OAuth 2.0 Server, you should post a request to /oauth20/applications.

Here is an example request:
POST /oauth20/applications
Content-Type: application/json
{
  "name": "testApp",
  "description": "This is a test app",
  "scope": "basic",
  "redirect_uri": "http://10.46.16.93:8080",
  "client_id": "b9db6d84dc98a895035e68f972e30503d3c724c8",
  "client_secret": "105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838",
  "application_details": {
    "division": "IT",
    "organization": "MM"
  }
}

The meaning of each JSON field of the request body is described in the following table:

field description required
name The name of the client application. true
description The description of the client application. false
scope The scope/s allowed to the client application. If several scopes are allowed they should be separated by space. true
application_details Custom details about the client application represented as a JSON object false
redirect_uri The redirection URI that will be used in case of authorization code flow. true, used only for authorization code flow
client_id A predefined client_id of the client application. false
client_secret A predefined client_secret of the client application. false

attention sign Note, client_id and client_secret fields are used for migration purposes (if you have already client applications registered in another system) and it's not recommended to pass them if you register a new client application.

Here are how the different responses look like:
  • Successful response
    Status Code: 200 OK
    Content-Type: application/json
    {
      "client_id": "b9db6d84dc98a895035e68f972e30503d3c724c8",
      "client_secret": "105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838"
    }
  • If you try to register a client application with predefined client_id and client_secret that have been already registered, then the following response is returned
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error": "already registered client application"}
  • If you try to register a client application with a non-existing scope
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error": "scope does not exist"}
  • If some of the mandatory parameters are missing or invalid
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error": "name, scope or redirect_uri is missing or invalid"}
attention sign By default a registered client application status is 0 - meaning it is not active. In order to issue an access token for that client application, it should be activated first.

Here is an example request for client application activation:
PUT /oauth20/applications/b9db6d84dc98a895035e68f972e30503d3c724c8
Content-Type: application/json
{"status":1}
where b9db6d84dc98a895035e68f972e30503d3c724c8 is the client_id of the client application.

If the client application status is updated successfully, the following response will be returned:
Status Code: 200 OK
Content-Type: application/json
{"status":"client application updated"}

If the client application status is not updated, the following response will be returned:
Status Code: 400 Bad Request
Content-Type: application/json
{"error": "cannot update client application"}

4.5 Obtain an access token

As per the RFC-6749 (http://tools.ietf.org/html/rfc6749), the supported grant types in the ApiFest OAuth 2.0 Server are: authorization_code, client_credentials, password and refresh_token. Let's see how to obtain an access token with each of these grant types.

4.5.1 Authorization code flow

In this flow, an access token is obtained using an authorization code that is obtained as a first step.
Below are example request and response for obtaining an authorization code.
Request:
GET /oauth20/auth-codes?client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&redirect_uri=http://10.46.16.93:8080&response_type=code&scope=basic

Response:
{"redirect_uri": "http://10.46.16.93:8080?code=UXtpWHNWVTdLxQSxfcCOUKuDEZQ%23MXi%3Dqx-BfQiXdwJ-TTeU%3DzBdVtqTSeAWFjDStdTi_Pao%3DhlnytNEwpvTTpigHfQiUYYogccQQrjHWNMBsxdSYudIDlsExausvQlnFzLgODtNlanlo_KKAItavxKallDSzoNVqRZOi%3DcrJ-DzibyMtMMvBZ%23iRbvgDRVG%3DfSTgj%23D"}

Here is a table that describes the query parameters in the authorization code request:

parameter description required
client_id The client_id of the client application an authorization code will be issued for. true
redirect_uri The redirection URI to which the user should be redirected along with the authorization code. true
response_type The response_type should be code for authorization code flow. If the response_type is not code, then the following response will be returned: {"error":"unsupported_response_type"} true
scope The scope/s of the access token that will be issued using that authorization code. The scope/s should be allowed for the client application defined by the client_id. false, if no scope is passed then the client application scope will be used

The second step to issue an access token is to use the authorization code returned in the response of the step 1.

Here are example request and response:
Request:
POST /oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=UXtpWHNWVTdLxQSxfcCOUKuDEZQ%23MXi%3Dqx-BfQiXdwJ-TTeU%3DzBdVtqTSeAWFjDStdTi_Pao%3DhlnytNEwpvTTpigHfQiUYYogccQQrjHWNMBsxdSYudIDlsExausvQlnFzLgODtNlanlo_KKAItavxKallDSzoNVqRZOi%3DcrJ-DzibyMtMMvBZ%23iRbvgDRVG%3DfSTgj%23D&redirect_uri=http://10.46.16.93:8080&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8

Response:
Status Code: 200 OK
Content-Type: application/json
{
  "access_token": "857fd034dabea45bd130ef3f4af4d7929118d91974e00f399335794889404349",
  "refresh_token": "b8d4a3bcecdb53479cf59bcb724840123d795a7a3e6b7af6ea43d532436ff44d",
  "token_type": "Bearer",
  "expires_in": "120",
  "scope": "basic",
  "refresh_expires_in": "300"
}

The following table describes the parameters used to obtain an access token:

parameter description required
grant_type authorization_code true
code The authorization code obtained as a result of the authorization code request. true
redirect_uri The redirection URI used to obtain the authorization code. Note, the redirect_uri must be the same as the one used to obtained authorization code. true
client_id The client_id of the client application an access token will be issued for. true
client_secret The client_secret the client application an access token will be issued for. true

The following table describes the fields in the response:

field description example
access_token The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. 857fd034dabea45bd130ef3f4af4d7929118d91974e00f399335794889404349
refresh_token The refresh token issued for a given request. The refresh token could be used within refresh_expires_in period. The time unit is seconds. b8d4a3bcecdb53479cf59bcb724840123d795a7a3e6b7af6ea43d532436ff44d
token_type The type of the access token. Bearer
expires_in The time within the access token is valid. The time unit is seconds. 120
scope The scope/s of the access token. basic
refresh_expires_in The time within the refresh token is valid. The time unit is seconds. 300

Here are examples for different responses returned by the service.
  • Successful response
    Status Code: 200 OK
    Content-Type: application/json
      "access_token": "a5530044c596681e5e142fa2255bffbf78a2adcfbbaeef0d88a6c1a8407a4cb1",
      "refresh_token": "a4a6ea9f621391731153c025888130f39acc725e05174f30ec50719cfce39cb7",
      "token_type": "Bearer",
      "expires_in": "120",
      "scope": "basic",
      "refresh_expires_in": "300"
    }
  • If redirect_uri does not match the redirect_uri used to obtain the auth code or the auth code is not valid.

    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error": "invalid auth_code"}
attention sign For security reasons, once an authorization code is used to obtain an access token, it is invalidated no matter the access token is issued or not.

4.5.2 Client credentials grant type

Access tokens with grant type client credentials are obtained by posting a request to /oauth20/tokens with grant_type=client_credentials.
Here is an example request:
POST /oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&scope=basic

The following table describes the parameters used to obtain an access token with grant type client_credentials:

parameter description required
grant_type client_credentials true
client_id The client_id of the client application an access token will be issued for. true
client_secret The client_secret the client application an access token will be issued for. true
scope The scope/s of the access token that will be issued. The scope/s should be allowed for the client application defined by the client_id. false, if no scope is passed then the client application scope will be used

Here is an example response:
Status Code: 200 OK
Content-Type: application/json
{
  "access_token": "1182d52650cc5f41418abb5dfec08d98a485818ea027188e6b90bc2a32477e5e",
  "token_type": "Bearer",
  "expires_in": "300",
  "scope": "basic"
}
The following table describes the fields in the response:

field description example
access_token The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. 1182d52650cc5f41418abb5dfec08d98a485818ea027188e6b90bc2a32477e5e
token_type The type of the access token. Bearer
expires_in The time within the access token is valid. The time unit is seconds. 120
scope The scope/s of the access token. basic

Here are examples for different responses returned by the service.
  • Successful response
    Status Code: 200 OK
    Content-Type: application/json
    {
      "access_token": "1182d52650cc5f41418abb5dfec08d98a485818ea027188e6b90bc2a32477e5e",
      "token_type": "Bearer",
      "expires_in": "300",
      "scope": "basic"
    }
  • If the scope passed is not allowed for that client application or the scope does not exist
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"status":"scope not valid"}
  • If the client_id or client_secret is not valid or the client application status is not active
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error": "invalid client_id/client_secret"}

4.5.3 Resource owner password credentials grant

Access tokens with grant type client credentials are obtained by posting a request to /oauth20/tokens with grant_type=password.
Here is an example request:
POST / oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&username=john&password=secret_pass&scope=basic

The following table describes the parameters used to obtain an access token with grant type password:

parameter description required
grant_type password true
client_id The client_id of the client application an access token will be issued for. true
client_secret The client_secret the client application an access token will be issued for. true
username The username of the user the access token will be used for. true
password The password of the user the access token will be used for. true
scope The scope/s of th eaccess token that will be issued. The scope/s should be allowed for the client application defined by the client_id. false, if noscope is passed then the client application scope will be used

Here is an example response:
Status Code: 200 OK
Content-Type: application/json
{
  "access_token": "1fa18309dade07a9e6e02ce0a66c0d5f04653309f9a20eec6b4956748d640e91",
  "refresh_token": "540bd3671663cdf6fb24eea5b33fcce3aca67d34d3b4074db4339ee58c1f43d6",
  "token_type": "Bearer",
  "expires_in": "120",
  "scope": "basic",
  "refresh_expires_in": "300"
}
The following table describes the fields in the response:

field description example
access_token The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. 1fa18309dade07a9e6e02ce0a66c0d5f04653309f9a20eec6b4956748d640e91
refresh_token The refresh token issued for a given request. The refresh token could be used within refresh_expires_in period. The time unit is seconds. 540bd3671663cdf6fb24eea5b33fcce3aca67d34d3b4074db4339ee58c1f43d6
token_type The type of the access token. Bearer
expires_in The time within the access token is valid. The time unit is seconds. 120
scope The scope/s of the access token. basic
refresh_expires_in The time within the refresh token is valid. The time unit is seconds. 300

Here are examples for different responses returned by the service.
  • Successful response
    Status Code: 200 OK
    Content-Type: application/json
    {
      "access_token": "1fa18309dade07a9e6e02ce0a66c0d5f04653309f9a20eec6b4956748d640e91",
      "refresh_token": "540bd3671663cdf6fb24eea5b33fcce3aca67d34d3b4074db4339ee58c1f43d6",
      "token_type": "Bearer",
      "expires_in": "120",
      "scope": "basic",
      "refresh_expires_in": "300"
    }
    As you can see the response contains refresh_token and refresh_expires_in fields. A refresh token could be used (within refresh_expires_in period) to obtain a new access token without using username and password. You can see more about the refresh token grant type in chapter 4.5.4.
  • If the scope passed is not allowed for that client application or the scope does not exist
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"status":"scope not valid"}
  • If the client_id or client_secret is not valid or the client application status is not active
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error": "invalid client_id/client_secret"}

4.5.4 Refresh token grant type

Access tokens with grant type client credentials are obtained by posting a request to /oauth20/tokens with grant_type=refresh_token.
Here is an example request:
POST / oauth20/tokens
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&client_id=b9db6d84dc98a895035e68f972e30503d3c724c8&client_secret=105ef93e7bb386da3a23c32e8563434fad005fd0a6a88315fcdf946aa761c838&refresh_token=ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6&scope=basic

The following table describes the parameters used to obtain an access token with grant type refresh_token:

parameter description required
grant_type refresh_token true
client_id The client_id of the client application an access token will be issued for. true
client_secret The client_secret the client application an access token will be issued for. true
refresh_token The value of refresh_token obtained along with an access token issued. true
scope The scope/s of theaccess token that will be issued. The scope/s should be allowed for the client application defined by the client_id and the scope MUST NOT include any scope not originally granted by the resource owner. false, if no scope is passed then the client application scope will be used

Here is an example response:
Status Code: 200 OK
Content-Type: application/json
{
  "access_token": "9ae75e11888d962ce1ffff1fa57cdc2a67380b89f2cfcece0a538ce36b08e5e6",
  "refresh_token": "ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6",
  "token_type": "Bearer",
  "expires_in": "120",
  "scope": "basic",
  "refresh_expires_in": "300"
}
The following table describes the fields in the response:

field description example
access_token The access token issued for a given request. The access token could be used within expires_in period. The time unit is seconds. 9ae75e11888d962ce1ffff1fa57cdc2a67380b89f2cfcece0a538ce36b08e5e6
refresh_token The refresh token issued for a given request. The refresh token could be used within refresh_expires_in period. The time unit is seconds. ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6
token_type The type of the access token. Bearer
expires_in The time within the access token is valid. The time unit is seconds. 120
scope The scope/s of the access token. basic
refresh_expires_in The time within the refresh token is valid. The time unit is seconds. 300

Here are examples for different responses returned by the service.
  • Successful response
    Status Code: 200 OK
    Content-Type: application/json
    {
      "access_token": "9ae75e11888d962ce1ffff1fa57cdc2a67380b89f2cfcece0a538ce36b08e5e6",
      "refresh_token": "ed716dd600cffb596a53779832149bc37f7a0f6e6bb4a1c3df870697599229d6",
      "token_type": "Bearer",
      "expires_in": "120",
      "scope": "basic",
      "refresh_expires_in": "300"
    }
    attention sign When a refresh token of an access token is used, the access token will be invalidated and the new one should be used instead.
    As you can see the response contains refresh_token and refresh_expires_in fields. A refresh token could be used (within refresh_expires_in period) to obtain a new access token without using username and password.
  • If the scope passed is not allowed for that client application, the scope does not exist or the scope is not allowed for the original access token issued
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"status":"scope not valid"}
  • If the client_id or client_secret is not valid or the client application status is not active
    Status Code: 400 Bad Request
    Content-Type: application/json
    {"error": "invalid client_id/client_secret"}

4.6 Define a custom grant_type

The ApiFest OAuth 2.0 Server enables you to define an additional to the RFC6749 (http://tools.ietf.org/html/rfc6749) grant type, if for some reasons you need one.
Let's say we need to define an OTP custom grant type. For that reason, set the value of the custom.grant_type property in the properties file to otp. Also, a custom class that handles that flow should be implemented. The class must implement com.apifest.oauth20.api.ICustomGrantTypeHandler interface defined in apifest-oauth20-api.
Here is an example class that handles a custom grant type.
public class OTPGrantTypeHandler implements ICustomGrantTypeHandler {

    @Override
    public UserDetails execute(HttpRequest request) throws AuthenticationException {

        // do some specific checks
        doChecks(request);

        //call OTP Service
        HttpResponse response = callOTPService(request);

        //construct the appropriate user details
        UserDetails userDetails = createUserDetails(response);

        return userDetails;
    }

    private void doChecks(HttpRequest request) {
     ...
    }
  
    private UserDetails createUserDetails(HttpResponse response) {
       ...
    }

    private HttpResponse callOTPService(HttpRequest request) {
     ...
    }
}

The class should be bundled in the same jar where the implementation of the custom authentication is. The fully qualified name of the class should be set to custom.grant_type.class property. Let's say the name of the jar is custom-classes.jar, then for custom grant type activation the following properties should be set in the ApiFest OAuth 2.0 Server properties file:
custom.classes.jar=/home/apifest/custom-classes.jar
custom.grant_type=otp
custom.grant_type.class=com.apifest.example.OTPGrantTypeHandler