Author: Mohammad Mazhar Ansari

MuleSoft provides OAuth 2.0 policy and can be applied on API’s using API manager however there are chances that clients don’t have API Manager subscription but still want to use OAuth 2.0. In this blog, we will try to learn how we can implement OKTA (OAuth 2.0) policy in MuleSoft without an API manager.

What is OAuth 2.0?

  • OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.

What is OKTA?

  • OAuth 2.0 is the industry-standard protocol for authorization. OKTA extends OAuth 2.0 and provides authentication along with authorization.

Register with OKTA Developer:

Get Client ID and Client Secret:

  • Add a new application by clicking on Application menu -> Add Application.
  • Click on Web -> Next
  • Fill all information and Click on Done
  • Copy the Client Id and Client Secret to use in MuleSoft

Validate Request Component:

This is a reusable component that can be used across multiple projects and need to be placed before API Kit Router or just after HTTP/HTTPS Listener.

The flow of the process is as follows:

  • Store Method and Request Path
  • Call Okta (OAuth 2.0) Validation to validate access token passed with request
  • Validate if the access token is active or not
  • If access token is valid, read the scope permission mapping (okta.json)
  • Validate whether Scope, Method and Request path exists in permission mapping
  • If yes log success message
  • Else log failure message and raise unauthorized access error
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:batch="http://www.mulesoft.org/schema/mule/batch" xmlns:file="http://www.mulesoft.org/schema/mule/file"
	xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
	xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/batch http://www.mulesoft.org/schema/mule/batch/current/mule-batch.xsd">
	<sub-flow name="sf-validate-request" doc:id="c534e155-9e52-411c-84cf-47ea18850acf" >
		<ee:transform doc:name="DW Map for vRequestParameters" doc:id="1ee4cab5-bebf-420a-a4eb-510e840318d7">
			<ee:message>
			</ee:message>
			<ee:variables>
				<ee:set-variable resource="dataweave/vRequestParameters.dwl" variableName="vRequestParameters" />
			</ee:variables>
		</ee:transform>
		<http:request method="POST" doc:name="Request for Validation Okta access token " doc:id="f4139a0b-07ab-4e8a-b0b9-3c24118ec999" config-ref="HTTP_Request_configuration_token" path="/introspect" target="vValidationOutput">
			<http:headers><![CDATA[#[output application/java
---
{
	"Content-Type" : "application/x-www-form-urlencoded"
}]]]></http:headers>
			<http:query-params><![CDATA[#[output application/java
---
{
	"client_secret" : p('okta.clientSecret'),
	"token_type_hint" : attributes.queryParams.token_type_hint,
	"client_id" : p('okta.clientId'),
	"token" : attributes.queryParams.token
}]]]></http:query-params>
		</http:request>
		<choice doc:name="Check If Token Valid" doc:id="925b8573-82f9-4a28-be24-b10b009d8516" >
			<when expression="#[vars.validationOutput.active == true]">
				<file:read doc:name="Read Permission Mapping" doc:id="95070a85-2d17-4a69-894f-f5c29025e754" path="/okta.json" target="permissions"/>
				<ee:transform doc:name="DW Map To validate Request" doc:id="e3feae14-1d18-49cd-943f-68e1604b848e">
							<ee:message>
							</ee:message>
					<ee:variables >
						<ee:set-variable variableName="validateScope" ><![CDATA[%dw 2.0
output application/json
var permissions = vars.vPermissions
var scope = vars.vValidationOutput.scope
var method = vars.vRequestParameters.method
var url = vars.vRequestParameters.url
var urlArray =  (url default "" )splitBy "/"
fun matchURL (url1: String, urlArray: Array) = !((url1 splitBy "/" map (item, index) -> {
    result: if (upper(urlArray[index]) == upper(item) or item == "*") true
            else false
}).result contains false) and (sizeOf(url1 splitBy "/") == sizeOf(urlArray))
---
sizeOf((permissions default []) filter (upper($.scope) == upper(scope) and upper($.method) == upper(method) and matchURL($.url, urlArray))) > 0]]></ee:set-variable>
					</ee:variables>
						</ee:transform>
				<choice doc:name="Check If Request Parameters valid" doc:id="06be1d67-5204-40da-9b18-29ed5aa88939">
					<when expression="#[vars.validateScope == true]">
						<logger level="INFO" doc:name="Logger" doc:id="17af9b42-f4bb-4d0a-b363-ab146c0f50cb" message='#["Record found for Method: " ++ (vars.vRequestParameters.method default "" )++ " Request Path: " ++ (vars.vRequestParameters.url default "")]'/>
					</when>
					<otherwise >
						<logger level="INFO" doc:name="Logger" doc:id="beedc9de-392d-48b4-aae4-471169c1173e" message='#["No Record found for Method: " ++ (vars.vRequestParameters.method default "" )++ " Request Path: " ++ (vars.vRequestParameters.url default "")
++ " Scope: " ++ (vars.validationOutput.scope default "")]' />
						<raise-error doc:name="Raise UNAUTHORIZED error" doc:id="07c04ebe-3140-4e3d-8fe4-5ee4818ff38d" type="HTTP:UNAUTHORIZED" description="UNAUTHORIZED" />
					</otherwise>
				</choice>
			</when>
			<otherwise >
				<logger level="INFO" doc:name="Logger" doc:id="77f0bf6d-1f8f-4524-9898-15414e2892f8" />
				<raise-error doc:name="Raise UNAUTHORIZED error" doc:id="778de6b4-4fb9-4f92-b96f-115646a74911" type="HTTP:UNAUTHORIZED" description="UNAUTHORIZED" />
			</otherwise>
		</choice>
	</sub-flow>
</mule>
  • Sample API
  • For above API okta.json will look like below:
[
  {
    "scope": "SCOPE01",
    "method": "GET",
    "url": "/api/test01"
  },{
    "scope": "SCOPE02",
    "method": "POST",
    "url": "/api/test01"
  },{
    "scope": "SCOPE03",
    "method": "GET",
    "url": "/api/test02"
  },{
    "scope": "SCOPE03",
    "method": "GET",
    "url": "/api/test03"
  },{
    "scope": "SCOPE04",
    "method": "GET",
    "url": "/api/*/test04"
  }
]
  • Scope, Method, and URL combination will define the access

Implementation: 

Insert a flow reference between HTTP Listener and APIkit Router Activity. And call flow is defined in last step.

<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:apikit="http://www.mulesoft.org/schema/mule/mule-apikit" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.mulesoft.org/schema/mule/mule-apikit http://www.mulesoft.org/schema/mule/mule-apikit/current/mule-apikit.xsd ">
    <flow name="okta-oauth2-main">
        <http:listener config-ref="HTTP_Listener_config" path="/api/*">
            <http:response statusCode="#[vars.httpStatus default 200]">
                <http:headers>#[vars.outboundHeaders default {}]</http:headers>
            </http:response>
            <http:error-response statusCode="#[vars.httpStatus default 500]">
                <http:body>#[payload]</http:body>
                <http:headers>#[vars.outboundHeaders default {}]</http:headers>
            </http:error-response>
        </http:listener>
        <flow-ref doc:name="validate request" doc:id="ad6491be-dbfb-4f8b-b4bd-198ca257ff8c" name="sf-validate-request"/>
		<apikit:router config-ref="okta-oauth2-config" />
        <error-handler>
            <on-error-propagate type="APIKIT:BAD_REQUEST">
                <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
                    <ee:message>
                        <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Bad request"}]]></ee:set-payload>
                    </ee:message>
                    <ee:variables>
                        <ee:set-variable variableName="httpStatus">400</ee:set-variable>
                    </ee:variables>
                </ee:transform>
            </on-error-propagate>
            <on-error-propagate type="APIKIT:NOT_FOUND">
                <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
                    <ee:message>
                        <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Resource not found"}]]></ee:set-payload>
                    </ee:message>
                    <ee:variables>
                        <ee:set-variable variableName="httpStatus">404</ee:set-variable>
                    </ee:variables>
                </ee:transform>
            </on-error-propagate>
            <on-error-propagate type="APIKIT:METHOD_NOT_ALLOWED">
                <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
                    <ee:message>
                        <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Method not allowed"}]]></ee:set-payload>
                    </ee:message>
                    <ee:variables>
                        <ee:set-variable variableName="httpStatus">405</ee:set-variable>
                    </ee:variables>
                </ee:transform>
            </on-error-propagate>
            <on-error-propagate type="APIKIT:NOT_ACCEPTABLE">
                <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
                    <ee:message>
                        <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Not acceptable"}]]></ee:set-payload>
                    </ee:message>
                    <ee:variables>
                        <ee:set-variable variableName="httpStatus">406</ee:set-variable>
                    </ee:variables>
                </ee:transform>
            </on-error-propagate>
            <on-error-propagate type="APIKIT:UNSUPPORTED_MEDIA_TYPE">
                <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
                    <ee:message>
                        <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Unsupported media type"}]]></ee:set-payload>
                    </ee:message>
                    <ee:variables>
                        <ee:set-variable variableName="httpStatus">415</ee:set-variable>
                    </ee:variables>
                </ee:transform>
            </on-error-propagate>
            <on-error-propagate type="APIKIT:NOT_IMPLEMENTED">
                <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
                    <ee:message>
                        <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Not Implemented"}]]></ee:set-payload>
                    </ee:message>
                    <ee:variables>
                        <ee:set-variable variableName="httpStatus">501</ee:set-variable>
                    </ee:variables>
                </ee:transform>
            </on-error-propagate>
        </error-handler>
    </flow>
    <flow name="okta-oauth2-console">
        <http:listener config-ref="HTTP_Listener_config" path="/console/*">
            <http:response statusCode="#[vars.httpStatus default 200]">
                <http:headers>#[vars.outboundHeaders default {}]</http:headers>
            </http:response>
            <http:error-response statusCode="#[vars.httpStatus default 500]">
                <http:body>#[payload]</http:body>
                <http:headers>#[vars.outboundHeaders default {}]</http:headers>
            </http:error-response>
        </http:listener>
        <apikit:console config-ref="okta-oauth2-config" />
        <error-handler>
            <on-error-propagate type="APIKIT:NOT_FOUND">
                <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
                    <ee:message>
                        <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{message: "Resource not found"}]]></ee:set-payload>
                    </ee:message>
                    <ee:variables>
                        <ee:set-variable variableName="httpStatus">404</ee:set-variable>
                    </ee:variables>
                </ee:transform>
            </on-error-propagate>
        </error-handler>
    </flow>
    <flow name="get:\api\test01:okta-oauth2-config">
        <logger level="INFO" message="get:\api\test01:okta-oauth2-config" />
    </flow>
    <flow name="get:\api\test02:okta-oauth2-config">
        <logger level="INFO" message="get:\api\test02:okta-oauth2-config" />
    </flow>
    <flow name="get:\api\test03:okta-oauth2-config">
        <logger level="INFO" message="get:\api\test03:okta-oauth2-config" />
    </flow>
    <flow name="get:\api\(id)\test04:okta-oauth2-config">
        <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core">
            <ee:variables>
                <ee:set-variable variableName="id">attributes.uriParams.'id'</ee:set-variable>
            </ee:variables>
        </ee:transform>
        <logger level="INFO" message="get:\api\(id)\test04:okta-oauth2-config" />
    </flow>
    <flow name="post:\api\test01:okta-oauth2-config">
        <logger level="INFO" message="post:\api\test01:okta-oauth2-config" />
    </flow>
</mule>

Test Application: 

  1. Register a client application in OKTA with a scope and generate client id and client secret. For more detail please refer here.
  2. Get access token using command
  3. curl “https://<domain>.okta.com/oauth2/default/v1/token” -d “grant_type=client_credentials&client_id=<application client ID>&client_secret=<application Client Secret>&response_type=token” -X POST
  1. Access resource for which desired scope have access with access token generate from step # 3
  1. Access resource for which desired scope don’t have access with access token generate from step # 3

Conclusion: Using this reusable component we can implement OAuth 2.0 (OKTA) authentication as well as give resource/role based access using scope.

Leave a Comment