The security is paramount and the internet never forgets….
Yeah, that sounds familiar. We’ve been already there with the first instalment, namely:
This time let’s see how to implement SAP Kyma API rules oauth2_introspection access strategy with SAP IAS opaque tokens, its introspection endpoint and the BTP destinations.
1. Rationale behind the use of opaque tokens
They are both compact and opaque (=meta) tokens which may constitute an advantage whenever the size of a payload matters and whenever it is paramount to keep the token information secret.
However, as the opaque tokens need to be further validated and exchanged by a service provider’s introspection endpoint, that makes them not really suitable for offline scenarios.
Q. Are the opaque tokens preferred security media for securing API-driven communications in the enterprise world ?
A1. Seemingly, SAP ABAP solutions, with SAP S/4HANA Private and Public Cloud at the front, make pretty much exclusive use of opaque tokens or otherwise SAML assertions (xml tokens) with the OAuth2 APIs.
And this is for a good reason. The good reason being, in the ABAP world, both authorization and resource servers share the same database, and that makes the opaque tokens an attractive choice for securing SAP S/4HANA OAuth2 APIs.
A2. Otherwise, SPAs, mobile apps are good candidates for having their access secured with the opaque tokens as well.
2. oauth2_introspection access strategy
In order to build an oauth2_introspection strategy one needs to:
- have a destination service fetch a valid opaque token.
- provide a Kyma API rule with a valid opaque token.
Eventually, ory oathkeeper will be calling into the SAP IAS introspection endpoint with the value of an opaque token passed in a header of an API rule.
Good to read:
2.1 Retrieve an opaque token for client credentials flow with OAuth2ClientCredentials destination
As aforementioned, the oauth2 introspection flow requires an opaque bearer access token be retrieved first.
This is done by adding an additional property – tokenService.body.token_format -set to opaque – in the destination definition, as depicted below:
![]() |
Good to know:
- An opaque token is a short-lived bearer access token. The destination service has a built-in load-balancing logic to fetch one or more tokens from the service provider which will be cached until their expiry.
{
"owner": {
"SubaccountId": "<SubaccountId>",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "poster-quovadis_opaque",
"Type": "HTTP",
"URL": "https://httpbin-anywhere.<API rule custom domain>",
"Authentication": "OAuth2ClientCredentials",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"tokenService.body.token_format": "opaque",
"HTML5.DynamicDestination": "true",
"clientId": "<clientId>",
"Description": "poster-quovadis-opaque",
"scope": "openid",
"clientSecret": "<clientSecret>",
"tokenServiceURL": "https://<sap ias tenant>.accounts400.ondemand.com/oauth2/token"
},
"authTokens": [
{
"type": "Bearer",
"value": "NTM2ZGYxMzYtMDU3Yy00ZWNhLTliZDctMTIzYjA2MTg5NGE2bmdtUncyOFI1RUdLa0UxdUhqVGNoaWZ5TC1LR2xwZXdEaXVzczF0NlFxOA",
"http_header": {
"key": "Authorization",
"value": "Bearer NTM2ZGYxMzYtMDU3Yy00ZWNhLTliZDctMTIzYjA2MTg5NGE2bmdtUncyOFI1RUdLa0UxdUhqVGNoaWZ5TC1LR2xwZXdEaXVzczF0NlFxOA"
},
"expires_in": "3600"
}
]
}
2.2 oauth2_introspection access strategy configuration with an API rule
The opaque token needs to be passed in a header when calling an API rule….
Ory oathkeeper will retrieve this opaque bearer token either from the Authorization header (default location) or from a custom header defined as token_from: header: <header_name> in the manifest and will pass it along to the introspection endpoint for validation and exchange.
As already mentioned, an opaque token must have the scopes defined as a string of values and not as an array of values. Failure to comply with this requirement will result in the following error message:
The authentication handler encountered an error audience=application authentication_handler=oauth2_introspection error=map[message:json: cannot unmarshal array into Go struct field AuthenticatorOAuth2IntrospectionResult.scope of type string
This can be easily accomplished by defining all scopes as a single entry scope with multiple values instead of a list of single-valued scopes, as depicted below.
On a side note, the audiences are supported as either a string of values or an array of values.
![]() |
The SAP IAS introspection endpoint needs to be protected against a so-called token scanning with either Basic Authentication, Bearer Access Token or x509 certificate.
Seemingly, the ory oathkeeper supports both the Basic Authentication through theintrospection_request_headers
object as shown below:
apiVersion: gateway.kyma-project.io/v1beta1
kind: APIRule
metadata:
labels:
app.kubernetes.io/name: httpbin-introspect
name: httpbin-introspect
namespace: quovadis
spec:
gateway: quovadis-azure-dns-gateway.azure-dns.svc.cluster.local
host: httpbin-introspect.btp.quovadis-anywhere.com
rules:
- accessStrategies:
- config:
introspection_request_headers:
Authorization: >-
Basic
MTEwODVkYTgtNT****************FbVRzaw==
introspection_url: https://<sap ias tenant>.accounts400.ondemand.com/oauth2/introspect
required_scope:
- openid
- read
- write
token_from:
header: Authorization
handler: oauth2_introspection
methods:
- GET
- POST
- PUT
- DELETE
- PATCH
- HEAD
path: /.*
timeout: 300
service:
name: httpbin
port: 8000
timeout: 300
On a side note, you can use the below destination definition to help you create a Basic Authentication payload for use with the introspection_request_headers
object.
{
"Name": "introspect-me",
"Type": "HTTP",
"URL": "https://sap.com",
"Authentication": "BasicAuthentication",
"ProxyType": "Internet",
"User": "<client_id>",
"Description": "provide base64-encoded introspection endpoint credentials",
"Password": "<client_secret>"
},
"authTokens": [
{
"type": "Basic",
"value": "MTEwODVkYTgtNT****************FbVRzaw==",
"http_header": {
"key": "Authorization",
"value": "Basic MTEwODVkYTgtNT****************FbVRzaw=="
}
}
]
}
Or, alternatively, you can have an APIRule with the pre_authorization
object in lieu of the introspection_request_headers
object, as follows:
apiVersion: gateway.kyma-project.io/v1beta1
kind: APIRule
metadata:
labels:
app.kubernetes.io/name: httpbin-introspect
name: httpbin-introspect
namespace: <namespace>
spec:
gateway: quovadis-azure-dns-gateway.azure-dns.svc.cluster.local
host: httpbin-introspect.<custom domain>
rules:
- accessStrategies:
- config:
introspection_request_headers: {}
introspection_url: https://<sap ias tenant>.accounts400.ondemand.com/oauth2/introspect
pre_authorization:
client_id: <client_id>
client_secret: <client_secret>
enabled: true
token_url: https://<sap ias tenant>.accounts400.ondemand.com/oauth2/token
required_scope:
- openid
- read
- write
target_audience:
- toto01
- toto02
handler: oauth2_introspection
methods:
- GET
- POST
- PUT
- DELETE
- PATCH
- HEAD
path: /.*
service:
name: httpbin
port: 8000
timeout: 300
service:
name: httpbin
port: 8000
timeout: 300
The Bearer Access token protection of the introspection endpoint can be really attractive because you can use a different and confidential SAP IAS client for this purpose.
Last but not least, it looks like the x509 client certificate protection mechanism of the SAP IAS introspection endpoint is not supported by ory oathkeeper.
As a way around you may have to use a proxy to the SAP IAS introspection endpoint with the x.509 client certificate authentication method.
The next blogpost will focus on bringing the user context into the picture. So stay tuned!