Skip to content
arlowatts edited this page Dec 20, 2023 · 2 revisions

Example API Client Console Application

This example client application helps developers understand and debug:

  • authentication using a client id and a client secret (for non-production environements)
  • authentication using a signed JWT
  • invoking an example service (forthcoming)

This example client uses .NET 6.0, which you can use to create server apps, command line apps, or WinForms desktop apps.

Build

Build the project by running dotnet build in the command line from Client/src/.

Run

Fill out the fields in Client/src/appsettings.json with the appropriate configuration values. Run the project by running dotnet run in the command line from Client/src/.

How it Works

This example client application follows several steps to authenticate and authorize the user before the request is processed by the APIs. After the client is authenticated, it submits a single request given in the configuration file.

The configuration file appsettings.json that referred to in the following instructions is located at Client/src/appsettings.json.

Authentication

  1. First, the program reads the configuration values from appsettings.json. Then if UseKeyFile is true, it will attempt to read the key from the supplied .pfx file (see below for instructions to create this file). If UseKeyFile is false, the program will instead use ClientId and ClientSecret to authenticate itself.

  2. Next, the program will determine what URL endpoint it should get the access token from. It gets this URL by querying the given Authority string suffixed with /.well-known/openid-configuration. If you are getting 404 Not Found errors at this point, make sure that you've entered only the Keycloak base URL in the Authority field of appsettings.json. The result of this operation is another URL that the client application will use for the actual authentication.

  3. The program sends a request to the URL it received in step 2. In this request it will include the Audience and Scope parameters from appsettings.json, as well as the chosen authentication credentials (key file or ID and secret). If the authentication is successful, the server will return a JSON Web Token that will provide the client with access to the stated scope.

Authorization

With the access token the client receives in step 3 of Authentication, the client is ready to submit a request to the ServiceUrl given in the same appsettings.json file. This should be the URL to one of the nine API services within this application. The API must be active and ready to receive requests.

  1. The client loads the HL7-v2 formatted data payload given in appsettings.json into the program. It encodes it in base64 and wraps it in a FHIR-compliant JSON object. This JSON object will form the body of the request to the API.

  2. The client creates an HTTPRequestMethod object and populates it with the appropriate content and headers. The headers are Content-Type: application/json and Authorization: Bearer {access token}. The content is the FHIR-style JSON object from step 2, converted to a string.

  3. The HTTP request is sent to the destination service which verifies the access token. If the token is not successfully verified and authorized, a 401 Unauthorized error response will be returned to the client. Otherwise, the response will reflect the success or failure of the operation. If you get errors at this stage during testing, check the logs of the API (if you have access to them) to see more detailed messages. Also check the logs of the PharmaNet environment the API is using to see if it recorded an interaction.

Example Configuration

Below is an example appsettings.json file. In this example, no key file is provided so the program will be configured to use the client ID and secret. This example assumes that you have the Claim API running in a Docker container mapped to localhost:8181. The HL7 Payload is one of the sample messages from the k6 test suite for the Claim service.

{
    "UseKeyFile": "false",
    "ServiceUrl": "http://localhost:8181/api/v1/Claim",
    "Payload": "MSH|^~&|PHARMACY|VENDOR_BC01000971|PNP|PP||RB:69.11.119.122|ZPN|180736|D|2.1||\rZZZ|TDT||180736|P1|XXBSK||||\rZCA|000001|03|30|KC|16|\rZCB|BC000001ED|220823|180736\rZCC|||||||||||\rZCF|220823|000000000|999999999\r",
    "OpenIdConnect": {
        "Audience": "",
        "Authority": "https://common-logon-test.hlth.gov.bc.ca/auth/realms/moh_applications",
        "ClientId": "{your-client-id}",
        "ClientSecret": "{your-client-secret}",
        "Scope": "openid system/Claim.write system/Claim.read"
    },
    "JwtSigning": {
        "CertificatePassword": "{your-certificate-password}",
        "CertificatePfxFile": "{your-certificate.pfx}"
    }
}

Generating and Using a Signed JWT for Client Authentication with Keycloak

Keycloak can generate a keystore.jks file containing the private key for the client application. It can also accept the upload of a JKS or PFX certificate or PEM for validating the signed JWT provided by the client during authentication. You can also have Keycloak point to a URL where you keep your public keys in a JWKS file. Then you are responsible for generating/maintaining your certificates and their corresponding public/private key pairs. This can help to minimize configurations in Keycloak.

In this example client, the Keycloak administrator provides us with a keystore.jks file and its password. We need to convert this file to a pfx certificate file to allow us to sign the JWT.

Here are the steps, assuming that the private key is created by the Keycloak administrator for you.

Convert the JKS to a pkcs12 (pfx) Certificate File Format

If Keycloak is supplying your client's private key, it will hang onto the public key to verify your client authentication. Keycloak provides a JKS file format for your private keys. It will need to be converted to a pfx certificate file in order to use it with the Client code provided here.

You will need to know the JKS source password, but first you will be prompted to enter a destination password for the pfx file. It will need to be at least 6 characters long.

keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.pfx -srcstoretype jks -deststoretype pkcs12

You can now use this pfx file in the Client example, and its private key will be used to sign the JWT for OAuth 2.0 authentication against Keycloak. You will use the password you set in the appsettings.json configuration file as well.

You will want to research ways to store certificates securely and in a way that makes them available to the app configurations.

Getting the Public Key From Your PFX File (Optional)

If you want to check the signing of the JWT using your public key, you can create a public key file and then use it to check the signing. In this case, follow these steps.

Step 1 - Convert the PKCS File to a PEM File

Using openssl, you will now convert the pkcs12 file to pem format. The pem will also require a passphrase to be set.

openssl pkcs12 -in keystore.pfx -out keystore.pem

Step 2 - Extract the RSA256 Public Key Output

openssl rsa -in keystore.pem -RSAPublicKey_out > keystore_public.txt

If you paste your base64 encoded Signed Jwt into https://jwt.io/, you can then paste the contents of your public key file to verify that your Jwt is properly signed. This is what Keycloak will do to verify that you signed your Jwt.

Warning

The above method of using appsettings.json to store the private key is not recommended for production. You will want to store your private key in a vault or secure area, and retrieve it in code to sign the JWT.

Clone this wiki locally