Hypermedia Authentication API

Introduction

This document describes the Hypermedia Authentication API (HAAPI) that allows OAuth clients to request tokens from the Curity Identity Server in an API-driven way, i.e., without requiring a browser to perform the user authentication and consent interactions.

Curity Identity Server supports a wide range of authentication methods, with highly dynamic flows that depend on previous user actions as well on contextual information. Due to this characteristic, HAAPI adopts a hypermedia-driven style, where the client application is guided through the flow based on hypermedia controls such as forms and links, present in the HAAPI responses. The client application should use these responses and controls to drive the UI presented to the user and obtain the information required to produce the next request. For this matter, HAAPI uses a JSON-based custom media type with the application/vnd.auth+json media type identifier. The client application must always send an Accept header including this media type identifier, to inform Curity Identity Server that the request is an HAAPI request. This is required because HAAPI shares most of the URIs that already exist for the traditional HTML-based interactions.

Tip

For a broader overview of the API, checkout the recorded Webinar and the getting started articles in the resource library available on our Web site.

Access control

All requests to HAAPI require a HAAPI access token. In order to obtain it, a client application must perform an OAuth 2.0 token request to the token endpoint, using the client credentials grant.

The HAAPI access token is a DPoP-bound token and not a bearer token. Due to this, the token request also needs a DPoP header with a proof token (see OAuth 2.0 Demonstration of Proof-of-Possession draft specification which may change as it is finalized.)

Tip

For an introduction to DPoP, checkout the Demonstration of Proof-of-Possession overview in the resource library available on our Web site.

The following example request illustrates the HAAPI access token request, using a client secret as the client authentication method:

POST /dev/oauth/token HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
DPoP: eyJqd2siOnsiYWxnIjoiUFMyNTYiLCJlIjoiQVFBQiIsImV4dCI6dHJ1ZSwia2V5X29wcyI6WyJ2ZXJpZnkiXSwia3R5IjoiUlNBIiwibiI6InhYRTB3Um55MVUtbmNSZnd4RUt4WlhJMzJmOTR5NDRlSGpTUnBwSjFmdDFwUEtwanZydlIwWXc5dGo2Zm9GMVJJeDJKdVJmamFLQURNSlpPTUd3Mks2eERpUkFXMzh5Y19faldLMUMxMFY2X3NxZkNSeU5BRFVtNUlrTzcxOXNYRW5iZTU1dVd6aTlCX3VyZ0tZQ1VpcTI1MXdRRC1UbXdRbUpfWEpabmhDdVlmbm4tRzcxWmJhNFMxdGJPS1F5dzlmOHVtRFFOQXVwTlptUU1oVW9xbTcxbm1qWkVGSWtvWlNtaWRtdHctd0pEZVN4Z3AtU2pVTXFPYk1XT2IwOWNLUF9kUmZyTlhaYi12UjloNVBUUm92aC1nX1lOODl4SU5rUld6cld0akxLdGtVLS02R2xkXzhoUW1uajlMVDlRMFFJdmFtVWI0VFlGVFNXcWNvZkFBUSJ9LCJhbGciOiJQUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiJjZjdkNmZmYy1iYWY5LTQ5NTgtOWViYy1jYmY1NTQ4OGNlZTciLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0My9kZXYvb2F1dGgvdG9rZW4iLCJpYXQiOjE1OTI1ODE0NDN9.vquk9ES8KDU-kY2dd_t5XQM3NfhOiXVbB_qr8iNzdFhHf7C0EMa4-TVs6LlYvVmfujWeoyLkcjqNi2qXg3CY9uZSqdS3qcdjKjt6antieZLVrGNNYkBFtikCzz2nNWjh9JoAtTgo7E3Hs20FhFtAMLWMc8XL2T1ADDkHwhgDKLTKPA8wbLwInegaICqtcEojFv9LIAapSAwpGyU4-CcoHlihJc6u1tb6rta7jXeLvxq_e8yo3ZH9AYYOT0dh_cf6WEEhTv4IqcA5kyIUWuoQGwJsicl9t2v5vQclVglyQRvEQQIO6YR5yXJbKczqqb5CtZvXRHEGAyO0iMxfuLC6vQ

client_id=client-one&client_secret=client-secret&scope=urn:se:curity:scopes:haapi&grant_type=client_credentials

Notice:

  • The request DPoP header, containing the DPoP proof token.
  • The request payload with grant_type set to client_credentials and using the client_secret field.
  • The scope set to urn:se:curity:scopes:haapi, signalling that the requested token is for HAAPI.

In order for the token to be issued, the client must also have the haapi capability enabled in the server configuration.

The HAAPI access token must be used on all HAAPI requests, by adding it to the Authorization header and using the DPoP scheme, along side with a per-request DPoP proof token present in the DPoP header, as illustrated in the following request:

GET /dev/oauth/authorize?client_id=client-one&response_type=code&...
Host: example.com
Accept: application/vnd.auth+json
Authorization: DPoP ey...Yw
DPoP: eyJqd2siOnsiYWxnIjoiUFMyNTYiLCJlIjoiQVFBQiIsImV4dCI6dHJ1ZSwia2V5X29wcyI6WyJ2ZXJpZnkiXSwia3R5IjoiUlNBIiwibiI6InhYRTB3Um55MVUtbmNSZnd4RUt4WlhJMzJmOTR5NDRlSGpTUnBwSjFmdDFwUEtwanZydlIwWXc5dGo2Zm9GMVJJeDJKdVJmamFLQURNSlpPTUd3Mks2eERpUkFXMzh5Y19faldLMUMxMFY2X3NxZkNSeU5BRFVtNUlrTzcxOXNYRW5iZTU1dVd6aTlCX3VyZ0tZQ1VpcTI1MXdRRC1UbXdRbUpfWEpabmhDdVlmbm4tRzcxWmJhNFMxdGJPS1F5dzlmOHVtRFFOQXVwTlptUU1oVW9xbTcxbm1qWkVGSWtvWlNtaWRtdHctd0pEZVN4Z3AtU2pVTXFPYk1XT2IwOWNLUF9kUmZyTlhaYi12UjloNVBUUm92aC1nX1lOODl4SU5rUld6cld0akxLdGtVLS02R2xkXzhoUW1uajlMVDlRMFFJdmFtVWI0VFlGVFNXcWNvZkFBUSJ9LCJhbGciOiJQUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiIxZWE1ODY2OS01OTIwLTRhMmMtOGU0MS05NTMyMDAyZGQxODAiLCJodG0iOiJHRVQiLCJodHUiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2Rldi9vYXV0aC9hdXRob3JpemUiLCJpYXQiOjE1OTI1ODE0NDN9.G0MTv82emHMNE2VLwLj57uBcKMLdIh1IFzQaB_qRgWwzOQL6nmfvUNQQzHKkPUjkP4B8M3FK7PH9w7hyJOBCiQqNnFGBCUbsV67zCGpGG7gO1u74o6-_yfiydQOFrZJZaib0vD-P9471KhLq0NikUT9-rUghDxt1o2OLDDbRKM3am_iLCeiNoeB9uMR6iimyXKwphS1RaYDHO7k_82-8G1soGm-EF40St7_kMZbSUR4T_XKjp0hZKx-U5FuKnGzd_4SUpT7kTrFqMv6COjg9GbGqyNS4glfYawPywHyZcAHwPSeycu6G48PHaX_N1v-mVgDFo80ITSCBgQycm-sfBA

Client attestation

Tokens requests can also use an attestation-based authentication method, in alternative to client secrets. This method is adequate for use by public clients, such as native clients or browser-based clients, which cannot hold static client secrets. The attestation process uses client-side platform specific functionalities to verify the client application identity. Due to that, the recommended way is for client applications to use a Curity provided client SDK (Software Development Kit) to perform attestation and manage API access tokens.

Attestation settings are defined in the client configuration section and vary with the client’s platform, as described in the following sections. Some more advanced configurations can be made via client attestation policies, namely defining the TTL of HAAPI access tokens (haapi-access-token-ttl setting).

Android client attestation configuration

A statically configured OAuth client can use Android based attestation via the attestation client configuration. The following configuration excerpt shows how to use this configuration element.

<authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
    <client-store>
        <config-backed>
            <client>
                <id>the-client-id</id>
                <attestation>
                  <android>
                      <package-name>com.example.client.app</package-name>
                      <signature-digest>Z2DK...9oKQ=</signature-digest>
                  </android>
                </attestation>
            </client>
        </config-backed>
    </client-store>
</authorization-server>

The package-name property must have the client application Android package name.

The signature-digest property must have the SHA-256 hash of the signing certificate, encoded using Base64. To obtain this value, run the signingReport gradle task on the Android Studio project, look for the SHA-256 line and convert its value to Base64.

Listing 283 Obtain the signing certificate hash and convert it to Base64.
$ ./gradlew signingReport
...
SHA-256: 67:60:CA:11:93:B6:5D:61:56:42:70:29:A1:10:B3:86:A8:48:C7:33:83:7B:B0:54:B0:0A:E3:E1:4A:7D:A0:A4
Valid until: Wednesday, July 5, 2045
$ echo "67:60:CA:11:93:B6:5D:61:56:42:70:29:A1:10:B3:86:A8:48:C7:33:83:7B:B0:54:B0:0A:E3:E1:4A:7D:A0:A4" | xxd -r -p | base64
Z2DKEZO2XWFWQnApoRCzhqhIxzODe7BUsArj4Up9oKQ=

To support client applications running in Android emulators, namely during the development phase, a custom Android attestation policy needs to be defined in facilities

<config xmlns="http://tail-f.com/ns/config/1.0">
  <facilities xmlns="https://curity.se/ns/conf/base">
      <client-attestation>
          <android-policy xmlns="https://curity.se/ns/conf/client-attestation">
              <id>the-emulator-policy-name</id>
              <verify-boot-state>false</verify-boot-state>
              <minimum-security-level>software</minimum-security-level>
              <override-certificate-chain-validation>
                  <do-not-validate-certificate-chain/>
              </override-certificate-chain-validation>
          </android-policy>
      </client-attestation>
  </facilities>
</config>

Then, the client attestation configuration needs to explicitly refer this policy

<authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
    <client-store>
        <config-backed>
            <client>
                <id>the-client-id</id>
                <attestation>
                  <android>
                      <package-name>com.example.client.app</package-name>
                      <signature-digest>Z2DK...9oKQ=</signature-digest>
                      <android-policy>the-emulator-policy-name</android-policy>
                  </android>
                </attestation>
            </client>
        </config-backed>
    </client-store>
</authorization-server>

Warning

Notice that custom policies should only be used for development purposes or to support special devices, since they may reduce the Android attestation security.

On the client side, the attestation protocol is performed automatically by the Curity provided SDK, including the use of native Android attestation APIs.

iOS client attestation configuration

A statically configured iOS client can use iOS attestation (i.e. Apple Device Check services) by configuring the iOS attestation settings of the client. These settings include the app-id of the iOS client app, that is made up of the Apple-assigned teamId together with the app’s bundleId, separated by a .. For example, when the teamId is 01234abcde and the app’s bundleId is app.curity.io, then the AppId to configure is 01234abcde.app.curity.io. This value is needed to identify the attested client app.

Listing 284 example configuration snippet for an iOS OAuth client
<authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
    <client-store>
        <config-backed>
            <client>
              <id>ios-oauth-client</id>
              ...
              <capabilities>
                ...
                <haapi/>
              </capabilities>
              <attestation>
                <ios>
                  <app-id>01234abcde.app.curity.io</app-id>
                <ios>
              </attestation>
            </client>

Further configuration of iOS related attestation is done by a shared iOS Attestation Policy in Curity’s facilities. Here you can set properties to:

  • Indicate whether attestation is done for production or non-production apps through the mode setting,
  • Configure how the attested credentials are cryptographically validated. By default this uses Apple’s CA to verify attested credential data. It can be overridden such that no verification of the attested credential data is done, or to use an alternative CA certificate.

When a client does not configure a particular iOS Attestation Policy, a default policy is applied that is comparable to a policy with a production-mode and uses Apple’s CA to verify attested credential data.

Listing 285 example configuration snippet for a non-validating iOS Attestation policy
<config xmlns="http://tail-f.com/ns/config/1.0">
  <facilities xmlns="https://curity.se/ns/conf/base">
      <client-attestation>
          <ios-policy xmlns="https://curity.se/ns/conf/client-attestation">
              <id>non-prod-policy</id>
              <mode>non-production</mode>
              <override-certificate-chain-validation>
                  <do-not-validate-certificate-chain/>
              </override-certificate-chain-validation>
          </ios-policy>
      </client-attestation>
  </facilities>
</config>

Note that when custom trust-anchors are used in an iOS Attestation Policy, they must be provided in PEM-format.

When a iOS client is successfully attested, a CAT is issued that the client can exchange for a HAAPI token, similarly to how web- and Android clients do.

Browser (Web) client attestation configuration

A browser-based OAuth client needs to be properly configured in Curity before it can perform a successful attestation and use HAAPI. Currently, this is only possible for statically configured clients and involves two parts.

First, the HTTP origin where the browser-based application is running needs to be added to the allowed-origins list and attestation needs to be set to web. The haapi capability also needs to be enabled, similarly to what happens for the other client types. The following snippet exemplifies these configuration requirements, for a browser-based client application running from https://example.com:1234.

Listing 286 example configuration snippet for a browser-based client.
<authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
  <client-store>
      <config-backed>
          <client>
            <id>haapi-public-client</id>
            <allowed-origins>https://example.com:1234</allowed-origins>
            ...
            <capabilities>
              ...
              <haapi/>
            </capabilities>
            <attestation>
              <web />
            </attestation>
          </client>

Finally, the client may optionally use a custom attestation policy. For that, a new web-policy needs to be defined under facilities/client-attestation. This policy can then override the default attestation behavior characteristics:

  • disable-origin-verification - Allows a browser-based client to obtain attestation from any origin. This setting should not be set to true in production environments and should be used only for development and testing purposes.

After creating the custom policy, the static client configuration needs to refer it, as shown in the next snippet.

Listing 287 example custom attestation policy snippet for a browser-based client.
<config xmlns="http://tail-f.com/ns/config/1.0">
  <facilities xmlns="https://curity.se/ns/conf/base">
      <client-attestation>
          <web-policy xmlns="https://curity.se/ns/conf/client-attestation">
              <id>test-policy</id>
              <disable-origin-verification>true</disable-origin-verification>
          </web-policy>
      </client-attestation>
  </facilities>
  ...
  <profiles xmlns="https://curity.se/ns/conf/base">
      <profile>
          <id>...</id>
          <type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
          <settings>
              <authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
                  <client-store>
                      <config-backed>
                          <client>
                              <id>haapi-public-client</id>
                              <attestation>
                                  <web>
                                      <web-policy>test-policy</web-policy>
                                  </web>
                              </attestation>
                              ...
                          </client>

Warning

Note that using a custom policy that changes the default behavior may compromise the security characteristics of the system.

Disabling attestation for testing purposes

It is possible to completely disable the attestation validation performed for a specific configured client. This is achieved by setting attestation/disable-attestation-validation under the client’s configuration. The client application should still perform the attestation protocol, using the provided SDKs, however the attestation data will not be validated.

Warning

Disabling attestation validation for a client allows any application to act as that client. Therefore, this should only be used in non-production environments, as a way to enable some testing scenarios (e.g. testing iOS applications running on simulators and not real devices).

Debugging Web CAT problems

It may be difficult to understand when problems happen because, by the nature of client attestation on a browser, a lot of things happen in the background (generation of cryptographic keys, execution of binary code, exchange of messages between Javascript application and the Javascript HAAPI driver) which can go wrong in certain circumstances.

Problems might occur, for example, due to users having older browsers which do not have the most recent Web Crypto API or cannot execute WebAssembly. We have also seen browsers that appear to “freeze” a session while the user likely closes the window, only to restart again much later and attempt to continue an authentication flow when the temporary tokens stored by the browser have long expired.

To make it easier to troubleshoot such issues, we have added extra logging both in the Javascript driver library and in the Curity Identity Server.

To enable extra logging on the client, add the following Javascript snippet to your application:

Listing 288 enabling debug information for the haapi-web-driver.
window['se:curity:web-cat:debug'] = true

Refer to the haapi-web-driver package documentation for more details.

On the server side, the logger called se.curity.identityserver.controllers.cat.RequestModel, in particular, will log information about requests from Web CAT Clients (look for the CAT request ... message).

On successful CAT issuance, se.curity.identityserver.controllers.cat.CatController will log the full attestation data collected on the client (look for the messages containing WebAttestationData and Successfully issued a CAT...).

Make sure to enable DEBUG-level logging for these loggers in non-production environments.

Flow state management

HAAPI was designed to be used in cross origin browser-based applications, i.e., applications that have a different origin than the one where HAAPI is located. Due to that, HAAPI uses a custom header and not cookies to communicate the state required through the several steps that constitute an authorization and authentication flow. This state management protocol is based on two headers:

  • The Set-Session-Id response header is used by the server to communicate an opaque identifier to the client application.
  • The Session-Id request header is used by the client to send this opaque identifier on all subsequent requests from the same flow. Note that the server can send new Set-Session-Id headers in the middle of a flow. In that case, the client application must use the newer identifier to complete the flow.

Session identifiers are bound to access token DPoP keys, meaning if those keys change (e.g. due to a new access token being requested), then a new flow needs to be started.

API Driven UI

It is possible to use HAAPI to create an API-driven front-channel user interface, such as a Single Page Application (SPA), as an alternative to the template driven approach documented on chapter Front-End Development. See section API Driven UI for more information.

SDK

Curity provides a number of SDKs that allow the development of tools to extend and integrate with the Curity Identity Server. The following HAAPI SDKs are currently available to help develop client applications that use HAAPI.