Content based API authorization with
WSO2 APIM

Anuradha Prasanna
7 min readJun 21, 2019

Requirement

A public API exposed from WSO2 APIM was needed for an existing back-end API.

Problem

Existing backend API is a multi purpose core API that provides different services based on the request payload. This means anyone who has credentials to access this API will have access to all the functions it supports which can be pretty dangerous (Eg. if this is a banking API). So this API requires different authorization to access for different payloads. So a content based authorization model is required.

Solution !

Validate the payload contents and few parameters in it and decide whether the calling party is allowed to access the API or not.

Before dive in to solution design,

First we must understand what is the problem in detail.

This API supports below operations

  • Credit card settlement
  • Get credit card statement
  • Get account details
  • Get last 10 account transactions
  • Open new savings/current account

These operations will be mapped to different users with the role

  • Credit card settlement — Credit card user role
  • Get credit card statement — Credit card user role
  • Get account details — Bank account user role
  • Get last 10 account transactions — Bank account user role
  • Open new savings/current account — Bank officer role

Below are the users of each role

  • Credit card customer role — Tim, Gina
  • Bank account customer role — Ricky, Ellen
  • Bank officer role — Tony

So you can see these different roles should be able to access different functionalities only permitted to their role. If you enable this API to the third-parties directly any user who has access credentials to the API can do anything. A credit card customer can create bank account. To make things worse lets think for now this API can also do bank transfers, hows that ? :)

How did I implement the solution !

For the simplicity and the time saving I created content based authorization policies in a WSO2 carbon registry entry so I can refer that in the “In mediation sequence” of the API to decide whether the API access if authorized or not based on the user calls the API.

I could also use a custom API handler to do the same thing(see how custom handlers are created in WSO2 documentation here) but creating Java classes and deploying makes things little painful if you have to modify the policy logic where in a API mediation “In sequence” you can change logic on the fly, at least in a development/test environment (even though you wouldn't do that in a production environment) which makes my life easy and makes things simple.

Firstly I must say above methods are two ways to implement the solution, and this solution does not necessarily have to be the best or one of the best solutions. There are different other ways to implement this solution such as use a in-memory dat store such as Redis or Infinispan to store the authorization policies which makes the API response time much lesser compared to registry based policy solution.
Another solution is to define the content based authorization policies using XACML (eXtensible Access Control Markup Language). Deciding which method to use depends on how much time you have, what kind of a serious integration solution are you implementing or what are the quick technology skills you have handy to start off.

So let me explain the solution the way I did.

See below image that I have added extra two steps to the API manager request pass through process.

So they are in detail,

Identify the payload —

Extract the identifier properties from the payload so that I know how to handle it. In below example payloads shown in the image its the “type” property in the request payload

Tony’s Request

Validate the policy —

This is basically check whether the identified request payload ("type": "open_new_account" in above example) and see if that is allowed for the client who is calling the API. In this client (here the client can be a human user, mobile app, back office system or something else too) is mapped to the OAuth token because you don’t share a OAuth token with multiple parties if you do things right. But one party can have multiple OAuth tokens and this is not a problem. Tokens are always generated against an Application in API manager. So knowing the token from the incoming request means related Application is known.

Client/User > OAuth token > Application

Now the next step is to create authorization policy that maps to an Application, so with this it can be visualized in sequence as

Client/User > OAuth token > Application > Custom Authorization Policy

Now if we define what is permitted or what is not allowed in this policy we are good to go with the solution.

Now lets try to “exampalize” this,

Tony is a bank officer who is in the “Bank Officer” role and access the API with the API token 3849–3be4–9322–612a04ab4 and this token is issued to the Application canned “BankOfficerAPIApp” and now above sequence becomes

Tony > 3849–3be4–9322–612a04ab4 > BankOfficerAPIApp > to below

<Authorization>
<Policy>
<App>BankOfficerAPIApp</App>
<AllowedRequestTypes>
<Type>open_new_account</Type>
</AllowedRequestTypes>
</Policy>
</Authorization>

now this says “Tony” can access the API with the request body with type=open_new_account

One important thing to remember — Custom policy name must be same as Application name (BankOfficerAPIApp in here).

Lets implement the solution,

Note I am using WSO2 API Manager version 2.6.0

Lets create the API in WSO2 APIM Publisher portal,
Below shows the complete API implementation steps and code

Firstly, Upload the code below in order to select the in flow sequence in above screen “BankCoreAPI_ValidatePayloadMethodSeq”

<?xml version="1.0" encoding="UTF-8"?>
<sequence name="BankCoreAPI_ValidatePayloadMethodSeq" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<enrich description="Store original payload for later use">
<source type="body"/>
<target property="request_payload" type="property"/>
</enrich>
<property expression="//type" name="request_type" scope="default" type="STRING"/>
<property expression="get-property('registry','conf:/resources/policies/banking_api_auth_policies.xml')" name="resultOM" scope="default" type="OM"/>
<property expression="$ctx:resultOM//Authorization" name="RootOM" scope="default" type="OM"/>
<enrich>
<source clone="true" property="RootOM" type="property"/>
<target type="body"/>
</enrich>
<xslt key="conf:/resources/xslt_processors/policy_processor.xslt">
<property expression="$ctx:api.ut.application.name" name="api_application"/>
<resource key="conf:/resources/xslt_processors/policy_processor.xslt" location="policy_processor.xslt"/>
</xslt>
<property expression="//App" name="application" scope="default" type="STRING"/>
<property expression="//AllowedRequestTypes/Type" name="allowed_types_collection" scope="default" type="STRING"/>
<filter xpath="get-property('application')=$ctx:api.ut.application.name">
<then/>
<else>
<payloadFactory media-type="xml">
<format>
<Fault>
<detail>
<ServerFault>
<error_code>500900</error_code>
<error_message>The client not configured for API authorization policies</error_message>
</ServerFault>
</detail>
</Fault>
</format>
</payloadFactory>
<property name="HTTP_SC" scope="axis2" type="STRING" value="501"/>
<property name="RESPONSE" scope="axis2" type="STRING" value="true"/>
<header action="remove" name="To" scope="default"/>
<property name="messageType" scope="axis2" type="STRING" value="application/json"/>
<respond/>
</else>
</filter>
<filter regex="true" source="fn:contains(get-property('allowed_types_collection'),get-property('request_type'))">
<then/>
<else>
<payloadFactory media-type="xml">
<format>
<Fault>
<detail>
<ServerFault>
<error_code>500950</error_code>
<error_message>Client request is not authorized to call the API</error_message>
</ServerFault>
</detail>
</Fault>
</format>
<args/>
</payloadFactory>
<property name="HTTP_SC" scope="axis2" type="STRING" value="401"/>
<property name="RESPONSE" scope="axis2" type="STRING" value="true"/>
<header action="remove" name="To" scope="default"/>
<property name="messageType" scope="axis2" type="STRING" value="application/json"/>
<respond/>
</else>
</filter>
<enrich description="Restore original payload">
<source clone="false" property="request_payload" type="property"/>
<target type="body"/>
</enrich>
</sequence>

You can quickly create a backend service mock to test this with any mock framework you like, I like Mockoon.

Save and Publish the API.

When the API is created it can be accessed with below API URI https://localhost:8243/services/bankingcore/v1/

Now,

Lets create the necessary registry resources which are dependencies for the “BankCoreAPI_ValidatePayloadMethodSeq” in flow sequence we used

create the file, banking_api_auth_policies.xml with below content, this specifies which Applications/roles are allowed to access which request types

<?xml version="1.0" encoding="UTF-8"?>
<localEntry key="MyAPI_Auth_Policies">
<Authorization>
<Policy>
<App>BankOfficerAPIApp</App>
<AllowedRequestTypes>
<Type>open_new_account</Type>
</AllowedRequestTypes>
</Policy>
<Policy>
<App>BankAccountCustomerAPIApp</App>
<AllowedRequestTypes>
<Type>account_details</Type>
<Type>last_ten_transactions</Type>
</AllowedRequestTypes>
</Policy>
<Policy>
<App>CreditCardCustomerAPIApp</App>
<AllowedRequestTypes>
<Type>creditcard_settlement</Type>
<Type>creditcard_statement</Type>
</AllowedRequestTypes>
</Policy>
</Authorization>
</localEntry>

and
create the file policy_processor.xslt with content below

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="api_application"/>
<xsl:template match="/">
<root xmlns=" ">
<xsl:for-each select="//Policy">
<xsl:if test="App=$api_application">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

Upload both of these files to paths in registry

banking_api_auth_policies.xml > /_system/config/resources/policies

policy_processor.xslt > /_system/config/resources/xslt_processors

Finally,

Below screen shows how the API Application is created, Notice the Application name is “BankOfficerAPIApp”

Now generate OAuth tokens for this Application.

Subscribe the new API BankingCoreAPI to the new Application BankOfficerAPIApp.

You can create different API Applications like above steps shows for the other client roles “BankAccountCustomer” and “CreditCardCustomer”

Now, to test

  1. Access the API with Tony’s request with the OAuth token generated for the “BankOfficerAPIApp”, this becomes successful !
  2. Access the API with Tony’s request with the OAuth token generated for “BankAccountCustomer”, this becomes failed and you will get a HTTP 401 error because the token for is not allowed to call the requests with type “open_new_account”

This concept can be applies to any such high level requirement with slight modifications to correctly identify which users authorized to access which content and operations.

Hope you have learnt something useful !!!

--

--

Anuradha Prasanna

an enterprise architect, technology enthusiast, dog lover , music maniac, a husband & a father ! 🐶