Integrating With the Broadcom CA Layer7 API Gateway

Integrating With the Broadcom CA Layer7 API Gateway

tutorials

This tutorial describes how to integrate the Broadcom CA Layer7 API Gateway with the Curity Identity Server. The Broadcom CA Layer7 API Gateway is a security gateway that proxies HTTP traffic and performs security checks on the data and identity layers.

The Gateway will be configured to use access tokens issued by the Curity Identity Server to protect API access. In this example architecture, the Curity Identity Server sits behind the Gateway as well. But, that is not a requirement, and it does not affect the implementation of the API protection.

Phantom Tokens

This integration uses the concept of Phantom Tokens. The introspect endpoint will return a JWT version of the incoming access token referred to as a Phantom Token. To configure the introspect endpoint to return Phantom Tokens see Introspection and Phantom Tokens.

Protecting APIs

The API is protected by having the Gateway check for an access token in the incoming request’s Authorization header. For the Gateway to validate the incoming access token, it must do two things:

  1. If the token hasn’t been seen before, the Gateway must ping the OAuth server (Introspection).
  2. The Gateway must cache the response from the OAuth server in the Gateway cache (but not in the Gateway DB, which is considerably more resource-consuming).

Token Verification using the CA API Gateway

  1. The Web-Backend makes a request towards the OAuth-protected API.
  2. The Gateway first tries to find the Access Token in the cache. If there’s a miss, it calls the OAuth server’s Introspect endpoint.
  3. The OAuth server responds with token information and an Access Token in JSON Web Token format (JWT).
  4. The Gateway caches the response for the lifetime of the token.
  5. The Gateway forwards the request to the OAuth-protected service (the API), passing the JWT instead of the original access token in the Authorization header back to the protected service.

Setting up the Gateway to Validate Access Tokens

To validate an access token, a new policy fragment must be created. Its task is to perform the operations described in the previous section. The benefit of the policy fragment is that it can be included in any service published by the gateway.

Note

The steps described to create this policy are made using the Broadcom CA Layer7 API Gateway version 9.1. The same tasks are possible with any version 8.x of the gateway, with minor differences on where to find the assertions, and the look and feel.

1. Create a New Policy

Use the Create Policy option in the policies window, and select Included Fragment as policy type. Name it Require OAuth Token.

Create new Policy

2. Define Cluster Properties

To make the policy portable between environments, it’s recommended to use cluster properties when applicable. There are three variables and one credential that need to be defined in this policy.

Variables

From the Tasks menu, open Global Settings -> Manage Cluster-Wide Properties and add:

  • oauth_introspect_client_id = the_name_of_the_gateway_client
  • oauth_server_host = the hostname of the Curity Identity Server (eg: https://idsvr.example.com:8443)
  • oauth_server_introspection_path = the path of the introspection endpoint as configured in the Curity Identity Server (eg: /oauth/v2/oauth-introspect)

Define Cluster Wide Properties

Credentials

From the Task menu, open Certificates, Keys and Secrets -> Manage Stored Passwords and add:

oauth_introspect_clientsecret = the client secret

Define Cluster Wide Credentials

3. Add the Policy Content

To add the policy content, open the newly created Require OAuth Token policy and copy the XML from the Appendix A: – Require OAuth Token fragment in this document and paste it into the window. This should create a structure that looks like the following:

Add the policy content

After saving and activating, the policy fragment is ready to use.

Important

If self-signed certificates or an internal CA is used for issuing the certificates for the Curity Identity Server, it must be imported into the Gateway’s trust store before the policy can be used.

4. Create Encapsulated Assertion

This step is optional but makes the enforcement easier to use. To create an encapsulated assertion, right-click on the Require OAuth Token policy in the service window and select Create Encapsulated Assertion. Enter the oauth.* variables (oauth.client_id, oauth.subject, oauth.expiration, oauth.jwt, oauth.scope) as output variables and place the assertion in the Access Control section.

Create Encapsulated Assertion

Enforcing Access Token in API Policies

Now that the Require OAuth Token policy is configured, it can be included in all services that it should protect.

The included fragment (or the encapsulated assertion) should be placed at the beginning of the service policy after the Require SSL or TLS transport assertion.

Exported variables

The Require OAuth Token fragment will export the following variables:

  • oauth.client_id
  • oauth.subject
  • oauth.expiration
  • oauth.jwt
  • oauth.scope

Note

This assumes that The Curity Identity Server is configured to respond with a JWT from the introspection endpoint. See Introspection and Phantom Tokens for more details about the introspection endpoint and how to include a JWT.

Passing the JWT to Backend APIs

After a successful lookup of the Access Token, the service will eventually route to a backend API. At this point, it should pass on the internal JWT it received from the introspect endpoint. This is done by adding the oauth.jwt to the Authorization header of the routed request.

Passing JWT to backend

Introspect With application/jwt as Accept Header

The Curity Identity Server can also respond to requests in the introspection endpoint with the Accept: application/jwt header. When introspecting a valid access token, The Curity Identity Server responds with 200 OK and the JWT in the response body. An expired or invalid access token causes The Curity Identity Server to respond with 204 No Content. This means that the gateway doesn’t need to parse the JSON (as when using normal introspection), making the proxying even faster. Appendix B: – Require OAuth Token, introspect with application/jwt contains a policy fragment that uses application/jwt. Also, the Curity Identity Server’s status codes on the introspection request are considered, and the Gateway responds accordingly. This fragment only exports the variable oauth.jwt, which can replace the access token in the Authorization header forwarded to the API.

Note

For the Broadcom CA Layer 7 API Gateway to be able to parse the response from Curity (which in this case includes the header Content-type: application/jwt), you will need to add another cluster-wide variable with key contentType.otherTextualTypes and value application/jwt.

Appendix A: – Require OAuth Token Fragment

<?xml version="1.0" encoding="UTF-8"?>
<wsp:Policy xmlns:L7p="http://www.layer7tech.com/ws/policy" xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy">
    <wsp:All wsp:Usage="Required">
        <L7p:CommentAssertion>
            <L7p:Comment stringValue="=== Require OAuth Token ==="/>
        </L7p:CommentAssertion>
        <L7p:AuditDetailAssertion>
            <L7p:Detail stringValue="Policy Fragment: Require OAuth Token"/>
        </L7p:AuditDetailAssertion>
        <L7p:CommentAssertion>
            <L7p:Comment stringValue="1. ------ Validate Header -----"/>
        </L7p:CommentAssertion>
        <L7p:CustomizeErrorResponse>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="LEFT.COMMENT"/>
                        <L7p:value stringValue="400"/>
                    </L7p:entry>
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="No token in Authorization header"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "bad_request",
"error_description" : "No access token found in authorization header"
}]]></L7p:Content>
            <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
            <L7p:ExtraHeaders nameValuePairArray="included"/>
            <L7p:HttpStatus stringValue="400"/>
        </L7p:CustomizeErrorResponse>
        <L7p:SetVariable>
            <L7p:Base64Expression stringValue="JHtyZXF1ZXN0Lmh0dHAuaGVhZGVyLmF1dGhvcml6YXRpb259"/>
            <L7p:VariableToSet stringValue="oauth_header"/>
        </L7p:SetVariable>
        <L7p:ComparisonAssertion>
            <L7p:CaseSensitive booleanValue="false"/>
            <L7p:Expression1 stringValue="${oauth_header}"/>
            <L7p:MultivaluedComparison multivaluedComparison="FAIL"/>
            <L7p:Operator operatorNull="null"/>
            <L7p:Predicates predicates="included">
                <L7p:item dataType="included">
                    <L7p:Type variableDataType="string"/>
                </L7p:item>
                <L7p:item binary="included">
                    <L7p:CaseSensitive booleanValue="false"/>
                    <L7p:Negated booleanValue="true"/>
                    <L7p:Operator operator="EMPTY"/>
                    <L7p:RightValue stringValue=""/>
                </L7p:item>
            </L7p:Predicates>
        </L7p:ComparisonAssertion>
        <L7p:Regex>
            <L7p:AutoTarget booleanValue="false"/>
            <L7p:CaptureVar stringValue="introspect_token"/>
            <L7p:OtherTargetMessageVariable stringValue="oauth_header"/>
            <L7p:Regex stringValue="Bearer\s+(.*)"/>
            <L7p:RegexName stringValue="Retrieve token"/>
            <L7p:Replacement stringValue=""/>
            <L7p:Target target="OTHER"/>
        </L7p:Regex>
        <L7p:SetVariable>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="Take first result from regex-match"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X3Rva2VuWzFdfQ=="/>
            <L7p:VariableToSet stringValue="oauth_token"/>
        </L7p:SetVariable>
        <L7p:CommentAssertion>
            <L7p:Comment stringValue="2. ------ Validate Token -----"/>
        </L7p:CommentAssertion>
        <L7p:CustomizeErrorResponse>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="LEFT.COMMENT"/>
                        <L7p:value stringValue="500"/>
                    </L7p:entry>
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="Invalid response from server"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "internal_error",
"error_description" : "Invalid response from OAuth server"
}]]></L7p:Content>
            <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
            <L7p:ExtraHeaders nameValuePairArray="included"/>
        </L7p:CustomizeErrorResponse>
        <wsp:OneOrMore wsp:Usage="Required">
            <wsp:All wsp:Usage="Required">
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue=""/>
                    <L7p:ContentType stringValue="application/json; charset=utf-8"/>
                    <L7p:DataType variableDataType="message"/>
                    <L7p:VariableToSet stringValue="introspect_response"/>
                </L7p:SetVariable>
                <L7p:CacheLookup>
                    <L7p:CacheEntryKey stringValue="${oauth_token}"/>
                    <L7p:CacheId stringValue="oauth_token_cache"/>
                    <L7p:ContentTypeOverride stringValue=""/>
                    <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
                    <L7p:Target target="OTHER"/>
                </L7p:CacheLookup>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Check Cache"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
            <wsp:All wsp:Usage="Required">
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="dG9rZW49JHtvYXV0aF90b2tlbn0mY2xpZW50X2lkPSR7Z2F0ZXdheS5vYXV0aF9pbnRyb3NwZWN0X2NsaWVudF9pZH0mY2xpZW50X3NlY3JldD0ke3NlY3Bhc3Mub2F1dGhfaW50cm9zcGVjdF9jbGllbnRzZWNyZXQucGxhaW50ZXh0fQ=="/>
                    <L7p:ContentType stringValue="application/x-www-form-urlencoded; charset=utf-8"/>
                    <L7p:DataType variableDataType="message"/>
                    <L7p:VariableToSet stringValue="introspect_request"/>
                </L7p:SetVariable>
                <L7p:HttpRoutingAssertion>
                    <L7p:FailOnErrorStatus booleanValue="false"/>
                    <L7p:HttpMethod httpMethod="POST"/>
                    <L7p:ProtectedServiceUrl stringValue="${gateway.oauth_server_host}${gateway.oauth_server_introspection_path}"/>
                    <L7p:ProxyPassword stringValueNull="null"/>
                    <L7p:ProxyUsername stringValueNull="null"/>
                    <L7p:RequestHeaderRules httpPassthroughRuleSet="included">
                        <L7p:ForwardAll booleanValue="true"/>
                        <L7p:Rules httpPassthroughRules="included">
                            <L7p:item httpPassthroughRule="included">
                                <L7p:Name stringValue="Cookie"/>
                            </L7p:item>
                            <L7p:item httpPassthroughRule="included">
                                <L7p:Name stringValue="SOAPAction"/>
                            </L7p:item>
                        </L7p:Rules>
                    </L7p:RequestHeaderRules>
                    <L7p:RequestMsgSrc stringValue="introspect_request"/>
                    <L7p:RequestParamRules httpPassthroughRuleSet="included">
                        <L7p:ForwardAll booleanValue="true"/>
                        <L7p:Rules httpPassthroughRules="included"/>
                    </L7p:RequestParamRules>
                    <L7p:ResponseHeaderRules httpPassthroughRuleSet="included">
                        <L7p:ForwardAll booleanValue="true"/>
                        <L7p:Rules httpPassthroughRules="included">
                            <L7p:item httpPassthroughRule="included">
                                <L7p:Name stringValue="Set-Cookie"/>
                            </L7p:item>
                        </L7p:Rules>
                    </L7p:ResponseHeaderRules>
                    <L7p:ResponseMsgDest stringValue="introspect_response"/>
                    <L7p:SamlAssertionVersion intValue="2"/>
                </L7p:HttpRoutingAssertion>
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X3Jlc3BvbnNlLmh0dHAuaGVhZGVyLmNhY2hlLWNvbnRyb2x9"/>
                    <L7p:VariableToSet stringValue="cache_control"/>
                </L7p:SetVariable>
                <L7p:Regex>
                    <L7p:AutoTarget booleanValue="false"/>
                    <L7p:CaptureVar stringValue="cache_max_age"/>
                    <L7p:CaseInsensitive booleanValue="true"/>
                    <L7p:OtherTargetMessageVariable stringValue="cache_control"/>
                    <L7p:Regex stringValue="max-age=([0-9]+)"/>
                    <L7p:RegexName stringValue="Get Max-age"/>
                    <L7p:Replacement stringValue=""/>
                    <L7p:Target target="OTHER"/>
                </L7p:Regex>
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="JHtjYWNoZV9tYXhfYWdlWzFdfQ=="/>
                    <L7p:VariableToSet stringValue="cache_max_age"/>
                </L7p:SetVariable>
                <L7p:CacheStorage>
                    <L7p:CacheEntryKey stringValue="${oauth_token}"/>
                    <L7p:CacheId stringValue="oauth_token_cache"/>
                    <L7p:MaxEntries stringValue="1000"/>
                    <L7p:MaxEntryAgeSeconds stringValue="${cache_max_age}"/>
                    <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
                    <L7p:Target target="OTHER"/>
                </L7p:CacheStorage>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Call Curity"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
        </wsp:OneOrMore>
        <L7p:EvaluateJsonPathExpression>
            <L7p:Expression stringValue="$.active"/>
            <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
            <L7p:Target target="OTHER"/>
            <L7p:VariablePrefix stringValue="introspect_active"/>
        </L7p:EvaluateJsonPathExpression>
        <L7p:CustomizeErrorResponse>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="LEFT.COMMENT"/>
                        <L7p:value stringValue="401"/>
                    </L7p:entry>
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="Expired or Invalid"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "invalid_request",
"error_description" : "Token invalid or expired"
}]]></L7p:Content>
            <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
            <L7p:ExtraHeaders nameValuePairArray="included"/>
            <L7p:HttpStatus stringValue="401"/>
        </L7p:CustomizeErrorResponse>
        <L7p:ComparisonAssertion>
            <L7p:CaseSensitive booleanValue="false"/>
            <L7p:Expression1 stringValue="${introspect_active.result}"/>
            <L7p:MultivaluedComparison multivaluedComparison="FAIL"/>
            <L7p:Operator operatorNull="null"/>
            <L7p:Predicates predicates="included">
                <L7p:item dataType="included">
                    <L7p:Type variableDataType="boolean"/>
                </L7p:item>
                <L7p:item binary="included">
                    <L7p:CaseSensitive booleanValue="false"/>
                    <L7p:RightValue stringValue="true"/>
                </L7p:item>
            </L7p:Predicates>
        </L7p:ComparisonAssertion>
        <L7p:CustomizeErrorResponse>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="LEFT.COMMENT"/>
                        <L7p:value stringValue="500"/>
                    </L7p:entry>
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="Invalid response from server"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "internal_error",
"error_description" : "Invalid response from OAuth server"
}]]></L7p:Content>
            <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
            <L7p:ExtraHeaders nameValuePairArray="included"/>
        </L7p:CustomizeErrorResponse>
        <L7p:EvaluateJsonPathExpression>
            <L7p:Expression stringValue="$.exp"/>
            <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
            <L7p:Target target="OTHER"/>
            <L7p:VariablePrefix stringValue="introspect_expire"/>
        </L7p:EvaluateJsonPathExpression>
        <L7p:EvaluateJsonPathExpression>
            <L7p:Expression stringValue="$.sub"/>
            <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
            <L7p:Target target="OTHER"/>
            <L7p:VariablePrefix stringValue="introspect_subject"/>
        </L7p:EvaluateJsonPathExpression>
        <L7p:EvaluateJsonPathExpression>
            <L7p:Expression stringValue="$.scope"/>
            <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
            <L7p:Target target="OTHER"/>
            <L7p:VariablePrefix stringValue="introspect_scope"/>
        </L7p:EvaluateJsonPathExpression>
        <L7p:EvaluateJsonPathExpression>
            <L7p:Expression stringValue="$.client_id"/>
            <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
            <L7p:Target target="OTHER"/>
            <L7p:VariablePrefix stringValue="introspect_clientid"/>
        </L7p:EvaluateJsonPathExpression>
        <L7p:EvaluateJsonPathExpression>
            <L7p:Expression stringValue="$.jwt"/>
            <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
            <L7p:Target target="OTHER"/>
            <L7p:VariablePrefix stringValue="introspect_jwt"/>
        </L7p:EvaluateJsonPathExpression>
        <L7p:CustomizeErrorResponse>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="LEFT.COMMENT"/>
                        <L7p:value stringValue="401"/>
                    </L7p:entry>
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="Expired or Invalid"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "invalid_request",
"error_description" : "Token invalid or expired"
}]]></L7p:Content>
            <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
            <L7p:ExtraHeaders nameValuePairArray="included"/>
            <L7p:HttpStatus stringValue="401"/>
        </L7p:CustomizeErrorResponse>
        <L7p:SetVariable>
            <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X2V4cGlyZS5yZXN1bHR9"/>
            <L7p:VariableToSet stringValue="oauth.expiration"/>
        </L7p:SetVariable>
        <L7p:ComparisonAssertion>
            <L7p:CaseSensitive booleanValue="false"/>
            <L7p:Expression1 stringValue="${oauth.expiration}"/>
            <L7p:MultivaluedComparison multivaluedComparison="FAIL"/>
            <L7p:Operator operatorNull="null"/>
            <L7p:Predicates predicates="included">
                <L7p:item dataType="included">
                    <L7p:Type variableDataType="int"/>
                </L7p:item>
                <L7p:item binary="included">
                    <L7p:Operator operator="GT"/>
                    <L7p:RightValue stringValue="${gateway.time.seconds}"/>
                </L7p:item>
            </L7p:Predicates>
        </L7p:ComparisonAssertion>
        <L7p:SetVariable>
            <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X3N1YmplY3QucmVzdWx0fQ=="/>
            <L7p:VariableToSet stringValue="oauth.subject"/>
        </L7p:SetVariable>
        <L7p:SetVariable>
            <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X3Njb3BlLnJlc3VsdH0="/>
            <L7p:VariableToSet stringValue="oauth.scope"/>
        </L7p:SetVariable>
        <L7p:SetVariable>
            <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X2NsaWVudGlkLnJlc3VsdH0="/>
            <L7p:VariableToSet stringValue="oauth.client_id"/>
        </L7p:SetVariable>
        <L7p:SetVariable>
            <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X2p3dC5yZXN1bHR9"/>
            <L7p:VariableToSet stringValue="oauth.jwt"/>
        </L7p:SetVariable>
        <L7p:ExportVariables>
            <L7p:ExportedVars stringArrayValue="included">
                <L7p:item stringValue="oauth.client_id"/>
                <L7p:item stringValue="oauth.expiration"/>
                <L7p:item stringValue="oauth.jwt"/>
                <L7p:item stringValue="oauth.scope"/>
                <L7p:item stringValue="oauth.subject"/>
            </L7p:ExportedVars>
        </L7p:ExportVariables>
    </wsp:All>
</wsp:Policy>

Appendix B: – Require OAuth Token, introspect with application/jwt

<?xml version="1.0" encoding="UTF-8"?>
<wsp:Policy xmlns:L7p="http://www.layer7tech.com/ws/policy" xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy">
    <wsp:All wsp:Usage="Required">
        <L7p:CommentAssertion>
            <L7p:Comment stringValue="=== Require OAuth Token ==="/>
        </L7p:CommentAssertion>
        <L7p:AuditDetailAssertion>
            <L7p:Detail stringValue="Policy Fragment: Require OAuth Token"/>
        </L7p:AuditDetailAssertion>
        <L7p:CommentAssertion>
            <L7p:Comment stringValue="1. ------ Validate Header -----"/>
        </L7p:CommentAssertion>
        <L7p:AuditDetailAssertion>
            <L7p:Detail stringValue="Policy Fragment: Require OAuth Token"/>
        </L7p:AuditDetailAssertion>
        <L7p:CustomizeErrorResponse>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="LEFT.COMMENT"/>
                        <L7p:value stringValue="401"/>
                    </L7p:entry>
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="No token in Authorization header"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "unauthorized",
"error_description" : "No access token found in authorization header"
}]]></L7p:Content>
            <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
            <L7p:ExtraHeaders nameValuePairArray="included"/>
            <L7p:HttpStatus stringValue="401"/>
        </L7p:CustomizeErrorResponse>
        <L7p:SetVariable>
            <L7p:Base64Expression stringValue="JHtyZXF1ZXN0Lmh0dHAuaGVhZGVyLmF1dGhvcml6YXRpb259"/>
            <L7p:VariableToSet stringValue="oauth_header"/>
        </L7p:SetVariable>
        <L7p:ComparisonAssertion>
            <L7p:CaseSensitive booleanValue="false"/>
            <L7p:Expression1 stringValue="${oauth_header}"/>
            <L7p:MultivaluedComparison multivaluedComparison="FAIL"/>
            <L7p:Operator operatorNull="null"/>
            <L7p:Predicates predicates="included">
                <L7p:item dataType="included">
                    <L7p:Type variableDataType="string"/>
                </L7p:item>
                <L7p:item binary="included">
                    <L7p:CaseSensitive booleanValue="false"/>
                    <L7p:Negated booleanValue="true"/>
                    <L7p:Operator operator="EMPTY"/>
                    <L7p:RightValue stringValue=""/>
                </L7p:item>
            </L7p:Predicates>
        </L7p:ComparisonAssertion>
        <L7p:Regex>
            <L7p:AutoTarget booleanValue="false"/>
            <L7p:CaptureVar stringValue="introspect_token"/>
            <L7p:OtherTargetMessageVariable stringValue="oauth_header"/>
            <L7p:Regex stringValue="Bearer\s+(.*)"/>
            <L7p:RegexName stringValue="Retrieve token"/>
            <L7p:Replacement stringValue=""/>
            <L7p:Target target="OTHER"/>
        </L7p:Regex>
        <L7p:SetVariable>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="Take first result from regex-match"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X3Rva2VuWzFdfQ=="/>
            <L7p:VariableToSet stringValue="oauth_token"/>
        </L7p:SetVariable>
        <L7p:CommentAssertion>
            <L7p:Comment stringValue="2. ------ Validate Token -----"/>
        </L7p:CommentAssertion>
        <L7p:CustomizeErrorResponse>
            <L7p:AssertionComment assertionComment="included">
                <L7p:Properties mapValue="included">
                    <L7p:entry>
                        <L7p:key stringValue="LEFT.COMMENT"/>
                        <L7p:value stringValue="502"/>
                    </L7p:entry>
                    <L7p:entry>
                        <L7p:key stringValue="RIGHT.COMMENT"/>
                        <L7p:value stringValue="Generic error"/>
                    </L7p:entry>
                </L7p:Properties>
            </L7p:AssertionComment>
            <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "bad_gateway",
"error_description" : "Bad Gateway"
}]]></L7p:Content>
            <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
            <L7p:ExtraHeaders nameValuePairArray="included"/>
            <L7p:HttpStatus stringValue="502"/>
        </L7p:CustomizeErrorResponse>
        <wsp:OneOrMore wsp:Usage="Required">
            <wsp:All wsp:Usage="Required">
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue=""/>
                    <L7p:ContentType stringValue="application/json; charset=utf-8"/>
                    <L7p:DataType variableDataType="message"/>
                    <L7p:VariableToSet stringValue="introspect_response"/>
                </L7p:SetVariable>
                <L7p:CacheLookup>
                    <L7p:CacheEntryKey stringValue="${oauth_token}"/>
                    <L7p:CacheId stringValue="oauth_token_cache"/>
                    <L7p:ContentTypeOverride stringValue=""/>
                    <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
                    <L7p:Target target="OTHER"/>
                </L7p:CacheLookup>
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="MjAw"/>
                    <L7p:DataType variableDataType="int"/>
                    <L7p:VariableToSet stringValue="introspect_response_code"/>
                </L7p:SetVariable>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Check Cache"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
            <wsp:All wsp:Usage="Required">
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="dG9rZW49JHtvYXV0aF90b2tlbn0mY2xpZW50X2lkPSR7Z2F0ZXdheS5vYXV0aF9pbnRyb3NwZWN0X2NsaWVudF9pZH0mY2xpZW50X3NlY3JldD0ke3NlY3Bhc3Mub2F1dGhfaW50cm9zcGVjdF9jbGllbnRzZWNyZXQucGxhaW50ZXh0fQ=="/>
                    <L7p:ContentType stringValue="application/x-www-form-urlencoded; charset=utf-8"/>
                    <L7p:DataType variableDataType="message"/>
                    <L7p:VariableToSet stringValue="introspect_request"/>
                </L7p:SetVariable>
                <L7p:HttpRoutingAssertion>
                    <L7p:FailOnErrorStatus booleanValue="false"/>
                    <L7p:HttpMethod httpMethod="POST"/>
                    <L7p:ProtectedServiceUrl stringValue="${gateway.oauth_server_host}${gateway.oauth_server_introspection_path}"/>
                    <L7p:ProxyPassword stringValueNull="null"/>
                    <L7p:ProxyUsername stringValueNull="null"/>
                    <L7p:RequestHeaderRules httpPassthroughRuleSet="included">
                        <L7p:Rules httpPassthroughRules="included">
                            <L7p:item httpPassthroughRule="included">
                                <L7p:CustomizeValue stringValue="application/jwt"/>
                                <L7p:Name stringValue="accept"/>
                                <L7p:UsesCustomizedValue booleanValue="true"/>
                            </L7p:item>
                        </L7p:Rules>
                    </L7p:RequestHeaderRules>
                    <L7p:RequestMsgSrc stringValue="introspect_request"/>
                    <L7p:RequestParamRules httpPassthroughRuleSet="included">
                        <L7p:ForwardAll booleanValue="true"/>
                        <L7p:Rules httpPassthroughRules="included"/>
                    </L7p:RequestParamRules>
                    <L7p:ResponseHeaderRules httpPassthroughRuleSet="included">
                        <L7p:ForwardAll booleanValue="true"/>
                        <L7p:Rules httpPassthroughRules="included"/>
                    </L7p:ResponseHeaderRules>
                    <L7p:ResponseMsgDest stringValue="introspect_response"/>
                    <L7p:SamlAssertionVersion intValue="2"/>
                </L7p:HttpRoutingAssertion>
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="JHtodHRwUm91dGluZy5yZWFzb25Db2RlfQ=="/>
                    <L7p:DataType variableDataType="int"/>
                    <L7p:VariableToSet stringValue="introspect_response_code"/>
                </L7p:SetVariable>
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X3Jlc3BvbnNlLmh0dHAuaGVhZGVyLmNhY2hlLWNvbnRyb2x9"/>
                    <L7p:VariableToSet stringValue="cache_control"/>
                </L7p:SetVariable>
                <wsp:OneOrMore wsp:Usage="Required">
                    <wsp:All wsp:Usage="Required">
                        <L7p:Regex>
                            <L7p:AutoTarget booleanValue="false"/>
                            <L7p:CaptureVar stringValue="cache_max_age"/>
                            <L7p:CaseInsensitive booleanValue="true"/>
                            <L7p:OtherTargetMessageVariable stringValue="cache_control"/>
                            <L7p:Regex stringValue="max-age=([0-9]+)"/>
                            <L7p:RegexName stringValue="Get Max-age"/>
                            <L7p:Replacement stringValue=""/>
                            <L7p:Target target="OTHER"/>
                        </L7p:Regex>
                        <L7p:SetVariable>
                            <L7p:Base64Expression stringValue="JHtjYWNoZV9tYXhfYWdlWzFdfQ=="/>
                            <L7p:VariableToSet stringValue="cache_max_age"/>
                        </L7p:SetVariable>
                        <L7p:CacheStorage>
                            <L7p:CacheEntryKey stringValue="${oauth_token}"/>
                            <L7p:CacheId stringValue="oauth_token_cache"/>
                            <L7p:MaxEntries stringValue="1000"/>
                            <L7p:MaxEntryAgeSeconds stringValue="${cache_max_age}"/>
                            <L7p:OtherTargetMessageVariable stringValue="introspect_response"/>
                            <L7p:Target target="OTHER"/>
                        </L7p:CacheStorage>
                    </wsp:All>
                    <wsp:OneOrMore wsp:Usage="Required">
                        <L7p:ComparisonAssertion>
                            <L7p:CaseSensitive booleanValue="false"/>
                            <L7p:Expression1 stringValue="${cache_control}"/>
                            <L7p:Operator operatorNull="null"/>
                            <L7p:Predicates predicates="included">
                                <L7p:item dataType="included">
                                <L7p:Type variableDataType="string"/>
                                </L7p:item>
                                <L7p:item binary="included">
                                <L7p:Operator operator="EMPTY"/>
                                <L7p:RightValue stringValue=""/>
                                </L7p:item>
                            </L7p:Predicates>
                        </L7p:ComparisonAssertion>
                    </wsp:OneOrMore>
                    <L7p:assertionComment>
                        <L7p:Properties mapValue="included">
                            <L7p:entry>
                                <L7p:key stringValue="RIGHT.COMMENT"/>
                                <L7p:value stringValue="Only cache the response when cache_control header has max-age"/>
                            </L7p:entry>
                        </L7p:Properties>
                    </L7p:assertionComment>
                </wsp:OneOrMore>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Call Curity"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
        </wsp:OneOrMore>
        <L7p:CommentAssertion>
            <L7p:Comment stringValue="3. ------ Check Curity's response status code ------"/>
        </L7p:CommentAssertion>
        <wsp:OneOrMore wsp:Usage="Required">
            <wsp:All wsp:Usage="Required">
                <L7p:ComparisonAssertion>
                    <L7p:CaseSensitive booleanValue="false"/>
                    <L7p:Expression1 stringValue="${introspect_response_code}"/>
                    <L7p:MultivaluedComparison multivaluedComparison="FIRST"/>
                    <L7p:Operator operatorNull="null"/>
                    <L7p:Predicates predicates="included">
                        <L7p:item dataType="included">
                            <L7p:Type variableDataType="int"/>
                        </L7p:item>
                        <L7p:item binary="included">
                            <L7p:RightValue stringValue="200"/>
                        </L7p:item>
                    </L7p:Predicates>
                </L7p:ComparisonAssertion>
                <L7p:SetVariable>
                    <L7p:Base64Expression stringValue="JHtpbnRyb3NwZWN0X3Jlc3BvbnNlLm1haW5wYXJ0fQ=="/>
                    <L7p:VariableToSet stringValue="oauth.jwt"/>
                </L7p:SetVariable>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Curity responds 200"/>
                        </L7p:entry>
                        <L7p:entry>
                            <L7p:key stringValue="RIGHT.COMMENT"/>
                            <L7p:value stringValue="jwt in introspect_response.mainpart"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
            <wsp:All wsp:Usage="Required">
                <L7p:ComparisonAssertion>
                    <L7p:CaseSensitive booleanValue="false"/>
                    <L7p:Expression1 stringValue="${introspect_response_code}"/>
                    <L7p:MultivaluedComparison multivaluedComparison="FIRST"/>
                    <L7p:Operator operatorNull="null"/>
                    <L7p:Predicates predicates="included">
                        <L7p:item dataType="included">
                            <L7p:Type variableDataType="int"/>
                        </L7p:item>
                        <L7p:item binary="included">
                            <L7p:RightValue stringValue="204"/>
                        </L7p:item>
                    </L7p:Predicates>
                </L7p:ComparisonAssertion>
                <L7p:CustomizeErrorResponse>
                    <L7p:AssertionComment assertionComment="included">
                        <L7p:Properties mapValue="included">
                            <L7p:entry>
                                <L7p:key stringValue="LEFT.COMMENT"/>
                                <L7p:value stringValue="400"/>
                            </L7p:entry>
                            <L7p:entry>
                                <L7p:key stringValue="RIGHT.COMMENT"/>
                                <L7p:value stringValue="No token in Authorization header"/>
                            </L7p:entry>
                        </L7p:Properties>
                    </L7p:AssertionComment>
                    <L7p:Content stringValueReference="inline"><![CDATA[{
"error" : "invalid_request",
"error_description" : "Token invalid or expired"
}]]></L7p:Content>
                    <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
                    <L7p:ExtraHeaders nameValuePairArray="included">
                        <L7p:item nameValuePair="included">
                            <L7p:Key stringValue="WWW-Authenticate"/>
                            <L7p:Value stringValue="Bearer error=&quot;invalid_token&quot;"/>
                        </L7p:item>
                    </L7p:ExtraHeaders>
                    <L7p:HttpStatus stringValue="401"/>
                </L7p:CustomizeErrorResponse>
                <L7p:RaiseError/>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Curity responds 204"/>
                        </L7p:entry>
                        <L7p:entry>
                            <L7p:key stringValue="RIGHT.COMMENT"/>
                            <L7p:value stringValue="introspected invalid or expired access_token"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
            <wsp:All wsp:Usage="Required">
                <L7p:ComparisonAssertion>
                    <L7p:CaseSensitive booleanValue="false"/>
                    <L7p:Expression1 stringValue="${introspect_response_code}"/>
                    <L7p:MultivaluedComparison multivaluedComparison="FIRST"/>
                    <L7p:Operator operatorNull="null"/>
                    <L7p:Predicates predicates="included">
                        <L7p:item dataType="included">
                            <L7p:Type variableDataType="int"/>
                        </L7p:item>
                        <L7p:item binary="included">
                            <L7p:RightValue stringValue="503"/>
                        </L7p:item>
                    </L7p:Predicates>
                </L7p:ComparisonAssertion>
                <L7p:CustomizeErrorResponse>
                    <L7p:AssertionComment assertionComment="included">
                        <L7p:Properties mapValue="included">
                            <L7p:entry>
                                <L7p:key stringValue="LEFT.COMMENT"/>
                                <L7p:value stringValue="400"/>
                            </L7p:entry>
                            <L7p:entry>
                                <L7p:key stringValue="RIGHT.COMMENT"/>
                                <L7p:value stringValue="No token in Authorization header"/>
                            </L7p:entry>
                        </L7p:Properties>
                    </L7p:AssertionComment>
                    <L7p:Content stringValueReference="inline"><![CDATA[{
"error": "service_unavailable",
"error_description": "Service Unavailable"
}]]></L7p:Content>
                    <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
                    <L7p:ExtraHeaders nameValuePairArray="included"/>
                    <L7p:HttpStatus stringValue="503"/>
                </L7p:CustomizeErrorResponse>
                <L7p:RaiseError/>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Curity responds 503"/>
                        </L7p:entry>
                        <L7p:entry>
                            <L7p:key stringValue="RIGHT.COMMENT"/>
                            <L7p:value stringValue="Service Unavailable"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
            <wsp:All wsp:Usage="Required">
                <L7p:ComparisonAssertion>
                    <L7p:CaseSensitive booleanValue="false"/>
                    <L7p:Expression1 stringValue="${introspect_response_code}"/>
                    <L7p:MultivaluedComparison multivaluedComparison="ANY"/>
                    <L7p:Operator operatorNull="null"/>
                    <L7p:Predicates predicates="included">
                        <L7p:item dataType="included">
                            <L7p:Type variableDataType="int"/>
                        </L7p:item>
                        <L7p:item regex="included">
                            <L7p:Pattern stringValue="^401|403|404|5[0][0-2]|50[4-9]|5[1-9][0-9]$"/>
                        </L7p:item>
                    </L7p:Predicates>
                </L7p:ComparisonAssertion>
                <L7p:CustomizeErrorResponse>
                    <L7p:AssertionComment assertionComment="included">
                        <L7p:Properties mapValue="included">
                            <L7p:entry>
                                <L7p:key stringValue="LEFT.COMMENT"/>
                                <L7p:value stringValue="400"/>
                            </L7p:entry>
                            <L7p:entry>
                                <L7p:key stringValue="RIGHT.COMMENT"/>
                                <L7p:value stringValue="No token in Authorization header"/>
                            </L7p:entry>
                        </L7p:Properties>
                    </L7p:AssertionComment>
                    <L7p:Content stringValueReference="inline"><![CDATA[{
"error": "bad_gateway",
"error_description": "Bad Gateway"
}]]></L7p:Content>
                    <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
                    <L7p:ExtraHeaders nameValuePairArray="included"/>
                    <L7p:HttpStatus stringValue="502"/>
                </L7p:CustomizeErrorResponse>
                <L7p:RaiseError/>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Curity responds 401,403,404,500-502,504-599"/>
                        </L7p:entry>
                        <L7p:entry>
                            <L7p:key stringValue="RIGHT.COMMENT"/>
                            <L7p:value stringValue="Gateway responds with 502"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
            <wsp:All wsp:Usage="Required">
                <L7p:CustomizeErrorResponse>
                    <L7p:AssertionComment assertionComment="included">
                        <L7p:Properties mapValue="included">
                            <L7p:entry>
                                <L7p:key stringValue="LEFT.COMMENT"/>
                                <L7p:value stringValue="400"/>
                            </L7p:entry>
                            <L7p:entry>
                                <L7p:key stringValue="RIGHT.COMMENT"/>
                                <L7p:value stringValue="No token in Authorization header"/>
                            </L7p:entry>
                        </L7p:Properties>
                    </L7p:AssertionComment>
                    <L7p:Content stringValueReference="inline"><![CDATA[{
"error": "internal_server_error",
"error_description": "Internal Server Error"
}]]></L7p:Content>
                    <L7p:ContentType stringValue="application/json; charset=UTF-8"/>
                    <L7p:ExtraHeaders nameValuePairArray="included"/>
                </L7p:CustomizeErrorResponse>
                <L7p:RaiseError/>
                <L7p:assertionComment>
                    <L7p:Properties mapValue="included">
                        <L7p:entry>
                            <L7p:key stringValue="LEFT.COMMENT"/>
                            <L7p:value stringValue="Any other status code"/>
                        </L7p:entry>
                        <L7p:entry>
                            <L7p:key stringValue="RIGHT.COMMENT"/>
                            <L7p:value stringValue="Gateway responds with 500"/>
                        </L7p:entry>
                    </L7p:Properties>
                </L7p:assertionComment>
            </wsp:All>
        </wsp:OneOrMore>
        <L7p:ExportVariables>
            <L7p:ExportedVars stringArrayValue="included">
                <L7p:item stringValue="oauth.jwt"/>
            </L7p:ExportedVars>
        </L7p:ExportVariables>
    </wsp:All>
</wsp:Policy>

Keep up with our latest articles and how-tos RSS feeds.