Accelerating API Security using ForgeRock

Posted August 29, 2021 by Jatinder Singh ‐ 9 min read

Demos the components and configuration needed to implement the much-needed API security using the ForgeRock Platform

API Security
API Security

Do you know Gartner predicts that by 2022, application programming interface (API) attacks will become the most frequent attack vector, causing data breaches for enterprise web applications? With accelerated Digital Transformation brought by Covid-19 and APIs being the defacto standard to deliver products & services, it is critical to secure all aspects of the API access to defend against lost revenue, fines, and brand and reputation damage.

The API security hardening involves various factors to reduce risk and increase security posture. Since we cannot cover everything through a blog post, the scope of this post is to lay a hands-on foundational understanding of how to secure API access using the OAuth2 federation protocol in ForgeRock AM. Thus, the blog post is a series on API security. This post aims to set the stage with a basic introduction, and the rest of the series will build on the foundation of this post to showcase additional features and configurations.

OAuth2 and OIDC Primer

OAuth2 is a standard-based (RFC-6749) federation protocol to allow secure authorization in a standard way across the web, mobile, and desktop applications. Using this protocol, a client can access protected resources on behalf of a user. It is essentially a delegation protocol. So what makes delegation possible? I am glad you asked. OAuth2 effectively implements delegation by introducing a concept called authorization grant. The concept allows a client to obtain the user’s authorization and exchange it for an access token that acts like a pass or a key.

So, why do we need the OAuth2 protocol, and what was the world like before OAuth2 existed for the app developers?

As said earlier, OAuth2 enables delegation. So if we remove delegation, how did the client then access protected resources is the question? The answer pre-2007 is by impersonating the user directly, i.e., the client would ask users for their credentials and authenticate with the access manager to gain unrestricted access to the protected resources. Not only did this involve users blindly trusting a service provider with their credentials, but if there was ever a breach or a known vulnerability, users were directly in the critical path of the security incidents. And not to mention, there was no straightforward way for a user to revoke access once the relationship between a user and the client ended.

With OAuth2, instead of directly sharing the credentials with the client, the client asks for a user’s consent via authorization grant with appropriate scopes. The client then submits this grant to the Authorization Server (AS) to obtain an access token that gives the client controlled and limited access to protected resources. This way of communication is far more secure and allows revocation of access if the relationship between a user and the client ends.

Okay, so OAuth2 makes sense, but why do we need OIDC?

While OAuth2 enables delegation, it does not provide a way for a client to know the identity or user information of who authorized that access and how they authenticated, which might be of great interest to the client. And this is where OIDC plays a key role. It provides an identity layer on top of OAuth2 by providing additional endpoints and tokens through which a client can retrieve information about the user (Identity) and their authentication session.

To keep the scope of the blog post minimal and genuinely understand the value OIDC creates, we will stick to the OAuth2 in this post and leave OIDC for the subsequent posts.

API Security in Action (DEMO)

In our demo, we will protect a fictional API called MaDOC or “My Doctor” that mimics some requirements of an EMR software. The API provides the following features:

  • Three different types of users - Admin, Doctor, and Patient;
  • Register Patients - Admin can register patients;
  • Create Appointments - Admin can create appointments for patients;
  • Create Health Records - Doctor can create health records for patients.

NOTE: To make it easier for my audience to follow along, I have included all source code, including a link to MaDOC API, AM Configuration, Postman collection, and IG routes through Sqoop Data’s Github repository. Please see the Github section below for details.

Security Requirements

My goal for the security requirements is to be minimal and instead concentrate on providing a short hands-on lab for deciphering API security using ForgeRock. With that in mind, MaDOC API has the following minimum security requirements:

  • MaDOC API must not be altered in any way to enforce security;
  • Secure /users, /appointments, and /healthrecords API endpoints;

Architecture

Architecture
Architecture

Please see below for the description of each component:

  • MaDOC - Provides a fictional EMR like API for patient management;
  • Identity Gateway (IG) - Acts as a Resource Server (RS) and integrates MaDOC API into the ForgeRock Platform without modifying the application itself or container where it runs;
  • Access Manager (AM) - Acts as an Authorization Server (AS), provides access management capability, and handles both AuthN and AuthZ requirements;
  • User - Acts as a Resource Owner (RO) and provides authorization to Client to access protected resources provided by MaDOC API.
  • Postman (Client) - While it’s not shown in the above diagram, we’ll use Postman as a Client to access protected resources held at MaDOC API for our demo.

Configure OAuth2 Services & Clients

As the first steps, we have to set up the OAuth2 services and clients within AM to be aware of all the parties participating in the OAuth2 dance. Our Postman collection provided as part of this blog post has two directories - Set-Up and Demo. In this part of the demo, we’ll go ahead and run all four steps provided in the Set-Up directory.

Step 1: Service Account Login

Request

curl --location --request POST 'https://identity1.sqoopdata.local:17143/openam/json/realms/root/authenticate' \
--header 'Accept-API-Version: resource=2.1' \
--header 'X-OpenAM-Username: amadmin' \
--header 'X-OpenAM-Password: changeit' \
--header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=EnGrBQWK7dPPmSuYS_HPAwBfUQI.*AAJTSQACMDIAAlNLABw2S0dPY0ZUOUpQK0YyVHcvT1A5QjJSS05sRHM9AAR0eXBlAANDVFMAAlMxAAIwMQ..*'

Response

{
    "tokenId": "EnGrBQWK7dPPmSuYS_HPAwBfUQI.*AAJTSQACMDIAAlNLABw2S0dPY0ZUOUpQK0YyVHcvT1A5QjJSS05sRHM9AAR0eXBlAANDVFMAAlMxAAIwMQ..*",
    "successUrl": "/openam/console",
    "realm": "/"
}
Step2: Create OAuth 2.0 Service Provider

Request

curl --location --request POST 'https://identity1.sqoopdata.local:17143/openam/json/realms/emr/realm-config/services/oauth-oidc?_action=create' \
--header 'iplanetDirectoryPro: {{adminSSOToken}}' \
--header 'Content-Type: application/json' \
--header 'Accept-API-Version: resource=1.0' \
--header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=w_-iGc_ok2uNBKK6Zj0XoCQXsrw.*AAJTSQACMDIAAlNLABxLWGVXUFhiejhkSUM3Tndjb2FrZGhXUk5OODg9AAR0eXBlAANDVFMAAlMxAAIwMQ..*' \
--data-raw '{
    "advancedOAuth2Config": {
        "responseTypeClasses": [
            "code|org.forgerock.oauth2.core.AuthorizationCodeResponseTypeHandler",
            "device_code|org.forgerock.oauth2.core.TokenResponseTypeHandler",
            "token|org.forgerock.oauth2.core.TokenResponseTypeHandler",
            "id_token|org.forgerock.openidconnect.IdTokenResponseTypeHandler"
        ],
        "grantTypes": [
            "authorization_code",
            "password"
        ]
    },
    "coreOAuth2Config": {
        "statelessTokensEnabled": true
    }
}'

Response

Note: Response truncated to maintain some sanity :) Please download our Postman collection for complete details.

{
    "_id": "",
    "_rev": "-516372400",
    "advancedOAuth2Config": {
        "responseTypeClasses": [
            "code|org.forgerock.oauth2.core.AuthorizationCodeResponseTypeHandler",
            "device_code|org.forgerock.oauth2.core.TokenResponseTypeHandler",
            "token|org.forgerock.oauth2.core.TokenResponseTypeHandler",
            "id_token|org.forgerock.openidconnect.IdTokenResponseTypeHandler"
        ],
        "grantTypes": [
            "authorization_code",
            "password"
        ],
        ...
    ...
}
Step3: Create Client Application (CA)

Request

Note: Request truncated to maintain some sanity :) Please download our Postman collection for complete details.

curl --location --request PUT 'https://identity1.sqoopdata.local:17143/openam/json/realms/emr/realm-config/agents/OAuth2Client/client-application' \
--header 'Content-Type: application/json' \
--header 'X-Requested-With: ForgeRock Collection' \
--header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=EnGrBQWK7dPPmSuYS_HPAwBfUQI.*AAJTSQACMDIAAlNLABw2S0dPY0ZUOUpQK0YyVHcvT1A5QjJSS05sRHM9AAR0eXBlAANDVFMAAlMxAAIwMQ..*' \
--data-raw '{
    "coreOAuth2ClientConfig": {
        "userpassword": "password",
        "scopes": {
            "inherited": false,
            "value": [
                "users",
                "appointments",
                "healthrecords"
            ]
        },
        "redirectionUris": {
            "inherited": false,
            "value": ["https://ig1.sqoopdata.local:17193/oauth2"]
        },
        ...
    ...
}'

Response

Note: Response truncated. Please download our Postman collection for complete details.

{
    "_id": "client-application",
    "_rev": "1376747112",
    "coreOAuth2ClientConfig": {
        "userpassword": null,
        "loopbackInterfaceRedirection": {
            "inherited": false,
            "value": false
        },
        "defaultScopes": {
            "inherited": false,
            "value": []
        },
        "refreshTokenLifetime": {
            "inherited": false,
            "value": 0
        },
        "scopes": {
            "inherited": false,
            "value": [
                "users",
                "appointments",
                "healthrecords"
            ]
        },
        ...
    ...
}
Step4: Create Resource Server (RS)

Request

Note: Request truncated. Please download our Postman collection for complete details.

curl --location --request PUT 'https://identity1.sqoopdata.local:17143/openam/json/realms/emr/realm-config/agents/OAuth2Client/resource-server' \
--header 'Content-Type: application/json' \
--header 'X-Requested-With: ForgeRock Collection' \
--header 'Cookie: amlbcookie=01; iPlanetDirectoryPro=EnGrBQWK7dPPmSuYS_HPAwBfUQI.*AAJTSQACMDIAAlNLABw2S0dPY0ZUOUpQK0YyVHcvT1A5QjJSS05sRHM9AAR0eXBlAANDVFMAAlMxAAIwMQ..*' \
--data-raw '{
    "coreOAuth2ClientConfig": {
        "userpassword": "password",
        "scopes": {
            "inherited": false,
            "value": [
                "am-introspect-all-tokens"
            ]
        },
        ...
    ...
}'

Response

Note: Response truncated. Please download our Postman collection for complete details.

{
    "_id": "resource-server",
    "_rev": "-1497047801",
    "coreOAuth2ClientConfig": {
        "userpassword": null,
        "loopbackInterfaceRedirection": {
            "inherited": false,
            "value": false
        },
        "defaultScopes": {
            "inherited": false,
            "value": []
        },
        ...
    ...
}

Identity Gateway Routes

Out of the box, the MaDOC API is not OAuth2 compliant; it does not know how to do the OAuth2 dance with the Authorization Server (AS). To bridge this gap, we will stand up IG to proxy MaDOC API calls. IG will act as a Resource Server and do the needed OAuth2 dance with the AS.

As part of this step, go ahead and copy our IG configuration provided in the ig-config directory of the source code to your IG instance.

Let’s Test

With all the needed OAuth2 infrastructure set up, we are all set to test if IG can protect our endpoints or not.

Since IG is proxying the MaDOC API, let’s see what would happen if we call the /users endpoint without an access token.

Request

curl --location --request GET 'https://ig1.sqoopdata.local:17193/users' \
--header 'Content-Type: application/json'

Response

We get a 401 Unauthorized error.

HTTP/1.1 401 
WWW-Authenticate: Bearer realm="OpenIG"
Content-Length: 0
Date: Fri, 27 Aug 2021 16:48:31 GMT

To access the protected endpoints, we need to obtain an access token. So let’s run through Steps 5 and 7 provided in the Demo directory of the Postman collection. The outcome of these steps essentially obtains the user session, authorization code, and access token.

Assuming you have obtained the access token, let’s re-run the previous request, passing the authorization bearer token in the header.

Request

curl --location --request GET 'https://ig1.sqoopdata.local:17193/users' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TIf9O_BtX2SG7Zz00akb-_G-OHA'

Response

Viola! We get a successful response back from the MaDOC API. Let’s try to understand what really happened in this request:

  • IG extracted the access token sent in the authorization header;
  • IG validated the access token against AS;
  • IG validated the access token has the scopes required by the filter configuration, i.e. “users” in this case;
  • After validating the request, IG forwarded the request to MaDOC API.
[
    {
        "id": 1,
        "username": "alice",
        "firstName": "Alice",
        "lastName": "Roy",
        "created": "2021-08-26T18:48:09.844768Z",
        "utype": 2
    },
    {
        "id": 2,
        "username": "bob",
        "firstName": "Bob",
        "lastName": "Nomandy",
        "created": "2021-08-27T16:42:45.515611Z",
        "utype": 1
    },
    {
        "id": 3,
        "username": "mark",
        "firstName": "Mark",
        "lastName": "Manning",
        "created": "2021-08-27T16:44:00.538517Z",
        "utype": 3
    }
]

Github Source Code

The companion source code for this blog post includes the following:

You can find the source code at our repository hosted at Github here.

You can find the source code for the MaDOC API at our Github here.

Summary

There you have it; our MaDOC API is now successfully protected with the OAuth2 protocol using the ForgeRock Platform. The remarkable thing is the API itself does not know the security protecting its endpoints, nor was it altered in any way to deliver the needed security posture.

If you enjoyed reading this post, stay tuned for the upcoming posts in the series.

References

Gartner https://www.gartner.com/en/webinars/4002323/api-security-protect-your-apis-from-attacks-and-data-breaches

OAuth2 RFC6749 https://datatracker.ietf.org/doc/html/rfc6749

OIDC 1.0 https://openid.net/specs/openid-connect-core-1_0.html

ForgeRock OAuth2 Guide https://backstage.forgerock.com/docs/am/7.1/oauth2-guide/