OAuth2 support for lightweight UI clients

Next to outbound OAuth2 secured requests in which OHI applications act as the client and the server, Oracle Health Insurance applications also support the following OAuth2 specific use case: a lightweight UI that executes entirely inside a web browser. One example of this is an Oracle JET based Javascript UI as is under construction for Oracle Health Insurance applications. Another example is a custom client built by customers.

The OAuth2 Implicit grant type is meant to support this use case type. However, since JavaScript clients cannot be trusted to "keep secrets", like a clientId and clientSecret, it is considered a bad idea to have JavaScript clients directly requesting access tokens with the OAuth Authorization Server. As an alternative, Oracle Health Insurance applications come with an Access Token Service that JavaScript clients can use to obtain OAuth Access Tokens. The Access Token Service uses the OAuth2 Assertion grant type (with JWT as assertion). The Assertion grant type allows propagation of the authenticated user to the RESTful services that are being called so that access control / authorization can be applied and Oracle Health Insurance applications can properly audit a user’s actions.

Configuration example: a client that uses the Access Token Service

This example demonstrates the setup for a client that interacts with the Access Token Service in order to obtain a bearer token that it subsequently uses to access an Oracle Health Insurance application’s Person service. An example of such a client is a lightweight UI that executes inside a web browser. For this scenario the calls that the Custom UI performs are explained in detail.

The example makes use of the following components:

  • An OAuth Authorization Server, for example as provided by the Oracle Identity Cloud Service (IDCS) or Oracle Access Manager (OAM).

  • An Oracle Health Insurance application that is deployed in its own WebLogic domain.

1. Configure the OAuth2 Authorization Server and register client applications

Setting up a specific OAuth Authorization Server is beyond the scope of this guide, see the Authorization Server’s product documentation for that.

Before a client application can request access to resources on a resource server, the client application must first register with the OAuth Authorization Service as OAuth client. For the purpose of this example, the following OAuth Client application needs to be registered:

Attribute Value

Client Id

Specify a client id that is unique within the context of the Authorization Server, e.g. "myClient"

Client Secret

Specify a secret or choose to generate one"

Make sure that the JWT Assertion grant type is supported.

2. Configure keystore and signing key

For this grant type the request to the authorization server contains client credentials in the basic authentication header and user credentials in a JWT assertion. When the Oracle Health Insurance application creates the JWT assertion it signs it with a signing key. In this step a keystore is configured that will hold the key pair, a private key that the Oracle Health Insurance application uses to sign the JWT and a public key that is shared with the Authorization Server and that is used for verification of the signature. This way trust between these two components is established.

If that is not done yet, a WebLogic application stripe with an initial key store needs to be created first. Use the following WLST command:

svc = getOpssService(name='KeyStoreService')
svc.createKeyStore(appStripe='<application_name>', name='keystore', password='password', permission=true)

Where <application_name> needs to be replaced with the name of the Oracle Health Insurance application, e.g. authorizations, claims or policies.

There should be a confirmation that the keystore is created.

Create a key in the existing keystore or create a new one by issuing the following POST request to the Oracle Health Insurance application’s "/api/keystores" resource:

{ "keyStoreName": "aKeyStorename",
  "keyStorePassword": "aKeyStorePassword"
}

with HTTP headers:

Accept = application/json
Content-Type = application/json

This should result in a 204 response.

In this keystore create a key by POSTing the following payload to the Oracle Health Insurance application’s "/api/keystores/aKeyStorename//keys":

{ "keyStoreName": "aKeyStorename/",
  "keyStorePassword": "aKeyStorePassword",
  "alias": "akey",
  "keyPassword": "aKeyPassword",
  "issuer": "CN=OHI",
  "subject": "CN=user",
  "validityDays": "365"
}

with headers:

Accept = application/json
Content-Type = application/json

This should again result in a 204 response.

3. Client configuration in the Oracle Health Insurance application

Configure the REST client in the Oracle Health Insurance application so that an OAuth Access Token can be obtained using the OAuth assertion grant type. POST a request with the following JSON payload to the Oracle Health Insurance application’s "/api/generic/oauthassertionjwtbearergrantconfigurations" resource:

{ "audienceName": "https://identity.oraclecloud.com/",
  "issuerName": "myClient",
  "clientId": "aConfiguredAndUniqueClientId",
  "expirationPeriod": "3600",
  "tokenURI": "https://host:port/oauth2/v1/token",
  "restClientId": "myRestClient",
  "keyStoreName": "aKeyStore",
  "keyStoreAlias": "akey",
  "jwsAlgorithm": "RS256",
  "scope": "urn:opc:idm:__myscopes__"
}

with headers:

Accept = application/json
Content-Type = application/json

This should result in a 201 response.

For this newly created client configuration, set the client secret by PUTting the following payload to the Oracle Health Insurance application’s "/api/oauthclientconfigurations/{id}/setclientsecret":

{ "clientSecret": "aConfiguredClientSecret"
}

with headers:

Accept = application/json
Content-Type = application/json

where {id} is the ID of the client configuration. It is possible to reset client secret for the client configuration using the same URL and payload structure mentioned above.

4. Export public signing key certificate

Oracle Health Insurance applications provide a capability to export the public key certificate by sending a GET request to a specific resource. In this example the URL would be "/api/keystores/aKeyStore/keys/akey?keyStorePassword=aKeyStorePassword".

This should result in a 200 response with certificate in PEM format as payload. For example (adjusted for brevity and readability):

-----BEGIN CERTIFICATE-----
MIICt...lrMdIqoQ==
-----END CERTIFICATE-----

5. Import signing key certificate to OAuth2 server

If the authorization server, like OAM, does not support loading of PEM files directly, convert the file into a format that can be imported, for example, to convert the file to DER format:

  • Save the certificate in a file called "cert.pem"

  • Run the following command:

    openssl x509 -outform der -in cert.pem -out cert.der
  • Transfer the cert.der file to the OAuth2 authorization server

  • On the OAM machine go to the "$domain_home/config/fmwconfig" directory of the OAM Oauth2 server

  • Remove any existing alias from the keystore:

    keytool -delete -alias akey -keystore default-keystore.jks
  • Import the certificate using the keytool:

    keytool -import -alias akey -keystore default-keystore.jks -file /tmp/cert.der
  • Verify the keystore contents:

    keytool -list -keystore default-keystore.jks -v

6. Using the Oracle Health Insurance application’s Token Access Service to securely obtain an

OAuth2 access token

Every Oracle Health Insurance application provides a Token Access Service that is meant to be used by clients that are not capable of "keeping secrets", like JavaScript applications that execute in a browser. In essence, the Token Access Service is a trusted client for the authorization server, capable of requesting access tokens on behalf of those JavaScript applications. JavaScript applications request access tokens through the Token Access Service, who, in turn, passes on the request to the OAuth2 authorization server and relays the received token back to the application. The client needs to present the following information to obtain an access token:

  • An authenticated user, e.g. by providing a session cookie or by sending credentials. Unauthenticated requests are rejected immediately.

  • An Anti-CSRF (Cross Site Request Forgery) token that the client obtained from the Token Access Service separately.

  • A clientId, like "myClient" in this example, that represents the scope or the resource that the client wants to access.

After validating the requests pre-requisites, the Token Access Service constructs a JSON Web Token that contains the user’s identity, signs that using the private key entry that was created earlier, and passes it on to the OAuth2 authorization server.

Assuming that authentication was successful, the following requests need to be executed by the client in order to obtain an access token:

6.1 Get an anti-CSRF token

An anti-CSRF token is obtained by sending a POST request to the Oracle Health Insurance application’s "/api/accesstokens/anticsrftoken" resource. The request needs to be accompanied by:

  • the clientId as a query parameter;

  • either a session cookie that was obtained after successful authentication (and that results in a principal being available in the application’s security context) or an Authorization HTTP header for Basic Authentication.

This results in an HTTP 200 response that contains:

  • an XSRF-TOKEN cookie;

  • an X-XSRF-TOKEN HTTP header value.

Both the cookie as well as the X-XSRF-TOKEN HTTP header value need to be presented in the following request. Note that the cookie and the HTTP header value belong together.

Note

Make sure that passing the X-XSRF-TOKEN HTTP Header is allowed by setting system property: ohi.cors.access.control.expose.headers=X-XSRF-TOKEN

6.2 Retrieve the access token

An access token is obtained by sending a POST request to the Oracle Health Insurance application’s "/api/accesstokens/accesstoken" resource. The request needs to be accompanied by:

  • the clientId as a query parameter;

  • a session cookie that was obtained after successful authentication (and that results in a principal being available in the application’s security context) or an Authorization HTTP header for Basic Authentication;

  • the XSRF-TOKEN cookie that was obtained in the previous step;

  • the X-XSRF-TOKEN HTTP header value.

This results in an HTTP 200 response that contains a JSON payload like the following (adjusted for brevity and readability):

{ "expires_in": 28800,
  "access_token": "eyJhb...JvuNQ"
}

The access token is a JWT that the client needs to present when accessing an OAuth2 secured resource. The client should not need to inspect it, i.e. the token is opaque to the client. The "iat" (issued at) claim identifies the time at which the JWT was issued and the "expires_in" value holds the expiration time. Note that it is the responsibility of the client to get a new access token in time.

7. Configure the Oracle Health Insurance application to validate the bearer token

Oracle Health Insurance applications support the following strategies for token validation:

  1. Using a set of JSON Web Keys (JWK). This implements RFC 7517, the JWK specification. The implementation supports obtaining a JWK Set from the following:

    • a "jwks" endpoint as exposed by some OAuth Authorization Servers

    • a set of keys in a specific KeyStore that is maintained locally2.

  2. Using an Authorization Server’s token introspection endpoint. This is an implementation of RFC 7662, the OAuth 2.0 Token Introspection specification.

The subparagraph below outlines use of a JWKSet that is based on the configured keystore.

7.1 Validate the bearer token using a JWKSet that is based on the configured

keystore

Export the authorization server’s signing key’s public certificate, e.g. by using a keytool command like the following:

keytool -exportcert -file /tmp/oauth2.cert -alias oauthkey -keystore default-keystore.jks -rfc

Get the PEM formatted certificate string from the exported file and import it into the keystore that is configured for the Oracle Health Insurance application. POST a request with the following JSON payload to the Oracle Health Insurance application’s "/api/keystores/<keyStoreName>/keys/<alias>/" resource, e.g. "/api/keystores/aKeyStore/keys/oauthkey/":

{ "keyStoreName": "aKeyStore",
  "keyStorePassword": "aKeyStorePassword",
  "alias": "oauthkey",
  "certificate": "-----BEGIN CERTIFICATE-----\nMIIDYTC...\nBgNV...\n-----END CERTIFICATE-----"
}

with headers:

Accept = application/json
Content-Type = application/json

This should result in a 204 No Content response.

Note the formatting of the "certificate" attribute value (which was adjusted for brevity and readability):

  • The string needs to start with "-----BEGIN CERTIFICATE-----".

  • The entire value needs to be on one line, the lines in the file need to separated by "\n" line feeds.

  • The string needs to end with "-----END CERTIFICATE-----".

To have the Oracle Health Insurance application construct a JWKSet that is based on the configured keystore (which now includes the certificate for the oauthkey alias) make sure that the following system properties are set:

ohi.oauth.token.validation.method=JWKSET
ohi.oauth.jwk.keystore.name=aKeyStore