Chapter 9. API Security
In a microservices architecture, the API Gateway acts as a security endpoint. It is crucial to protect the access to those microservices so only authorized applications, usually referred to as clients
, are able to interact with those services. It is required to provide to end users, or resource owners
, a mechanism to protect their valuable data from unwanted access.
During the last few years, the banking industry has invested a lot of time and resources to create and expose APIs. There are several initiatives, such as the CMA’s Open Banking in the UK, or the Payments Service Directive 2 (PSD2) in the European Union, that are pushing banks to securely expose their customer’s information using APIs. The mechanisms used to protect those APIs will be explained in this chapter.
Kong provides several plugins that can be used to protect APIs. In the next sections, we cover how to use those plugins to add a strong security layer on top of the microservices.
All of the code for the sample project in this book can be found
here
.
Key authentication
It is common that only a set of identified client applications will be allowed to interact with APIs exposed on Kong. If a bank uses Kong to expose a Payments API, the bank will probably use an on-boarding process to identify which third-parties want to use that Payments API on their applications. Some examples of such third-parties are retailers, small FinTech companies, etc. That on-boarding process will have several steps, including online forms, that will be used to ask the third-party what type of application it is planning to write. The on-boarding process will most likely include offline and back office steps, such as asking those companies to sign documents like contracts or a code of ethics agreement.
Finally, as the last step of that process, the bank will provide to the third-party a secret key that will be used to interact with the API. That key will only identify the client application, but not to the resource owner. To protect the information of the resource owner, a different mechanism called OAuth2 will be used, which is covered next.
Another interesting use of API keys is for analytics and billing purposes. One of the main benefits of an API Management tool is that it provides a convenient way to detect if an API is being used very often, or on the other hand, if it is becoming obsolete because its usage is declining. That tracking can be done on a consumer-by-consumer basis. You can create reports filtered by consumers using their API key to detect exactly what is the historical usage of an API by a particular customer. If there is a contract in which the usage of that API is being billed monthly, the use of the API key is crucial in order to be able to determine how many API calls the customer has completed in a specific period of time.
Creating consumers
Once the company has identified who the third-parties are that want to consume the API, those third-parties must be added to the database of Kong, as Consumers. That operation will be done using Kong’s Admin API, interacting with the resource Consumer.
In Chapter 5, “Meet the Kong,” some verbs associated with Consumer were shown. This table adds more verbs associated with API Security (OAuth2, JWT and API Keys):
Verb
|
Path
|
Description
|
GET
|
/consumers/{username or id}/oauth2
|
List the client applications belonging to that consumer. Those applications will have a client_id and a client_secret.
|
POST
|
/consumers/{username or id}/oauth2
|
Creates a new Application for a consumer. During the creation of the application, a redirect URI is specified, which will be useful in some OAuth flows.
|
GET
|
/consumers/{username or id}/key-auth
|
Lists the API keys a Consumer has.
|
POST
|
/consumers/{username or id}/key-auth
|
Creates a new API key for this Consumer.
|
GET
|
/consumers/{username or id}/jwt
|
Lists the credentials associated to this Consumer that will be used to validate JWT.
|
POST
|
/consumers/{username or id}/jwt
|
Creates new credentials for this Consumer to validate JWT.
|
The first step to create a Consumer is to POST a new resource to the collection Consumers, as it was explained in Chapter 5. For example, the following curl command will be used to create a new Consumer called PurpleMall
:
$ curl -X POST http://localhost:8001/consumers/ --data "username=PurpleMall"
Kong will return an HTTP 201 Created
in case the operation was successful, with this JSON object as a response:
{
"created_at":1517083052000,
"username":"PurpleMall",
"id":"3f3d9926-349c-4bb2-b378-9b6a6f40a1cb"
}
It is possible to check if the Consumer was created by using a GET operation to retrieve the collection of Consumers:
$ curl -X GET http://localhost:8001/consumers/
That operation will return this collection. The ID of the consumer, or alternatively the name, will be used later to create an API key for that customer:
{
"total": 1,
"data": [
{
"created_at":1517083052000,
"username":"PurpleMall",
"id":"3f3d9926-349c-4bb2-b378-9b6a6f40a1cb"
}
]
}
If the API is going to be used by other third-parties, it is needed to create new Consumers by repeating the previous POST operation.
Protecting an API with the Key Authentication plugin
Once we have created at least one Consumer, the next step is to protect the API by using the Key Authentication
plugin. The parameters of this plugin are:
Parameter
|
Type
|
Default
|
Description
|
config.key_names
|
Optional
|
apikey
|
It is an array of comma separated parameter names. The Key Authentication plugin will look at either headers or query parameters that match a name found in this list, to find a suitable key.
|
config.hide_credentials
|
Optional
|
false
|
It is a boolean. A value of true will hide to the microservices the key, so they will be unaware of the existence of this parameter.
|
There are more parameters such as config.key_in_body, config.anonymous or config.run_on_preflight. Please refer to the documentation of the plugin to learn more about those parameters.
Before activating the Key Authentication
plugin, it is necessary to detect which ID of the API is going to be protected. That can be done by querying the APIs collection using this command:
$ curl -X GET http://localhost:8001/apis/
Alternatively, it is possible to use the name of the API, which probably is easier and more convenient. Assuming that the name of the API that is going to be protected is ‘movies’ this curl instruction will enable the Key Authentication
plugin on that API:
$ curl -X POST http://localhost:8001/apis/movies/plugins \
--data "name=key-auth" \
--data "config.key_names=apikey"
The output of that instruction will be:
{
"created_at":1517083831000,
"config":
{
"key_names": ["apikey"],
"key_in_body":false,
"anonymous":"",
"run_on_preflight":true,
"hide_credentials":false
},
"id":"d8b5f6ff-9b25-4f84-8b5e-c7cc71548a2f",
"name":"key-auth",
"api_id":"bd5baf99-7838-46b7-a81f-575a3726c7b5",
"enabled":true
}
Once a particular API has been configured to use the Key Authentication
plugin, that API becomes unusable unless the client applications include a header named apikey
with a valid value.
Assigning keys to client applications
Once the plugin is enabled, the next step is to create a key for the consumer. Other API Management tools usually provide a pair of keys: an apikey
and an apisecret
. In case the API Management tool supports it, it is usually a good practice to use a pair of keys instead of just one. Later on in the OAuth2 section it will be shown that usually, most OAuth2 flows depend on the existence of a client_id and optionally a client_secret.
The API key is associated with a Consumer. Using the ID of the Consumer created previously (or the name of the Consumer), the following curl will create a new API key (the trailing -d parameter is needed, in order to guarantee that the body of the request is empty):
curl -X POST http://localhost:8001/consumers/3f3d9926-349c-4bb2-b378-9b6a6f40a1cb/key-auth -d ''
The output of that command is:
{
"id":"9a5fa2b0-d6dd-476a-9d61-0d55a9c7c6ff",
"created_at":1517087501000,
"key":"bP0MSdFkkUNz4NQQNzVw2iQ25c3XbotN",
"consumer_id":"3f3d9926-349c-4bb2-b378-9b6a6f40a1cb"
}
The most important part of the output of that command is the key
field. That information must be sent to the third-party using any safe mechanism. It is not recommended to use email to exchange keys; most high-end API Management tools provide a Developer Portal that can be used as a self-service for Consumers to generate their own API keys on the fly.
Using a key to invoke an API
Once the Consumer has the API keys and the API has been configured with the Key Authentication plugin in order to interact with the API, it will need to pass an additional header named apikey
with the value of the API key:
$ curl -X GET http://localhost:8000/movies -H 'apikey: bP0MSdFkkUNz4NQQNzVw2iQ25c3XbotN'
If the header “apikey” is present, Kong will accept the request and return an appropriate answer. If the header is not present or the value is wrong, Kong will return an HTTP 401 Unauthorized
error or an HTTP 403 Forbidden
error.
Alternatively, it is possible to pass the apikey
in the query string. However, it is not a recommended practice, because the query string usually contains parameters related to pagination, sorting, etc. Security parameters are expected to be found on headers.
Protecting APIs with OAuth2
API keys are a simple and convenient way to protect API usage from unsolicited applications and consumers. But, detecting if a client application is allowed or not to use an API is not enough. An authorization model is needed in which end users (the resource owners
) will be able to allow or disallow a client application to get access to their personal data or to perform operations on behalf of them.
OAuth2 is an authorization standard commonly used to grant to websites or applications access to the resource owners information. The first version (OAuth) was released in 2010 (RFC 5849). Two years later, in 2012, a second version of the standard (OAuth2; RFC 6749) was released, making the first version of OAuth obsolete. In 2014, OpenID Connect was published, as an authentication layer on top of OAuth2.
Although most end users are unaware of the existence of OAuth2, the truth is that it is the protocol running behind the scenes whenever a user authorizes an application to log in using the Facebook or Linkedin credentials, or when those users allow another application to get access to their profile on those social networks. In that case, a concrete grant type of OAuth2 called Authorization Code
is used to allow the end users to log in to a website using their Facebook or Linkedin credentials.
UK Open Banking relies on OAuth2 as its foundational framework for API Security. As OAuth2 is just an authorization framework, and not an authentication framework, Open Banking also recommends the use of OpenID Connect on top of OAuth2.
OAuth2 is used by resource owners to provide to client applications temporary access to their personal data. At the heart of the protocol, it is the creation of temporary access tokens
, thus avoiding the need to provide the password to those client applications. A token
may be seen like a temporary opaque ID that can be used by a third-party client application to retrieve information that belongs to a resource owner or to perform operations in the name of the resource owner.
There are four different ways to grant access to those client applications using OAuth2:
- Client Credentials grant
- Authorization Code grant
- Resource Owner Password Credentials grant
- Implicit grant
Additionally, the specification includes two other flows:
- Refresh token grant. It can be used to request a new access token when the previous one has expired.
- JWT Profile. Sets the value of grant_type as “urn:ietf:params:oauth:grant-type:jwt-bearer” and uses a JWT as assertion to obtain an OAuth2 Access Token.
Kong has plugins that implement OAuth2 and OpenID Connect. On the next sections, it will be explained how to use those plugins to implement the different OAuth2 grant types.
Enabling OAuth2 plugin
The OAuth 2.0 Authentication
plugin has different parameters, summarized in this table:
Parameter
|
Type
|
Default
|
Description
|
name
|
Mandatory
|
|
Must be oauth2
|
config.scopes
|
Optional
|
|
It is an array of scopes separated by commas. A scope is typically a permission, the specific detail of which type of data belonging to the resource owner the client application wants to read or write. It may be as well an operation, for example, the ability to make a payment using an account belonging to the resource owner.
|
config.token_expiration
|
Optional
|
7200
|
The number of seconds this token will be valid. It is recommended to use very low values, for example just a couple of minutes. A value of 300 seconds will be acceptable. By using Refresh Tokens, the client application can extend the life of the token.
|
config. enable_authorization_code
|
Optional
|
false
|
If enabled, the plugin of OAuth2 will be able to use the Authorization Code grant.
|
config. enable_client_credentials
|
Optional
|
false
|
If enabled, the plugin of OAuth2 will be able to use the Client Credentials grant.
|
config. enable_implicit_grant
|
Optional
|
false
|
If enabled, the plugin of OAuth2 will be able to use the Implicit Grant.
|
config. enable_password_grant
|
Optional
|
false
|
If enabled, the plugin of OAuth2 will be able to use the Resource Owner Password Credentials grants.
|
config. hide_credentials
|
Optional
|
false
|
If enabled, Kong will remove the OAuth2 headers when invoking the underlying microservices. It is recommended to enable this parameter.
|
config. refresh_token_ttl
|
Optional
|
1209600
|
The number of seconds the refresh token will be valid. It is recommended to think carefully about a proper value of this parameter in terms of business implications and security. A very low value (just a couple of days) will affect the user experience of the client applications (users will be asked too often to enter their credentials) and a value too high may have security implications.
|
There are more parameters such as config.mandatory_scope, config.auth_header_name, etc. Please, refer to the documentation of the plugin to learn more.
The following curl instruction could be used to protect an API called movies
to use the authorization code grant, to set a lifetime of the tokens of 4 minutes (240 seconds), and to establish ‘ratings’ and ‘revenue’ as valid scopes:
$ curl -X POST http://localhost:8001/apis/movies/plugins \
--data "name=oauth2" \
--data "config.enable_authorization_code=true" \
--data "config.token_expiration=240" \
--data "config.scopes=ratings,revenue"
Once the plugin is enabled, a Provision Key will be created. That provision key will be used to interact with the OAuth2 endpoints of the API and is supposed to be secret, so only internal applications, usually login forms, will know its value.
As it was described in the section Key Authentication
, it is necessary to create a Consumer to identify the third-party that will consume the API. That can be done by issuing a curl command like this:
$ curl -X POST http://localhost:8001/consumers/ --data "username=DisruptiveFinTech"
A Consumer may have different applications consuming an API. So, for example, a FinTech company may have a Data Aggregation application (subscribed to the Accounts API of several banks) but it may have as well a Quick Payments applications (subscribed to the Payments API). Those applications will be associated with a Consumer using a curl command like this:
$ curl -X POST http://localhost:8001/consumers/DisruptiveFinTech/oauth2 \
--data "name=DataAggregation" \
--data "redirect_uri=http://getkong.org"
The output of curl will be:
{
"client_id":"jJGCzhxxEst50sOtywFOINSRvfh48dNB",
"created_at":1517168275000,
"id":"7ed353cd-a6ba-4d6c-9146-fb3c781e2ebe",
"redirect_uri":["http:\/\/www.getkong.org"],
"name":"DataAggregation",
"client_secret":"AuIgB5NM4zU57EjHfP8su6bfA3EtnZny",
"consumer_id":"fee36abc-2474-49b7-addd-7d6b56a16316"
}
That curl command is using the parameter name
to set the name of the consuming application (in this case DataAggregation). The parameter redirect_uri
is also set to establish the URL that will be used to redirect the browser of the users once they have entered their credentials on the login page of their bank and have allowed the FinTech company to access their data.
That curl command will generate as well a pair of keys (client_id and client_secret) that will be used to interact with the OAuth2 endpoints. In case the API is going to be consumed by third-parties, the values of those fields must be provided to those companies in a secure way (avoiding email).
Once the plugin is enabled on an API, two new resources will be available via POST for that API. Those endpoints must be accessed using HTTPS (for example, the port 8443) because it is a requirement of OAuth2:
- oauth2/authorize - Used to generate codes in the Authorization Code grant
- oauth2/token - Used to obtain access tokens, to refresh tokens, etc.
The parameters used to interact typically with oauth2/authorize are:
Parameter
|
Type
|
Description
|
client_id
|
Mandatory
|
The ID of the application that wants to obtain an access token.
|
response_type
|
Mandatory
|
The type of response of this endpoint. It will typically be ‘code’.
|
scope
|
Mandatory
|
It is an array of scopes separated by commas.
|
provision_key
|
Mandatory
|
The provision key created when the OAuth2 plugin was enabled for this API.
|
authenticated_userid
|
Mandatory
|
The user id of the resource owner.
|
The parameters used to interact typically with oauth2/token are:
Parameter
|
Type
|
Description
|
client_id
|
Mandatory
|
The ID of the application that wants to obtain an access token.
|
client_secret
|
Optional
|
The secret of the application that wants to obtain an access token.
|
grant_type
|
Mandatory
|
The type of grant: authorization_code, password, client_credentials, implicit or refresh_token.
|
scope
|
Mandatory
|
It is an array of scopes separated by commas.
|
provision_key
|
Optional
|
The provision key created when the OAuth2 plugin was enabled to this API.
|
authenticated_userid
|
Optional
|
The user id of the resource owner.
|
refresh_token
|
Optional
|
If grant_type is refresh_token, this parameter will hold the refresh token
|
code
|
Optional
|
If grant_type is authorization_code, this parameter will hold the code.
|
In the next sections, the different OAuth2 grant flows are explained.
Client Credentials grant
It is common in internal environments that a consuming application needs to interact with an API in order to retrieve static information such as the list of branches of a bank, the list of supply chain providers, the list of countries in which a company is operating, etc. In that case, that information is not associated with a particular resource owner, so it does not make any sense to interact with that API using the username and password of a particular customer or employee.
For these cases, it is very common to use a simplistic OAuth2 flow called Client Credentials
. The credentials that will be used to obtain an OAuth2 access token will just be the ID of the client application. There is no need to involve any resource owner to provide their credentials, because this flow is intended to be used by consuming applications that just need to see general information or to perform operations not directly associated to a particular person.
This curl command will be used to enable the OAuth 2.0 Authentication
plugin to use a Client Credentials flow on the Movies API:
$ curl -X POST http://localhost:8001/apis/movies/plugins \
--data "name=oauth2" \
--data "config.enable_client_credentials=true"
The Client Credentials is so simple that it does not even require the plugin to establish a set of known scopes.
As described in the previous section (Enabling OAuth2 plugin) after the plugin has been configured, it is required to execute two additional steps:
A consuming application will be able to obtain OAuth2 access tokens using this curl command (in this case, the SSL port 8443 is used). The parameters client_id an client_secret are obtained after creating an application for a particular Consumer:
$ curl -X POST https://localhost:8443/movies/oauth2/token \
--data "client_id=fIeXHLeDwSuaCoHA0Bh2IgUeurBmX3Gt" \
--data "client_secret=hhRrADE3XYjZp6DBU5ezRVpDt1gkkfK6" \
--data "grant_type=client_credentials"
The response of curl will be:
{
"token_type": "bearer",
"access_token": "mzi3CJMautR8OTDWT5gwVKdWrGosSaCO",
"expires_in": 7200
}
Sometimes it is needed to use the parameter -k in curl, in order to avoid SSL validation problems.
The access_token returned by curl can be used to interact with the Movies API, passing the token in an Authorization header:
curl -X GET http://localhost:8000/movies \
-H 'Authorization: Bearer mzi3CJMautR8OTDWT5gwVKdWrGosSaCO'
If the Authorization header is missing, or the token is used after its expiration time, Kong will return an HTTP 401 Unauthorized
.
Authorization Code grant
The Authorization Code is the most complex OAuth2 flow. It is commonly used when the client application that consumes an API maintained by a third-party and running in a separate infrastructure. Typically, a FinTech company will be using this OAuth2 flow to gain access to information of customers of banks, in a scenario like this:
- An end user (=resource owner) uses an application or website of a FinTech.
- At some point of the journey, the client application of the FinTech suggests the users to log in with the credentials of their banks. This may be useful, for example, to make a payment, or to analyze the transactions of an account.
- The browser is redirected to the login page of the bank and the user (=resource owner) is asked to provide the credentials.
- The resource owner logins to the bank. The resource owners are asked as well to confirm if they agree to let the FinTech company access the information associated with the scopes.
- The login form interacts with the oauth2/authorize endpoint to create a temporary code.
- The login form redirects the browser to a page that belongs to the FinTech website, passing a temporary code in the query string.
- The backend client application of the FinTech uses the temporary code to interact with the oauth2/token endpoint of the bank to obtain an access token and a refresh token.
- The client application uses the access token to temporarily consume APIs of the bank on behalf of the resource owner. When that access token expires, the FinTech uses the refresh token to obtain a new access token.
This diagram describes the Authorization code flow:
The first step to enable this OAuth2 flow with Kong is to configure the OAuth 2.0 Authentication
plugin to use an Authorization Code flow. This curl command sets a couple of scopes (ratings and revenue), and instructs the OAuth2 server to require at least one scope accepted by the resource owner:
$ curl -X POST http://localhost:8001/apis/movies/plugins \
--data "name=oauth2" \
--data "config.scopes=ratings,revenue" \
--data "config.mandatory_scope=true" \
--data "config.enable_authorization_code=true"
The output of curl will include a provision_key that will be used later in a login form to be able to interact with the oauth2/authorize endpoint. As in any other OAuth2 flow, it is mandatory to perform two additional steps, as described in the “Enabling OAuth2 plugin” section:
To use the Authorization Code flow, you need:
- A login form running on the same infrastructure as Kong
- A form that asks the resource owner to grant access to the third-party to a set of scopes (this form is usually the same form as the login)
- A page on a website of the third-party website that can be accessed by the resource owner and that will contain the temporary authorization code.
In order to be able to test the grant, it is highly recommended to install any kind of mock login form that is able to simulate that the user is logged in, that the user has granted the scopes to the third-party, and that this third-party is getting the authorization code. Fortunately, in the GitHub repository of Kong there is a sample app (kong-oauth2-hello-world) that provides all of these artifacts. The steps required to install the kong-oauth2-hello-world are:
- Download the source code from GitHub
- Install the Node.js dependencies (npm install)
- Define required environment variables such as PROVISION_KEY, KONG_ADMIN and KONG_API URLs, the API_PATH of the API to be consumed, and a list of SCOPES.
- Run the app (node app.js)
- Use a browser to navigate to the URL of the app. That URL will include several query parameters, including the response_type (with a value of ‘code'), some scopes, and the client_id created in the Consumer Application.
The sample app uses basically the oauth2/authorize endpoint to generate a temporary code. This is an illustrative example of the API call that the sample app kong-oauth2-hello-world
is doing in its code:
$ curl -X POST https://localhost:8443/movies/oauth2/authorize \
--data "client_id=fIeXHLeDwSuaCoHA0Bh2IgUeurBmX3Gt" \
--data "response_type=code" \
--data "scope=ratings" \
--data "provision_key=Nt1iqLe6CPN9Gv91L1nPDLlvzJgYZ5K2" \
--data "authenticated_userid=userid123"
Once the app gets the authorization code, it will redirect to any mock URL. That URL will contain in the query string the temporary code. That code can be used to manually interact with the oauth2/token endpoint to get the access token:
$ curl -X POST https://localhost:8443/movies/oauth2/token \
--data "client_id=fIeXHLeDwSuaCoHA0Bh2IgUeurBmX3Gt" \
--data "client_secret=hhRrADE3XYjZp6DBU5ezRVpDt1gkkfK6" \
--data "grant_type=authorization_code" \
--data "code=GGp0UiTPVtpZbfme3bdaypzrUDLNTQln"
The output of curl will be:
{
"refresh_token": "1XwV5uuzdaX6InOM7otIxKKVklOmDy5F",
"token_type": "bearer",
"access_token": "oNMSjPVAVCmQVSQqt2jESOrQ2eHHWKRB",
"expires_in": 7200
}
Sometimes it is needed to use the parameter -k in curl, in order to avoid SSL certification problems.
That access_token can be used to interact with the Movies API, passing the token in an Authorization header:
curl -X GET http://locaslhost:8000/movies \
-H 'Authorization: Bearer oNMSjPVAVCmQVSQqt2jESOrQ2eHHWKRB'
If the Authorization header is missing, or the token is used after its expiration time, Kong will return an HTTP 401 Unauthorized
.
Resource Owner Password Credentials grant
There are some scenarios in which the client application has been created by the same organization that created the API. In that case, if both the client application and the API are running in the same infrastructure, it may be interesting if the client application uses its own login form to authenticate the user, so those credentials can be used as well to interact with the OAuth server directly, obtaining an access token.
This OAuth2 flow can be used if the following conditions are met:
- There is a relationship of trust between the consuming client application and the resource owner.
- The client application is able to obtain the user id and password of the user.
- The client application is able to validate the user id and password using, for example, an LDAP server.
- The client application and the API are running in the same organization.
This OAuth2 flow is commonly used as well in Single Sign-On scenarios. For example, it may be the case in which the consuming application has some kind of SSO token (a cookie, a corporate token, etc) that could be used to obtain an OAuth2 Access Token. Obviously, it needs another artifact that must be able to determine if the SSO token is still valid.
To use this flow with Kong, it is needed to create an artifact, or custom OAuth proxy, that will sit between the client application and the OAuth2 endpoint. This artifact will receive the user id and the password of the resource owner. The artifact will validate also the credentials (probably using an LDAP server) and finally will interact with the OAuth2/token endpoint to get an OAuth2 access token that will be sent back to the client application. To make sure that only this artifact is able to obtain this access token, the provision_key will be used as a parameter to the OAuth2/token call.
The following diagram describes the flow:
This curl instruction will be used to configure the Resource Owner Password Credentials flow:
$ curl -X POST http://localhost:8001/apis/movies/plugins \
--data "name=oauth2" \
--data "config.scopes=ratings,revenue" \
--data "config.enable_password_grant=true"
As with the other grants, these steps must be done, as described in section “Enabling OAuth2 plugin”:
To get an OAuth2 Access Token, the artifact between the client application and the OAuth2 endpoint will do this call:
$ curl -X POST https://localhost:8443/movies/oauth2/token \
--data "client_id=fIeXHLeDwSuaCoHA0Bh2IgUeurBmX3Gt" \
--data "client_secret=hhRrADE3XYjZp6DBU5ezRVpDt1gkkfK6" \
--data "grant_type=password" \
--data "provision_key=Nt1iqLe6CPN9Gv91L1nPDLlvzJgYZ5K2" \
--data "scope=revenue" \
--data "authenticated_userid=userid123"
The output of curl will be:
{
"refresh_token": "1XwV5uuzdaX6InOM7otIxKKVklOmDy5F",
"token_type": "bearer",
"access_token": "oNMSjPVAVCmQVSQqt2jESOrQ2eHHWKRB",
"expires_in": 7200
}
Sometimes it is needed to use the parameter -k in curl, in order to avoid SSL certification problems.
That access_token can be used to interact with the Movies API, passing the token in an Authorization header:
curl -X GET http://localhost:8000/movies \
-H 'Authorization: Bearer oNMSjPVAVCmQVSQqt2jESOrQ2eHHWKRB'
If the Authorization header is missing, or the token is used after its expiration time, Kong will return an HTTP 401 Unauthorized
.
Implicit grant
The Implicit grant is an OAuth2 flow very similar to the Authorization Code. This flow will typically be used by Single Page Applications (SPA) written in JavaScript that do not have the ability to safely store any secret. In that case, this flow is not returning a refresh_token, because the client application will not be able to store it.
Another main difference between the Implicit grant and the Authorization code grant is that in the Implicit grant, the Oauth2 endpoint is returning directly the access token in the redirection URL, and not a temporary code.
This is the less secure OAuth2 grant, so only very short-lived access tokens should be issued.
Refresh Token grant
The typical OAuth2 access token will have an expiration time of around 5 minutes (300 seconds). If a client application uses an expired access token, Kong will return an HTTP 401 Unauthorized
with this response:
{
"error_description": "The access token is invalid or has expired",
"error": "invalid_token"
}
In order to obtain a new access token, a possibility could be to request that the resource owners provide their credentials again. Obviously, once resource owners grant a third-party access to their personal data, they expect the client applications to use that information autonomously, not having to be asked for their credentials again and again.
OAuth2 provides an additional grant, or Refresh Token, that can be used to request new access tokens on behalf of the user. The endpoint that will be used to generate a new access token will be oauth2/token and it will use these parameters:
- grant_type - It must be: refresh_token
- client_id - The client ID associated with this Consumer and Application
- client_secret - The client secret associated with this Consumer and Application
- refresh_token - It must contain a previously issued refresh token
This sample curl shows how to get a refresh token:
$ curl -X POST https://localhost:8443/movies/oauth2/token \
--data "grant_type=refresh_token" \
--data "client_id=fIeXHLeDwSuaCoHA0Bh2IgUeurBmX3Gt" \
--data "client_secret=hhRrADE3XYjZp6DBU5ezRVpDt1gkkfK6" \
--data "refresh_token=1XwV5uuzdaX6InOM7otIxKKVklOmDy5F"
Upon completion, the OAuth2 server would return a JSON object like this:
{
"refresh_token": "5WrRlyTeYh7V2ELWQTtbpG6cUEAoRL8e",
"token_type": "bearer",
"access_token": "CpeACPZLMhUiMK5tOA9Xcy22Qn1qitHS",
"expires_in": 7200
}
The new refresh token will be different from the previous one. If the client application tries to invoke the grant passing a refresh token that was used previously, Kong will return an HTTP 400 Bad Request
with this error:
{
"error_description": "Invalid refresh_token",
"error": "invalid_request"
}
OpenID Connect
As described in the previous section, OAuth2 is an authorization framework that can be very useful for resource owners to grant temporary access to their personal information and to perform operations on their behalf. However, OAuth2 does not provide any facility to identify the end-user who has granted the permissions, because OAuth2 is not intended to be used to provide identity information.
OpenID Connect was born in 2014 as an identity layer on top of OAuth2. Unlike OAuth2, OpenID Connect provides to client applications a mechanism to identify to the resource owners who has granted permissions.
OpenID Connect has gained traction due to the release of the UK Open Banking APIs. These APIs are based on OAuth2, but the specification explicitly mandates the use of OpenID Connect on top of OAuth2 to be able to add identity information.
New and modified flows
This table summarizes the flows that OpenID Connect has modified or added on top of OAuth2:
Flow
|
New or Modified
|
Description
|
Authorization Code
|
Modified
|
In OpenID Connect, the scope must contain the value openid
although other scopes may be present. The response of the /token endpoint will include an additional field (id_token) that contains a JWT with identity information.
|
Implicit Flow
|
Modified
|
The scope must contain the value openid
although other scopes may be present. The response of the /authorize endpoint will include an additional field (id_token) that contains a JWT with identity information.
|
Hybrid Flow
|
New
|
This flow is designed to provide flexibility; some of the tokens are returned in the /token endpoint or in the /authorize. The response of the /authorize endpoint depends on the value of the parameter response_type, which can be one of these three choices: 1) code id_token ; 2) code token ; 3) code id_token token.
|
The ID Token
The ID Token is a JSON Web Token (JWT) that contains a set of claims with identity information. The standard claims are:
Claim
|
Type
|
sub
|
string
|
name
|
string
|
given_name
|
string
|
family_name
|
string
|
middle_name
|
string
|
nickname
|
string
|
preferred_username
|
string
|
profile
|
string
|
picture
|
string
|
website
|
string
|
email
|
string
|
email_verified
|
boolean
|
gender
|
string
|
birthdate
|
string
|
zoneinfo
|
string
|
locale
|
string
|
phone_number
|
string
|
phone_number_verified
|
boolean
|
address
|
JSON object
|
updated_at
|
number
|
New endpoints
OpenID Connect introduces a new endpoint: /userinfo. This new endpoint can be used either with GET or with POST and accepts an Authorization header that must contain an OAuth2 Access Token. The output of the endpoint will be a JSON object containing identity information of the resource owner. The fields of that JSON object will have the same names shown in the list of standard claims of the previous section.
Kong support for OpenID Connect
Although Kong Community Edition does not include yet support for OpenID Connect, Kong Enterprise Edition includes a suite of plugins to support OpenID Connect. The plugins included in the suite are:
- OpenID Connect Verification Plugin
- OpenID Connect Authentication Plugin
- OpenID Connect Protection Plugin
- OpenID Connect Revocation Plugin
- OpenID Connect Dereferencing Plugin
Protecting APIs with JWT
OAuth2 and OpenID Connect are the most common authorization and authentication frameworks that are used today to protect APIs. However, in some scenarios, it may make sense to use JSON Web Tokens (JWT - pronounced: “jot”) as an alternate way to protect an API. For example:
- Usually, after an OAuth2 flow, it is common to generate a JWT to interact with microservices. If one of those microservices tries to invoke to another API, it may be interesting to directly use the same JWT that was used to interact with the microservice.
- To secure mobile applications. There are some scenarios in which a mobile application could use JWT as an authorization token to interact with an API.
Kong supports out-of-the-box the validation of JWT tokens. Unfortunately, it does not yet support the generation of JWT; in case that functionality is needed in a project, it may be necessary to write a custom Kong plugin.
What is a JSON Web Token?
JSON Web Tokens (JWT) are defined in the RFC 7519. It is basically a compact and self-contained way to carry a set of claims between two parts. A JWT is composed of three Base64 strings separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1
lIjoiSm9hbyBNYXJ0aW5zIn0.TfesTf-QPpJu2y01B6rwQfKuiQTDza32Y3
QPXWcx7Nk
The three strings are:
- A Header. It is a JSON object that usually contains two attributes: 1) A hash algorithm (HS256, RS256, etc); 2) A type: JWT.
- A Payload. It is a JSON containing a set of attributes or claims like: iss (Issuer) sub (Subject) exp (Expiration) aud (Audience) etc.
- A signature.
When an API Gateway or a microservice is invoked using a JWT included in an Authorization header, it will be necessary to perform the following validations:
- Check if the JWT has not expired
- Check if the audience is the adequate
- Check if the JWT was signed by a known party (usually another API Gateway)
Enabling the JWT plugin
The following instruction will enable the JWT plugin in the Movies API. The plugin will verify as well that the JWT has not expired yet. The plugin will be configured to use the claim ‘iss’ to identify the key that will be used to validate the token:
$ curl -X POST http://localhost:8001/apis/movies/plugins \
--data "name=jwt" \
--data "config.claims_to_verify=exp"
Upon completion of that instruction, any invocation to the Movies API will return an HTTP 401 Unauthorized
, unless a valid JWT is passed in an Authorization header and at least a Consumer has been configured with a key that can be used to validate that token.
Adding JWT credentials to a Consumer
Before trying to use the Movies API with a JWT, it is necessary to associate with an existing Consumer a credential that will be used to validate the JWT.
Kong supports different algorithms to validate JWT:
- HS256 (the default)
- RS256
- ES256
The following instruction creates an HS256 credential for a Consumer named PurpleMall:
$ curl -X POST http://localhost:8001/consumers/PurpleMall/jwt \
--header "Content-Type: application/x-www-form-urlencoded"
The output of that command will be:
{
"created_at":1517350724000,
"id":"2ea7c77c-a627-4917-ada6-1bb052a46e03",
"algorithm":"HS256",
"key":"gn0DactcwAMKUmA0iuPVXkBnVjxxIbVW",
"secret":"Rz3fBOsIWvdoT3SAf1LLc2xIUkLEE1iC",
"consumer_id":"d25a7eaf-c44b-4425-97c5-4683f989b355"
}
The most important attributes of that output are:
- Key - Identifies the credential. That value must be included in the ‘iss’ claim of the JWT in order to be able to identify the credentials to be used to validate the JWT
- Secret - It is the secret used in the HS256 algorithm to calculate the signature.
Putting it all together
Once the API and the Consumer have been properly configured, the next step is to create a valid JWT for testing purposes. There are many online JWT generators available. A simple search on Google would provide different useful websites that provide the ability to generate and validate JWT. Probably the best known is JWT.IO, a simple website that provides a useful interface to validate and to generate JWT.
The steps required to generate a JWT are:
Step
|
Description
|
|
A page with a sample JWT will appear. The JWT will be shown on the left side as Encoded, and in the right side as Decoded.
|
Maintain the Header as it is.
|
Maintain the algorithm to be HS256.
|
Add a “iss” claim in the payload
|
The value should be the same than the “key” property associated with the JWT credential in the Consumer
|
Add a “exp” claim in the payload
|
The value should be the current time (in seconds) plus 60 or 90 seconds
|
Type the secret in the signature
|
The value should be the same than the “secret” property associated with the JWT credential in the Consumer
|
Copy the Encoded JWT to the clipboard
|
It should contain the claims and a signature based on the secret.
|
This is a sample of the header and payload:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234",
"name": "Simon Warren",
"iss" : "gn0DactcwAMKUmA0iuPVXkBnVjxxIbVW",
"exp" : 1517951758
}
This is a sample of that JWT, but encoded (secret: Rz3fBOsIWvdoT3SAf1LLc2xIUkLEE1iC):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwi
bmFtZSI6IlNpbW9uIFdhcnJlbiIsImlzcyI6ImduMERhY3Rjd0FNS1VtQ
TBpdVBWWGtCblZqeHhJYlZXIiwiZXhwIjoxNTE3OTUxNzU4fQ.s9nHjy6
knQMllCSN-Fqd9q165-pQwjBhi2bueTE-UzM
To interact with the Movies API, the JWT must be included in an Authorization header:
$ curl -X GET http://localhost:8000/movies \
--header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwi
bmFtZSI6IlNpbW9uIFdhcnJlbiIsImlzcyI6ImduMERhY3Rjd0FNS1VtQ
TBpdVBWWGtCblZqeHhJYlZXIiwiZXhwIjoxNTE3OTUxNzU4fQ.s9nHjy6
knQMllCSN-Fqd9q165-pQwjBhi2bueTE-UzM"
If the JWT was signed properly, the API would return an HTTP 200
with the response. In case the token JWT has expired, Kong will return an HTTP 401 Unauthorized
with a message explaining that the JWT expired. In case the claim “iss” contains a value different than the key associated with the Consumer, Kong would return an HTTP 403 Forbidden
, with a message saying that no credentials were found for that given ‘iss’.
CORS
Cross-Origin Resource Sharing (CORS) is a W3C Recommendation that allows browsers to interact with resources that are available on domains outside the domain from which the page was downloaded. Although it is common in a web page to include images and style sheets of other domains, any modern browser will protect the execution of JavaScript that tries to interact with APIs belonging to other domains.
CORS is based on response headers. Basically, when CORS is enabled in a particular API served from an API Gateway, a response header called Access-Control-Allow-Origin will be added to the response.
That header can be configured with different values. Some examples assuming that CORS is enabled in an API Gateway in the domain a.com:
- Access-Control-Allow-Origin: http://b.com
. With this configuration, any web page found in b.com will be able to interact with APIs found in a.com. However, if a web page found in c.com tries to interact with this gateway, the browser would block it.
- Access-Control-Allow-Origin: * . With this configuration, the APIs of a.com will be available to any application running on any domain.
To use CORS in Kong, it is enough to enable the plugin on any API, for example in Movies:
curl -X POST http://localhost:8001/apis/movies/plugins/ \
--header 'content-type: application/x-www-form-urlencoded' \
--data "name=cors"
The output of curl will be:
{
"created_at":1517354766000,
"config":
{
"credentials":false,
"preflight_continue":false
},
"id":"4e9f2a99-dee0-4c3d-a193-46a8f6ff0fab",
"name":"cors",
"api_id":"bd5baf99-7838-46b7-a81f-575a3726c7b5",
"enabled":true
}
Any call to a Movies API will now include these headers. From now on, a new header (Access-Control-Allow-Origin) will be included in the response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 622
Connection: keep-alive
X-Powered-By: Express
X-Content-Type-Options: nosniff
ETag: W/"26e-4lY2V90zz3jPaMtUYoDpyO0zGQw"
Date: Tue, 30 Jan 2018 23:28:13 GMT
Access-Control-Allow-Origin: *
X-Kong-Upstream-Latency: 2
X-Kong-Proxy-Latency: 1
Via: kong/0.11.2
Conclusion
API Security is a complex topic, since APIs can be protected with a variety of choices, including keys, OAuth, OpenID Connect, JWT, and CORS. Kong Gateway includes out-of-the-box different plugins that can be used to create a robust security architecture.
You have now reached the end of this book. We hope you have learned everything you need to take advantage of Kong.