Gen 8.6 Consuming REST APIs: Security
search cancel

Gen 8.6 Consuming REST APIs: Security

book

Article ID: 234770

calendar_today

Updated On:

Products

Gen Gen - Host Encyclopedia Gen - Run Time Distributed Gen - Workstation Toolset

Issue/Introduction

In January of 2022, Gen announced a new enhancement: Consuming REST APIs for Java and CICS applications. This enhancement enables you to use an OpenAPI specification to configure your applications to consume RESTful services. OpenAPI specs can contain a lot of different data, so we started with the most common use cases, like the ability to Get, Post, Put, and Delete. One use case that is not yet fully supported is configuring security schemes that are defined in the Open API spec. Our next add-on to this functionality will be to natively support security schemes like BasicAuth, BearerAuth, ApiKeyAuth, and OAuth2 (Client Credentials Flow). This document explains how to set up your own REST API security schemes using existing functionality, until native support is delivered.

Environment

Gen 8.6
Java applications
COBOL applications on CICS

Resolution

How Does REST API Security Work?

REST is an application architecture that leverages the HTTP protocol to manage resources. HTTP is at the heart of any REST API, which means that REST API Security is HTTP Security. Most HTTP security schemes utilize specially crafted header, query, or cookie parameters that can be implemented using existing Call REST functionality, by adding parameters to your OpenAPI spec operation definitions.

All of the security schemes we talk about in this document utilize basic building blocks that are already supported by the Gen Toolset and runtimes. We will go over the specifics of each security scheme before we show you how to implement it. Also, most of the changes required involve adding definitions of parameters to your OpenAPI spec.

The goal of this document is to show you how to implement REST API security schemes using existing functionality. We will illustrate how to implement API Key Authorization, Basic Authentication and OAuth2 Client Credentials to access a simple secured REST API (detailed below).

Example Ping API

The API we will be consuming for the examples in this document is a simple “ping” service with a single endpoint that returns a string value. Below you will see its OpenAPI spec without any security definitions. Throughout the document, we will show the modifications necessary to implement the different security schemes.

OpenAPI spec for Ping Service without security definitions
​​{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenAPI definition",
    "version": "v0"
  },
  "servers": [{
      "url": "http://ping.example.net:1234",
      "description": "Sample URL for ping service"
    }
  ],
  "paths": {
    "/api/ping": {
      "get": {
        "tags": ["simple-controller"],
        "operationId": "ping",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}

Implementing Basic Authentication

Basic Authentication (basic auth) utilizes the Authorization header to authenticate the user with the target service. To use Basic Auth, you must create a token from your username and password by joining them with a colon (username:password) and then Base64-encoding the entire string. The value for the Authorization header is “Basic {encoded_token}”.

Implement Base64 Encoding

To Base64-encode a text value, you can use an INLINE CODE statement to call a library function. For our example we'll be using Java, which has a Base64 encoder class. For COBOL, you could use inline code to implement your own encoder or call one you may have available via an EAB. The mechanics of the actual security scheme is the same no matter the platform.

This example action block creates a token that is suitable for use in the Authorization header. The only step that must be implemented using inline code is the call to the Java standard utility function to encode the token in Base64.

Action Block Description:
 
    1 +- GENERATE_BASIC_AUTH_HEADER_VAL
    2 |    IMPORTS:
    3 |      Work View   in user_principle (mandatory,transient,import only)
    4 |        passwd (mandatory)
    5 |        user (mandatory)
    6 |    EXPORTS:
    7 |      Work View   out auth_token (transient,export only)
    8 |        name
    9 |        in
   10 |        value
   11 |    LOCALS:
   12 |    ENTITY ACTIONS:
   13 | 
   14 |  SET out auth_token value TO concat(in user_principle user, concat(":", in user_principle passwd))
   15 |  INLINE CODE Language: JAVA Operating System: JVM
   16 |          ##OUT.AUTH_TOKEN.VALUE## = java.util.Base64.getEncoder().encodeToString(##OUT.AUTH_TOKEN.VALUE##.getBytes());
   17 |  SET out auth_token value TO concat("Basic ", out auth_token value)
   18 |  SET out auth_token in TO "HEADER"
   19 |  SET out auth_token name TO "Authorization"
   20 +--
 

Modify OpenAPI spec

Once you can create the Authorization header value, the next step is to update the OpenAPI spec. The only modification required for Basic Auth is the addition of a header parameter named Authorization to the operation. If you already have parameters defined, the header parameter can be added to the list with the other parameters. Here is an example of the change on our ping service, if it was configured for Basic Auth.

OpenAPI spec for Ping Service updated for Basic Auth
{
   ... (omitted for brevity)
  "paths": {
    "/api/ping": {
      "get": {
        "tags": ["simple-controller"],
        "operationId": "ping",
        "parameters": [{
            "name": "Authorization",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}

Remember that you must add this to the parameter list for each operation that requires Basic Auth. If not, the Toolset won’t read the parameter nor prompt you for a value in the Call REST dialog.

Code PAD statements in Toolset

The following is an example of implementing Basic Auth in a Gen model. You must provide the value for the Authorization header as described in the Implement Base64 Encoding section as an IMPORT view attribute for the action block containing the Call REST statement. The Toolset will see the new definition in the OpenAPI spec and create a parameter that must be matched to the proper IMPORT view attribute. If the value you provided is a properly encoded and valid username and password, your call to the Basic-Auth-secured API should succeed.

Action Block Description:
 
    1 +- PING_REST_BASIC
    2 |    IMPORTS:
    3 |      Work View   in user_principle (mandatory,transient,import only)
    4 |        passwd (mandatory)
    5 |        user (mandatory)
    6 |      Work View   in ping_server_config (mandatory,transient,import only)
    7 |        host_url (mandatory)
    8 |    EXPORTS:
    9 |      Work View   out auth_token (transient,export only)
   10 |        value
   11 |      Work View   out pingresp (transient,export only)
   12 |        pingval (nullable)
   13 |        httptext (nullable)
   14 |        httpcode (nullable)
   15 |    LOCALS:
   16 |      Work View   loc ping_server_config
   17 |        host_url
   18 |    ENTITY ACTIONS:
   19 | 
   20 |  USE generate_basic_auth_header_val
   21 |      WHICH IMPORTS: Work View   in user_principle  TO Work View   in user_principle
   22 |      WHICH EXPORTS: Work View   out auth_token  FROM Work View   out auth_token
   23 |  SET loc ping_server_config host_url TO concat(in ping_server_config host_url, ":11002")
   24 |  USE ping_rest_auth_hdr
   25 |      WHICH IMPORTS: Work View   out auth_token  TO Work View   in auth_token
   26 |                     Work View   loc ping_server_config  TO Work View   in ping_server_config
   27 |      WHICH EXPORTS: Work View   out pingresp  FROM Work View   out pingresp
   28 +--
 
Action Block Description:
 
    1 +- PING_REST_AUTH_HDR
    2 |    IMPORTS:
    3 |      Work View   in auth_token (mandatory,transient,import only)
    4 |        value (mandatory)
    5 |      Work View   in ping_server_config (mandatory,transient,import only)
    6 |        host_url (optional)
    7 |    EXPORTS:
    8 |      Work View   out pingresp (transient,export only)
    9 |        pingval (nullable)
   10 |        httptext (nullable)
   11 |        httpcode (nullable)
   12 |    LOCALS:
   13 |    ENTITY ACTIONS:
   14 | 
   15 |  CALL REST Web Service "PINGBASIC:GET:/api/ping"
   16 +--





Implementing API Key Authorization

API Key Authorization (Auth) is a common security scheme in “back-channel” APIs where services or applications request data from another service (3rd party or internal). Normally, API Key Auth is achieved by retrieving the API Key from a secure location and inserting that value into a header, query, or cookie parameter before making an HTTP request. API Key auth is simpler to implement than Basic Auth because the Toolset already has support for the OpenAPI apiKey security scheme and it doesn’t require Base64 encoding.

Modify OpenAPI spec

To define API Key Auth, you must define a securityScheme and refer to that scheme in your operation. This is what that would look like for the ping service, if it was configured for API Key Auth. Since the Gen Toolset already understands API Key Auth, you don’t need to manually add a parameter to your spec for the API Key.

OpenAPI spec for Ping Service updated for API Key Auth
{
  ... (omitted for brevity)
  "paths": {
    "/api/ping": {
      "get": {
        "tags": ["simple-controller"],
        "operationId": "ping",
        "security": [{
            "api_key": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "api_key": {
        "type": "apiKey",
        "description": "API Key access",
        "name": "API-KEY",
        "in": "header"
      }
    }
  }
}

Code PAD statements in Toolset

The following is an example of implementing API Key Auth in a Gen model. You must provide the value for the API key parameter as an IMPORT view attribute for the action block containing the Call REST statement. When the Toolset parses the OpenAPI spec, it will automatically add the parameter for the API Key value. You only need to match that parameter to the IMPORT view attribute that contains the API key token. In the example below, the API Key is a header parameter named API-KEY and it is matched to the auth_token->value IMPORT view attribute.

Action Block Description:
 
    1 +- PING_REST_APIKEY
    2 |    IMPORTS:
    3 |      Work View   in ping_server_config (optional,transient,import only)
    4 |        host_url (mandatory)
    5 |      Work View   in auth_token (mandatory,transient,import only)
    6 |        value (mandatory)
    7 |    EXPORTS:
    8 |      Work View   out pingresp (transient,export only)
    9 |        httptext (nullable)
   10 |        httpcode (nullable)
   11 |        pingval (nullable)
   12 |    LOCALS:
   13 |    ENTITY ACTIONS:
   14 | 
   15 |  CALL REST Web Service "PINGKEY:GET:/api/ping"
   16 +--





Implementing OAuth2 Client Credentials Flow

OAuth2 is a large framework consisting of multiple flows to implement authorization. One flow that is very commonly used between back-channel APIs is the client credentials flow. When we say OAuth from here forward, we are referring to the OAuth2 Client Credentials flow.

OAuth is the most complicated of the security schemes we have gone over, but can still be implemented quite easily in Gen. All that is required is combining the techniques from Basic Auth and API Key Auth. In a nutshell, your application must request a token from an authorization server, using Basic Auth to authenticate. The token is then used to authenticate with the remote service, normally as a Bearer token. Specifically, the value “Bearer {auth_token}” is placed into the Authorization header to inform the target service (in this case our ping service) that you wish to authenticate using a Bearer token. So, to successfully complete your desired Call REST statement, you just need to execute an additional Call REST statement to retrieve the token from the authorization server.

Since OAuth is a framework/standard that is implemented by multiple vendors (Okta, Google, Twitter, Slack, etc.) the exact way you communicate with an authorization server will vary slightly. The authorization server in our example comes from the open-source community but sticks close to the standard. You should always follow the documentation for the authorization server your organization utilizes, but you want to make sure that you request a token from the “token endpoint”. This is a standard endpoint in the OAuth framework and should be present in any compliant implementation.

If you still have questions about OAuth2, there are some really good resources on the Web, and Okta has a number of good articles on, including one on the client credentials flow.

Create Auth Server OpenAPI spec

You will probably not have an OpenAPI spec for your authorization server, so you must create one. The one for our example is pasted below. This should be a good starting point for your own definition, since OAuth is a standard. However, you will need to consult the documentation for your specific server or contact your system administrators for more exact details.

There are a few important things to note in the definitions. First, we have an Authorization header so we can perform Basic Authentication. The username and password you use should be the client_id and client_secret values that are established during the client registration process. If your target service is utilizing OAuth for authentication, this information should be readily available (as well as scopes, which we’ll get to shortly).

The value of grant_type should always be client_credentials for the Client Credentials flow. Lastly, the scope parameter may or may not be required. The documentation for your target service will list the OAuth scopes that are required to access the endpoint you want to consume. In the ping service, the required scope is ping.read, so that will be the value you’ll see later during implementation. It is valid to leave the scope off and let the authorization server provide the default scopes. In practice, the scope(s) required to access a specific endpoint/operation should be documented in the OpenAPI spec for the service.

OpenAPI spec for OAuth2 Auth Server
{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenAPI definition",
    "version": "v0"
  },
  "servers": [{
      "url": "http://auth.example.net:1234",
      "description": "Sample URL for OAuth2 authorization server"
    }
  ],
  "paths": {
    "/oauth2/token": {
      "post": {
        "tags": ["oauth-controller"],
        "operationId": "token",
        "parameters": [{
            "name": "Authorization",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }, {
            "name": "grant_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }, {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          },
          "token_type": {
            "type": "string"
          },
          "expires_in": {
            "type": "number",
            "format": "int32"
          }
        }
      }
    }
  }
}

Modify existing OpenAPI spec

In addition to the new spec for the OAuth auth server, you’ll need to modify the existing spec for the ping service. Identically to Basic Auth, you need to add a header parameter named Authorization to the operation. Once this is in place, you can proceed to implementing your change in the Gen model.

OpenAPI spec for Ping Service updated for OAuth
{
  ... (omitted for brevity)
  "paths": {
    "/api/ping": {
      "get": {
        "tags": ["simple-controller"],
        "operationId": "ping",
        "parameters": [{
            "name": "Authorization",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}

Code PAD statements in Toolset

To successfully execute the OAuth-secured Call REST statement you’ll need a few pieces of information sent in as view attributes. The client_id and client_secret are mandatory, so you can access the authorization server using Basic Auth. In our example we provide a view attribute for the ping service URL, though this is not required.

The first call is made to the authorization service using the grant_type and scope values as discussed previously. Also, a basic auth token must be created using the client_id and client_secret. The output from the call to the authorization server is a bearer token. This is an opaque string token, and its contents do not really matter to the Gen application. Once you have the bearer token, you must concatenate the string “Bearer ” (space is required) and bearer token value. That is the value that is set in the Authorization header parameter for the call to the ping service.

The ping service will recognize the token in the Authorization header, and if it can verify the token’s validity it will allow the request to be fulfilled. If not, you will likely receive a 401 error.

That’s all there is to it. In just a few short steps, you can fully implement a secure OAuth2 flow utilizing the functionality provided by the Consuming REST APIs enhancement.

Action Block Description:

    1 +- PING_REST_OAUTH
    2 |    IMPORTS:
    3 |      Work View   in user_principle (mandatory,transient,import only)
    4 |        passwd (mandatory)
    5 |        user (mandatory)
    6 |      Work View   in ping_server_config (optional,transient,import only)
    7 |        host_url (mandatory)
    8 |    EXPORTS:
    9 |      Work View   out auth_token (transient,export only)
   10 |        name
   11 |        in
   12 |        value
   13 |      Work View   out pingresp (transient,export only)
   14 |        httptext (nullable)
   15 |        httpcode (nullable)
   16 |        pingval (nullable)
   17 |    LOCALS:
   18 |      Work View   loc ping_server_config
   19 |        host_url
   20 |      Work View   loc http_response
   21 |        code_text (nullable)
   22 |        code (nullable)
   23 |      Work View   loc oauth_token
   24 |        expires_in_secs (nullable)
   25 |        auth_tok (nullable)
   26 |      Work View   oauth_token_req
   27 |        scopes
   28 |        grant_type
   29 |    ENTITY ACTIONS:
   30 | 
   31 |  USE generate_basic_auth_header_val
   32 |      WHICH IMPORTS: Work View   in user_principle  TO Work View   in user_principle
   33 |      WHICH EXPORTS: Work View   out auth_token  FROM Work View   out auth_token
   34 |  SET oauth_token_req grant_type TO "client_credentials"
   35 |  SET oauth_token_req scopes TO "ping.read"
   36 |  USE ping_rest_auth_server
   37 |      WHICH IMPORTS: Work View   out auth_token  TO Work View   in auth_token
   38 |                     Work View   oauth_token_req  TO Work View   in oauth_token_req
   39 |      WHICH EXPORTS: Work View   loc http_response  FROM Work View   out http_response
   40 |                     Work View   loc oauth_token  FROM Work View   out oauth_token
   41 |  SET out auth_token value TO concat("Bearer ", loc oauth_token auth_tok)
   42 |  SET loc ping_server_config host_url TO concat(in ping_server_config host_url, ":11001")
   43 |  USE ping_rest_auth_hdr
   44 |      WHICH IMPORTS: Work View   out auth_token  TO Work View   in auth_token
   45 |                     Work View   loc ping_server_config  TO Work View   in ping_server_config
   46 |      WHICH EXPORTS: Work View   out pingresp  FROM Work View   out pingresp
   47 +--

Action Block Description:
 
    1 +- PING_REST_AUTH_SERVER
    2 |    IMPORTS:
    3 |      Work View   in auth_token (mandatory,transient,import only)
    4 |        value (mandatory)
    5 |      Work View   in oauth_token_req (mandatory,transient,import only)
    6 |        scopes (mandatory)
    7 |        grant_type (mandatory)
    8 |    EXPORTS:
    9 |      Work View   out http_response (transient,export only)
   10 |        code_text (nullable)
   11 |        code (nullable)
   12 |      Work View   out oauth_token (transient,export only)
   13 |        expires_in_secs (nullable)
   14 |        auth_tok (nullable)
   15 |    LOCALS:
   16 |    ENTITY ACTIONS:
   17 | 
   18 |  CALL REST Web Service "AUTHSRVR:POST:/oauth2/token"
   19 +--

Action Block Description:

    1 +- PING_REST_AUTH_HDR
    2 |    IMPORTS:
    3 |      Work View   in auth_token (mandatory,transient,import only)
    4 |        value (mandatory)
    5 |      Work View   in ping_server_config (mandatory,transient,import only)
    6 |        host_url (optional)
    7 |    EXPORTS:
    8 |      Work View   out pingresp (transient,export only)
    9 |        pingval (nullable)
   10 |        httptext (nullable)
   11 |        httpcode (nullable)
   12 |    LOCALS:
   13 |    ENTITY ACTIONS:
   14 | 
   15 |  CALL REST Web Service "PINGOAUTH:GET:/api/ping"
   16 +--


 


Wrap-up

We have covered a lot of ground in this document, so make sure if something isn’t making sense that you go back and re-read the sections. There are also tons of resources on the web about these security topics, so feel free to explore to understand more about HTTP security. OAuth2 is an especially expansive subject, so it can help in debugging problems if you have an idea about the framework itself.

Troubleshooting

Depending on the security scheme your application must implement as a REST client, you may get different types of errors. However, you are likely to see 401 errors when getting your application ready to access a service that requires authentication/authorization. This is the most common HTTP code returned when the service can’t authorize your request. It may help to trace your application using the Diagram Trace Utility to make sure your Authorization header or API Key header values are what you intend them to be.

Also, if you have control over the remote service, you may be able to capture tracing information from that side as to why the service is not allowing a particular request. Additionally, if you are targeting Java/JVM, you can turn on logging in the applicationmanager.properties file by uncommenting the trace.runtime line and setting the value to on. Make sure you check Include optional runtime files during the assembly of your EAR file, so the property file is included correctly.

Going forward

The Gen team is looking to implement native support for these security schemes, so these manual configuration options are a stopgap if you absolutely need to access an API that requires authentication. It is also a good example of the flexibility the current implementation provides. If you have some understanding of the HTTP protocol and become familiar with the OpenAPI spec format, there are advanced scenarios you can support with the already-provided tools.

Additional Information

For additional information, please see hub article: Gen 8.6 Consuming REST APIs (Call REST) feature