Common AM - Apigee Integration Patterns

Written by Marek Detko

Summary

AM acts as an autorization server (AS) and Apigee is a Resource Server (RS).

All tokens are issued by AM. It doesn’t matter which OAuth2 grant was used to issue token. However if you decide to use either client credentials or resource owner password grants, Apigee offers quite easy way of integrating those flows. Example of such integration can be found in Wayne’s blog.

If you decide to use either authorization code or implicit grants, it is easier to issue token directly at external AS and just use the token to protect APIs at RS. If it is required to pass all requests through Apigee, then I suggest to proxy autorization and token endpoint to AS without trying to use any Apigee flows.

Token validated by Apigee

As Apigee doesn’t have any option to check/run things in a background we will have to include logic to retrive and maintain public keys from AM. Keys will be used to validate signature of the token (assuming here that tokens issued by AM are JWT tokens)

Cache

To avoid service callouts each time a request is made we will use a built-in caching capability to store public keys.

To configure cache go to Admin section, then select Environments. From the dropdown select the Environment. Here is how cache has been configured

Key/value map

To avoid using hardcoded values in the flows and to ease deployments to different environments Apigee allows to store variables in the key/value maps.

To configure them for the given environment go to Admin section, then select Environments, then from the dropdown select the environment. Finally select the Key Value Maps tab.

Apigee allows to create multiple named key/value maps and in addition for each of them it allows to encrypt values. In this example we will use non-encrypted key/value map.

To make it easier to recreate it, here are the keys and values used in this chapter:

Retrieve public keys

As this flow might be used in many places it is created as a shared flow in Apigee.

In short this flow tries to retrieve a list of keys from the cache (oauthas). Apigee allows to create mutiple named caches with a different expiration time.

If keys are available all other steps in the flow are skipped.

If keys are not available in the cache then:

  • Variables holding AM information (host, uris) are retrieved from the key/value map and stored in the flow variables (fr.am.base.url, fr.am.jwk.uri)
  • Apigee makes a service callout to AM to get list of public keys
  • Extracting keys from the response and assigning it to the internal variable public.jwks
  • Keys are stored in the oauthas cache
  • and finally made available to as cache.public.jwks

Main flow

Apigee has been configured to validate token before allowing to reach the /offline endpoint. It uses a built-in Verify JWT policy. Here is the flow

  • first Apigee extracts OAuth2 token from the Authorization header and stores it in the local variable clientrequest.oauthtoken
  • then it ensures that public keys used to verify token signature are available by invoking a shared flow
  • validates token by verifying signature and the following claims: iss, aud, scope (checks if read is available on the list)
  • finally if token is validated Apigee (in this example) generaters an OK message

Sample requests and response

successfull validation

  • access token
{
  "sub": "demo",
  "cts": "OAUTH2_STATELESS_GRANT",
  "auth_level": 0,
  "auditTrackingId": "88661de3-3f3e-4209-ba8c-c4e7f0ee5716-12358209",
  "iss": "https://marek-am.forgerocklabs.net:443/openam/oauth2",
  "tokenName": "access_token",
  "token_type": "Bearer",
  "authGrantId": "Fqa_BXz-TpofzEVTINQQnHWqGwo",
  "aud": "client",
  "nbf": 1552894274,
  "grant_type": "authorization_code",
  "scope": [
    "read"
  ],
  "auth_time": 1552894262,
  "realm": "/",
  "exp": 1552897874,
  "iat": 1552894274,
  "expires_in": 3600,
  "jti": "1BgbS33O4k5_LPAwKXMU_bWAqEc"
}
  • request
curl --request GET \
  --url https://marekdetko-eval-test.apigee.net/test/offline \
  --header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJ6aXAiOiJOT05FIiwia2lkIjoid1UzaWZJSWFMT1VBUmVSQi9GRzZlTTFQMVFNPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJkZW1vIiwiY3RzIjoiT0FVVEgyX1NUQVRFTEVTU19HUkFOVCIsImF1dGhfbGV2ZWwiOjAsImF1ZGl0VHJhY2tpbmdJZCI6Ijg4NjYxZGUzLTNmM2UtNDIwOS1iYThjLWM0ZTdmMGVlNTcxNi0xMjM1ODIwOSIsImlzcyI6Imh0dHBzOi8vbWFyZWstYW0uZm9yZ2Vyb2NrbGFicy5uZXQ6NDQzL29wZW5hbS9vYXV0aDIiLCJ0b2tlbk5hbWUiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiYXV0aEdyYW50SWQiOiJGcWFfQlh6LVRwb2Z6RVZUSU5RUW5IV3FHd28iLCJhdWQiOiJjbGllbnQiLCJuYmYiOjE1NTI4OTQyNzQsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJzY29wZSI6WyJyZWFkIl0sImF1dGhfdGltZSI6MTU1Mjg5NDI2MiwicmVhbG0iOiIvIiwiZXhwIjoxNTUyODk3ODc0LCJpYXQiOjE1NTI4OTQyNzQsImV4cGlyZXNfaW4iOjM2MDAsImp0aSI6IjFCZ2JTMzNPNGs1X0xQQXdLWE1VX2JXQXFFYyJ9.vKZF4fK8QoLjSaDD-f83oD5ouDZvTgVQ2peAi2xQLM-A9roPDS-ba6qUT9sK1csDq4rLgr_9oguq56yfmv_stVsnloJAsQKV2GpbZKeookjxxzWxPbcw0tOIBbwEh4Osrod0YTXy13NbOf_erHjpPKKw3OdJcdkx4ZPLMEIIicmxueM5Xi8bVmtnk891__KO1PYxvP0xvkRds-9SguM8yxVfMCCpoRnZeNZLENHY3Ozb3E7kcKqqMkD-osG3GxB1Qo4NpRyuJOsWvdvP6Xq2BslpFIYG8Wb7YmLKCvaLmB5KHja4TT1d4Ep7Pe3ronEG4qEMT5kmIDE25fCgoT9mUQ'
  • response
{
    "message": "OK"
}

Wrong/Missing scope

  • access token (note scope value: write)
{
  "sub": "demo",
  "cts": "OAUTH2_STATELESS_GRANT",
  "auth_level": 0,
  "auditTrackingId": "88661de3-3f3e-4209-ba8c-c4e7f0ee5716-12360983",
  "iss": "https://marek-am.forgerocklabs.net:443/openam/oauth2",
  "tokenName": "access_token",
  "token_type": "Bearer",
  "authGrantId": "zzW8kfBGPvEY7zXPZPqKOEWJtW4",
  "aud": "client",
  "nbf": 1552894813,
  "grant_type": "authorization_code",
  "scope": [
    "write"
  ],
  "auth_time": 1552894262,
  "realm": "/",
  "exp": 1552898413,
  "iat": 1552894813,
  "expires_in": 3600,
  "jti": "jKjQa_K7Yo6GveQUAiX-zF4exKw"
}
  • request
curl --request GET \
  --url https://marekdetko-eval-test.apigee.net/test/offline \
  --header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJ6aXAiOiJOT05FIiwia2lkIjoid1UzaWZJSWFMT1VBUmVSQi9GRzZlTTFQMVFNPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJkZW1vIiwiY3RzIjoiT0FVVEgyX1NUQVRFTEVTU19HUkFOVCIsImF1dGhfbGV2ZWwiOjAsImF1ZGl0VHJhY2tpbmdJZCI6Ijg4NjYxZGUzLTNmM2UtNDIwOS1iYThjLWM0ZTdmMGVlNTcxNi0xMjM2MDk4MyIsImlzcyI6Imh0dHBzOi8vbWFyZWstYW0uZm9yZ2Vyb2NrbGFicy5uZXQ6NDQzL29wZW5hbS9vYXV0aDIiLCJ0b2tlbk5hbWUiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiYXV0aEdyYW50SWQiOiJ6elc4a2ZCR1B2RVk3elhQWlBxS09FV0p0VzQiLCJhdWQiOiJjbGllbnQiLCJuYmYiOjE1NTI4OTQ4MTMsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJzY29wZSI6WyJ3cml0ZSJdLCJhdXRoX3RpbWUiOjE1NTI4OTQyNjIsInJlYWxtIjoiLyIsImV4cCI6MTU1Mjg5ODQxMywiaWF0IjoxNTUyODk0ODEzLCJleHBpcmVzX2luIjozNjAwLCJqdGkiOiJqS2pRYV9LN1lvNkd2ZVFVQWlYLXpGNGV4S3cifQ.ALjnkYIM9dHG-Q0S9WMvJQRJen4H-_e_Jt2qNhJ_40OD4AgIEYLxdeMUJJ8RoEipHDW7q0DBW-5qbTspKXTO33NVv6gvUHCJKk6dKT2DTJAvmp9D8AG1hU7Ssey8xt6ZAfaMoEVaoMTow1TgUbJwOQpR6ZTyaFGJv03l9EwPaYncAHWXHG8NOsDIb1qPp8S_1qLOzsZkNxhuF7PcGFW3SWjZ40AYXoWNnBx3fRvGs00iwmItc9evbVzUbkt0OmKhVRO5eLeOt36VVzifJXMhG8AUXTsMxknbxiWEaSD3xT82mpZ_2yK7FV7Hbm1V9QU4LW0Zu8beeFVc3PQm5zDjjQ'
  • response

code: 401

{
    "fault": {
        "faultstring": "Invalid Claim: policy(JWT-Verify-RS256)",
        "detail": {
            "errorcode": "steps.jwt.InvalidClaim"
        }
    }
}

Token introspected by AM

As in the previous example we will use a shared flow (Validate-Access-Token), which will be invoked everytime we try to access /online endpoint. This time we will use a conditional pre flow to invoke token validation to all requests trying to reach our ptoected endpoint(s). Apigee (RS) introspects the token against AM (AS) OAuth2 introspection endpoint and uses response to validate if access should be granted.

As the whole flow relies on the reponse from the AM’s introspection endpoint we don’t need to use Client-Based Access Tokens!

Key/Value Map Configuration

Introspection endpoint is protected with a basic authentication where OAuth2 client needs to present its credentials. The following properties are added to may/value map used in the previous example and additional encrypted key/value map is introduced to hold client_id and client_secret.

  • fr-am key/value map
Key Value
introspect_ep openam/oauth2/introspect
  • fr-pwd an encrypted key/value map
Key Value
client_basic_auth Y2xpZW50OnBhc3N3b3Jk

Value of the client_basic_auth is a bse64 encoded string: client_id:client_secret

Validate Access Token shared flow

The shared flow in a few steps checks whether token is still valid, has not expired or has not been revoked.

  • first OAuth2 access token is extracted from the request’s Authorization header and stored in the flow variable fr.am.oauthtoken
  • then client basic credentials (client_basic_auth) are retrieved from the fr-pwd encrypted key/value map
  • then AM host (am_base_url) and introspection endpoint (introspect_ep) are retrieved from the fr-am key/value map
  • once all data is read and extracted Apigee makes a service callout to AM to check the token
  • last thing in this flow is to extract several interesting properties from the reponse, like: active, scope, sub, issuer, authLevel

Pre Flow

Requests in Apigee can invoke some logic before request will be handled. In our case we are going to invoke Validate-Access-Token shared flow. Note that the shared flow is only invoked when condition evaluates to true. In our case only if the requested URI is /online

<PreFlow name="PreFlow">
    <Request>
        <Step>
            <Name>Introspect-Token</Name>
            <Condition>(proxy.pathsuffix MatchesPath "/online")</Condition>
        </Step>
    </Request>
    <Response/>
</PreFlow>

Main Flow

The main flow for the /online endpoint is executed after a PreFlow, meaning that all variable set by the PreFlow are available to the main one (here all variable extracted from the introspection response)

<Flow name="online">
    <Description/>
    <Request>
        <Step>
            <Name>Raise-Fault-Forbidden</Name>
            <Condition> (fr.am.token.active is false) OR !(fr.am.token.scope Matches "read")</Condition>
        </Step>
        <Step>
            <Name>Message-OK</Name>
        </Step>
    </Request>
    <Response/>
    <Condition>(proxy.pathsuffix MatchesPath "/online")</Condition>
</Flow>

Raise-Fault-Forbidden policy generates 403 response code with the sample message. It is only invoked when condition evaluates to true. In our case we check if token is not active or if read scope is missing.

If condition evaluates to false, Apigee forwards request to the backend API. Here it just generates a static JSON response with ‘OK’ message.

Samples

Successfull introspection

  • access token
{
  "sub": "demo",
  "cts": "OAUTH2_STATELESS_GRANT",
  "auth_level": 0,
  "auditTrackingId": "88661de3-3f3e-4209-ba8c-c4e7f0ee5716-12398450",
  "iss": "https://marek-am.forgerocklabs.net:443/openam/oauth2",
  "tokenName": "access_token",
  "token_type": "Bearer",
  "authGrantId": "yazSzhcORu8lGHQv8iIZ8GvZggk",
  "aud": "client",
  "nbf": 1552902132,
  "grant_type": "authorization_code",
  "scope": [
    "read"
  ],
  "auth_time": 1552902111,
  "realm": "/",
  "exp": 1552905732,
  "iat": 1552902132,
  "expires_in": 3600,
  "jti": "3zo3oEeTiuw1pfwxnxqd9PkYvY0"
}
  • request
curl --request GET \
  --url https://marekdetko-eval-test.apigee.net/test/online \
  --header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJ6aXAiOiJOT05FIiwia2lkIjoid1UzaWZJSWFMT1VBUmVSQi9GRzZlTTFQMVFNPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJkZW1vIiwiY3RzIjoiT0FVVEgyX1NUQVRFTEVTU19HUkFOVCIsImF1dGhfbGV2ZWwiOjAsImF1ZGl0VHJhY2tpbmdJZCI6Ijg4NjYxZGUzLTNmM2UtNDIwOS1iYThjLWM0ZTdmMGVlNTcxNi0xMjM5ODQ1MCIsImlzcyI6Imh0dHBzOi8vbWFyZWstYW0uZm9yZ2Vyb2NrbGFicy5uZXQ6NDQzL29wZW5hbS9vYXV0aDIiLCJ0b2tlbk5hbWUiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiYXV0aEdyYW50SWQiOiJ5YXpTemhjT1J1OGxHSFF2OGlJWjhHdlpnZ2siLCJhdWQiOiJjbGllbnQiLCJuYmYiOjE1NTI5MDIxMzIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJzY29wZSI6WyJyZWFkIl0sImF1dGhfdGltZSI6MTU1MjkwMjExMSwicmVhbG0iOiIvIiwiZXhwIjoxNTUyOTA1NzMyLCJpYXQiOjE1NTI5MDIxMzIsImV4cGlyZXNfaW4iOjM2MDAsImp0aSI6IjN6bzNvRWVUaXV3MXBmd3hueHFkOVBrWXZZMCJ9.cKymECU_A7ZY_3nntXJLTcTMW5VNvOfBTKbs20NlIHM1Jgs4Ikp1EgdwTtiU5kVWkiDO2cdSf_AF5cSsS1uUJnwo6mOmV1i6EYeRuCGN1STMHcq0GfjMPGHLJn99rT_tI1toHQzEW32vQ5e7f3VpPA6eAou7DQrZ3u_Q7h96VRipbAzpRQOomsTtlUGqaqeRqE-vPKlOfVFsH08hA46-05-Ji6JCOvmNfWE25YtGb6BMUFd4CZCZnUUd3j1wnXs-ilAm5UV6RogwfLm5xs80D0t2Se21UnQWQut_SP1UOUvn7v8tiO9tmqU4F7FvHR8LYIsuFLi3qv9TOSjCJMFnmw'
  • introscpection response

To check introspection response you need to enable tracing in Apigee

{
    "active": true,
    "scope": "read",
    "client_id": "client",
    "user_id": "demo",
    "token_type": "Bearer",
    "exp": 1552905732,
    "sub": "demo",
    "iss": "https://marek-am.forgerocklabs.net:443/openam/oauth2",
    "auth_level": 0
}
  • response

Confdition in the Raise-Fault-Forbidden step evaluates to false hence the response is:

{
    "message": "OK"
}

Introspection, wrong scope

  • access token (with write scope)
{
  "sub": "demo",
  "cts": "OAUTH2_STATELESS_GRANT",
  "auth_level": 0,
  "auditTrackingId": "88661de3-3f3e-4209-ba8c-c4e7f0ee5716-12400148",
  "iss": "https://marek-am.forgerocklabs.net:443/openam/oauth2",
  "tokenName": "access_token",
  "token_type": "Bearer",
  "authGrantId": "Nw8cWYgTAo73o643n46JDdim6LU",
  "aud": "client",
  "nbf": 1552902452,
  "grant_type": "authorization_code",
  "scope": [
    "write"
  ],
  "auth_time": 1552902111,
  "realm": "/",
  "exp": 1552906052,
  "iat": 1552902452,
  "expires_in": 3600,
  "jti": "Tc2Udrkm-YfB5nWr9MwLFt_hOz0"
}
  • request
curl --request GET \
  --url https://marekdetko-eval-test.apigee.net/test/online \
  --header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJ6aXAiOiJOT05FIiwia2lkIjoid1UzaWZJSWFMT1VBUmVSQi9GRzZlTTFQMVFNPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJkZW1vIiwiY3RzIjoiT0FVVEgyX1NUQVRFTEVTU19HUkFOVCIsImF1dGhfbGV2ZWwiOjAsImF1ZGl0VHJhY2tpbmdJZCI6Ijg4NjYxZGUzLTNmM2UtNDIwOS1iYThjLWM0ZTdmMGVlNTcxNi0xMjQwMDE0OCIsImlzcyI6Imh0dHBzOi8vbWFyZWstYW0uZm9yZ2Vyb2NrbGFicy5uZXQ6NDQzL29wZW5hbS9vYXV0aDIiLCJ0b2tlbk5hbWUiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiYXV0aEdyYW50SWQiOiJOdzhjV1lnVEFvNzNvNjQzbjQ2SkRkaW02TFUiLCJhdWQiOiJjbGllbnQiLCJuYmYiOjE1NTI5MDI0NTIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJzY29wZSI6WyJ3cml0ZSJdLCJhdXRoX3RpbWUiOjE1NTI5MDIxMTEsInJlYWxtIjoiLyIsImV4cCI6MTU1MjkwNjA1MiwiaWF0IjoxNTUyOTAyNDUyLCJleHBpcmVzX2luIjozNjAwLCJqdGkiOiJUYzJVZHJrbS1ZZkI1bldyOU13TEZ0X2hPejAifQ.XlTQTkAaS_9Pp8o7-8k-oViAacTGt8pVHmIxtlH-VQDpC-mgo4X_vZIeDXT9AdXoceHHF3B3bk7aiC1xOS99x8tcTahf3REL0r4pmfe8W1oHKdmpNLXyor6x7fHD0PVKCP233wh6q1ftlzGRmn69muNKK_GUFHLPn8vZUaPbCPSTl3rzHqpQ_kmrTX7w5ySLaresYHeq8zs2C26u_o1vQWNQUM0Lf2qlhkJyvkkNcCaTm6Jbfaq75DG64mqelzQV9j6HJfPzp82LnzP5YSAR9Av6l89efozU-4vVRLqdA2Yfs2Dh_dkY0Wki6HH4THuFfc1VlCkGJ5bg6-xc8CtpZQ'
  • introspection response
{
    "active": true,
    "scope": "write",
    "client_id": "client",
    "user_id": "demo",
    "token_type": "Bearer",
    "exp": 1552906052,
    "sub": "demo",
    "iss": "https://marek-am.forgerocklabs.net:443/openam/oauth2",
    "auth_level": 0
}
  • response

This time condition in the Raise-Fault-Forbidden step evaluates to true as read scope is missing in the list of scopes

{
    "status": 403,
    "reason": "Forbidden"
}

Authorization

In this use case we will be using JWT access token as a subject in the request to the AM’s autorization endpoint. Apigee will use response to either grant or deny access to the requested resources. Token validation is done by AM when it evaluates subject.

Using AM’s authorization engine we not only validate token but at the same time we are able to use any out-of-the-box or custom policy conditions to verify if contextual data has change since authentication or last authorization request.

AM has to be preconfigured. To simplify demonstration a new Resource Type has been introduced that matches any resource and defines only two actions GET and POST.

AM configuration

  • Resource Type - Patterns

  • Resource Type - Actions

  • Policy Definition

  • Agent configuration

You can use any agent to send authorization requests, including the oauth2 clients! Here however I have configured a web agent to differentiate it from OAuth2 client. Only agent name and passwords are required.

Cache entries

  • fr-am key/value map

This time we have to introduce to additional entries in our cache. First one pointing at the authotrization endpoint, and the second one at authentication endpoint

  • fr-pwd encrypted key/value map

Apigee Pre Flow

This time we will put the whole authorization logic into the Pre Flow using a shared flow Authorize-Request.

Again as Apigee is not capable of runing background tasks (at least using out-of-the-box features) we will have to take care of the agent’s token. This flow assumes that cache entry holding agent token expires before token will be invalidated (to simplify the flow)

  • in the first step we extract an OAuth2 access token from the Authorization header and we store it in the flow variable fr.am.oauthtoken
  • then we read basic AM information from the fr-am key/value map
  • next we check if the agent token is available in the cache (here we use the same cache where we stored public keys, I would suggest to use a different cache with properly set expiration time)
  • if agent token is available we skip to the step where the request to the AM’s authortization engine is crafted

Skipping is done using condition:

    <Condition>fr.am.agent.token is null</Condition>

The request is built in the following way:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout async="false" continueOnError="false" enabled="true" name="Authorize-Request">
    <DisplayName>Authorize Request</DisplayName>
    <Properties/>
    <Request clearPayload="true" variable="myRequest">
        <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
        <Set>
            <Headers>
                <Header name="accept-api-version">resource=2.1, protocol=1.0</Header>
                <Header name="content-type">application/json</Header>
                <Header name="Cookie">iPlanetDirectoryPro={fr.am.agent.token}</Header>
            </Headers>
            <QueryParams>
                <QueryParam name="_action">evaluate</QueryParam>
            </QueryParams>
            <Verb>POST</Verb>
            <Payload contentType="application/json">
                {
                    "resources": [ "{message.uri}" ],
                    "application": "apigee",
                    "subject": {
                        "jwt": "{fr.am.oauthtoken}"
                    },
                    "environment": {
                    }
                }
            </Payload>
        </Set>
    </Request>
    <Response>authzResponse</Response>
    <HTTPTargetConnection>
        <Properties/>
        <URL>https://{fr.am.base.url}/{fr.am.authorize}</URL>
    </HTTPTargetConnection>
</ServiceCallout>
  • then we extract the decision for a given action from the response and store it in the flow variable fr.am.authz.decision
  • then we send the ‘Authorization Forbidden’ message back if access to the resource was denied
<Step>
    <Condition>(fr.am.authz.decision = false) or (fr.am.authz.decision is null)</Condition>
    <Name>Raise-Forbidden</Name>
</Step>

In case agent token is not available in the cache, we authenticate agent and we store token in the cache. Following are the steps to get agent token:

  • Read agent name and agent password from th fr-pwd encrypted key/value map
  • Send authentication request to AM
<ServiceCallout async="false" continueOnError="false" enabled="true" name="Create-Agent-Token">
    <DisplayName>Create Agent Token</DisplayName>
    <Properties/>
    <Request clearPayload="true" variable="myRequest">
        <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
        <Set>
            <Headers>
                <Header name="accept-api-version">protocol=1.0</Header>
                <Header name="content-type">application/json</Header>
                <Header name="x-openam-password">{private.fr.am.agent.pwd}</Header>
                <Header name="x-openam-username">{private.fr.am.agent.name}</Header>
            </Headers>
            <Verb>POST</Verb>
        </Set>
    </Request>
    <Response>agentResponse</Response>
    <HTTPTargetConnection>
        <Properties/>
        <URL>https://{fr.am.base.url}/{fr.am.authenticate}</URL>
    </HTTPTargetConnection>
</ServiceCallout>
  • extract token from the response
  • store token in the cache
  • copy value of the token to the fr.am.agent.token flow variable

Authorization PreFlow

Authorization Main Flow

Authorization samples

Allowed GET request

  • request
curl --request GET \
  --url https://marekdetko-eval-test.apigee.net/test/authz \
  --header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJ6aXAiOiJOT05FIiwia2lkIjoid1UzaWZJSWFMT1VBUmVSQi9GRzZlTTFQMVFNPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJkZW1vIiwiY3RzIjoiT0FVVEgyX1NUQVRFTEVTU19HUkFOVCIsImF1dGhfbGV2ZWwiOjAsImF1ZGl0VHJhY2tpbmdJZCI6Ijg4NjYxZGUzLTNmM2UtNDIwOS1iYThjLWM0ZTdmMGVlNTcxNi0xMjQyNTU1OSIsImlzcyI6Imh0dHBzOi8vbWFyZWstYW0uZm9yZ2Vyb2NrbGFicy5uZXQ6NDQzL29wZW5hbS9vYXV0aDIiLCJ0b2tlbk5hbWUiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiYXV0aEdyYW50SWQiOiJ2SEZuamQ5OG4wTzdyeWg5UWlzS0xhVmJOanMiLCJhdWQiOiJjbGllbnQiLCJuYmYiOjE1NTI5MDcwMTgsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJzY29wZSI6WyJyZWFkIl0sImF1dGhfdGltZSI6MTU1MjkwNzAwNCwicmVhbG0iOiIvIiwiZXhwIjoxNTUyOTEwNjE4LCJpYXQiOjE1NTI5MDcwMTgsImV4cGlyZXNfaW4iOjM2MDAsImp0aSI6IjBFMFNjVktaWkI4b3Fqd1BVRUJuV3lNWS1McyJ9.l4LFmH69od95cSZwm_BGvWTAQqB1j1euXXwY7VBflppaSjGVPz5o7Fqs6AcrgULODyGjQ_QDXR4sratPl6li7DXpmD5cXNARYX_Btpxt-9eY8kgswGZktkOw9PBw-v_52byDljfmjZnOMVsUFEkYN8fL3FSkZ2jQIPpxs_XGJ-tEWM0QL9pEZnOBpIi8NHxjerp4UA1u26dM8gOZNkgUGt5pxxwQkHI-INQYKzQuU3SjbGM3vmbOGgSYEA9QkBwrztXchsO5-KJbm8t6kLaLjQTXDVYvWSRS9CkMbK3zu41-DdXTxoCM2G69H8XDPcnPlYA0398elMtkWSSkuwDDkA'
  • policy engine response

to check response one needs to enable tracing in Apigee

[
    {
        "advices": {},
        "ttl": 9223372036854775807,
        "resource": "/test/authz",
        "actions": {
            "POST": true,
            "GET": true
        },
        "attributes": {}
    }
]
  • response

As condition in the shared flow evaluates to false, a forbidden message is not sent

Condition

<Step>
    <Condition>(fr.am.authz.decision = false) or (fr.am.authz.decision is null)</Condition>
    <Name>Raise-Forbidden</Name>
</Step>

Response

{
    "message": "OK"
}

Forbidden GET request

Modify policy definitoion to deny GET action:

  • request

Send exactly the same request as in the previous example above

  • policy engine response
[
    {
        "advices": {},
        "ttl": 9223372036854775807,
        "resource": "/test/authz",
        "actions": {
            "POST": true,
            "GET": false
        },
        "attributes": {}
    }
]
  • response

This time condition evaluates to true and the forbidden message is returned with the HTTP code 403:

Condition

<Step>
    <Condition>(fr.am.authz.decision = false) or (fr.am.authz.decision is null)</Condition>
    <Name>Raise-Forbidden</Name>
</Step>

Response

{
    "code": 403,
    "message": "Forbidden"
}