Authenticate an OAuth API key

If you haven’t already, check out our authentication overview. This article contains instructions for how to authenticate your system with our recommended API key.

Let's start with the high-level steps:

  1. Provide your public key to Redox.
  2. Request an access token from Redox with an auth request.
  3. Store the access token in your own system.
  4. Initiate any API request with the access token in the header.

Check out the diagram for a visual version of these steps:

OAuth flow
OAuth flow

Now let's get into the nitty gritty for each step.

Provide your public key to Redox

First, you have to have an API key with a private/public key pair.

  1. Log in to the dashboard.
  2. From the navigation menu, select the Developer page.
  3. By default, the Developer page opens and displays the API Keys tab.
  4. Any created API keys display. The top section contains OAuth keys and the bottom section contains legacy keys. To create a new API key, click the Create API Key button. If you want to configure an existing API key, click the Edit button next to it. Then skip to step 7.
  5. A modal opens with the API key details. In the Name field, enter the API key name.
  6. Click the Add button.
    Create an OAuth API key
    Create an OAuth API key
  7. By default, the Settings page of the newly created API key displays with the details. The Name field displays the API key name you entered previously. The client ID field shows the automatically generated ID for the API key. Copy and store the client ID to use when retrieving an access token. Then, choose one of the following options to complete.

Request and store an access token

Next, you must request an access token with an auth request. There are two options for this:

  • Use our Postman collection; or
  • Use an application of your choice.

Either way, the authentication steps are the same. If you choose to use Postman, complete the prep steps. Otherwise, skip to the authentication steps.

Postman prep

For Postman, there's a little bit of setup to do before running the authentication requests.

  1. From the Settings tab of the API key, click the DevTools (Postman) tab.
  2. The Postman options display. Click the Download Postman collection button.
  3. Run the authentication collection in Postman and select the option for how you want to run it: Postman for Web or Postman for Mac.
  4. Postman opens and automatically imports the Redox Platform Authentication collection. You can use this collection for authenticating requests sent via the Redox FHIR® API or the Data Model API.
  5. If you haven't already downloaded the Postman environment when you were generating keys, click the Download Postman environment button from the DevTools tab.
  6. In Postman, click the Environments tab.
  7. Click the Import button.
  8. From the file explorer, select the Postman environment that you just downloaded.
  9. The Postman environment is added. Select the environment name in the left-hand pane or in the Environment drop-down.
  10. If the private key variable is still empty, enter the private key that you generated previously.

Now that Postman is ready, you can proceed to the auth request steps.

Send an auth request

  1. Generate a signed request in your system using your private key.
  2. Send an auth request with the signed assertion to https://api.redoxengine.com/v2/auth/token via HTTP POST from your system. Check out the Header and parameter definitions below these steps for explanations of the header and parameter values.
    1. With Postman, the authentication request looks something like this:

      Example: Auth request for OAuth (cURL)

      1
      curl --location --request POST 'https://api.redoxengine.com/v2/auth/token' \
      2
      --header 'Content-Type: application/x-www-form-urlencoded' \
      3
      --data-urlencode 'grant_type=client_credentials' \
      4
      --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
      5
      --data-urlencode 'client_assertion={{signed_assertion}}'
    2. With your own application, the request may look like this:

      Example: Auth request for OAuth (JavaScript)

      1
      const jose = require('jose');
      2
      const axios = require('axios');
      3
      const randomBytes = require('crypto').randomBytes;
      4
      const qsStringify = require('querystring').stringify;
      5
      const https = require('node:https');
      6
      7
      // import your private key as PEM or JWK
      8
      const privateKeyPEM = ``; // INSERT PRIVATE PEM KEY HERE
      9
      10
      const clientId = '<INSERT CLIENT ID HERE>';
      11
      const iat = Math.floor(new Date().getTime()/1000); // Current timestamp in seconds (undefined is valid)
      12
      const aud = 'https://api.redoxengine.com/v2/auth/token';
      13
      const kid = '<INSERT KID HERE>';
      14
      15
      async function getSignedAssertion(clientId, privateKeyPEM, kid, aud, iat) {
      16
      const privateKey = await jose.importPKCS8(privateKeyPEM, 'RS384');
      17
      18
      const payload = {};
      19
      20
      const signedAssertion = await new jose.SignJWT(payload)
      21
      .setProtectedHeader({
      22
      alg: 'RS384',
      23
      kid: kid
      24
      })
      25
      .setAudience(aud)
      26
      .setIssuer(clientId)
      27
      .setSubject(clientId)
      28
      .setIssuedAt(iat)
      29
      .setJti(randomBytes(8).toString('hex')) // a random string to prevent replay attacks
      30
      .sign(privateKey);
      31
      32
      return signedAssertion;
      33
      }
      34
      35
      async function requestJwtAccessTokenAxios(signedAssertion) {
      36
      const requestBody = qsStringify({
      37
      grant_type: 'client_credentials',
      38
      client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
      39
      client_assertion: signedAssertion
      40
      });
      41
      42
      try {
      43
      const result = await axios.post(
      44
      "https://api.redoxengine.com/v2/auth/token", requestBody, {
      45
      headers: {
      46
      'content-type': 'application/x-www-form-urlencoded'
      47
      }
      48
      }
      49
      );
      50
      51
      // return response with keys: access_token, token_type, and expires_in
      52
      return result.data;
      53
      }
      54
      catch(e) {
      55
      return e.response.data;
      56
      }
      57
      }
      58
      59
      async function requestJwtAccessTokenNoLibrary(signedAssertion) {
      60
      61
      const requestBody = qsStringify({
      62
      grant_type: 'client_credentials',
      63
      client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
      64
      client_assertion: signedAssertion
      65
      });
      66
      67
      const options = {
      68
      method: 'POST',
      69
      headers: {
      70
      'content-type': 'application/x-www-form-urlencoded'
      71
      }
      72
      };
      73
      74
      return new Promise(function(resolve, reject) {
      75
      try {
      76
      const req = https.request('https://api.redoxengine.com/v2/auth/token', options, (res) => {
      77
      console.log('statusCode:', res.statusCode);
      78
      console.log('headers:', res.headers);
      79
      let data = '';
      80
      81
      res.on('data', (d) => {
      82
      data += d;
      83
      });
      84
      res.on('end', (d) =>{
      85
      const parsed = JSON.parse(data);
      86
      resolve(parsed);
      87
      });
      88
      res.on('error', (error) => {
      89
      throw error;
      90
      })
      91
      });
      92
      93
      req.write(requestBody);
      94
      req.end();
      95
      }
      96
      catch(e) {
      97
      reject(e);
      98
      }
      99
      100
      });
      101
      }
      102
      103
      (async() => {
      104
      const signedAssertion = await getSignedAssertion(clientId, privateKeyPEM, kid, aud, iat);
      105
      const accessTokenAxios = await requestJwtAccessTokenAxios(signedAssertion);
      106
      const accessTokenNoLibrary = await requestJwtAccessTokenNoLibrary(signedAssertion);
      107
      console.log({accessTokenAxios});
      108
      console.log({accessTokenNoLibrary});
      109
      })();

      Example: Auth request for OAuth (python)

      1
      import datetime
      2
      import jwt
      3
      import requests
      4
      from uuid import uuid4
      5
      6
      with open('private_key.pem', 'rb') as f:
      7
      private_key = f.read()
      8
      9
      class BackendServiceAuth(requests.auth.AuthBase):
      10
      11
      def __init__(self, auth_location, client_id, kid, private_key):
      12
      self.auth_location = auth_location
      13
      self.client_id = client_id
      14
      self.kid = kid
      15
      self.private_key = private_key
      16
      self.token = None
      17
      18
      def __call__(self, request):
      19
      if not self.token:
      20
      expiration = datetime.datetime.now() + datetime.timedelta(minutes=5)
      21
      # Add 'kid' property to the header
      22
      headers = {
      23
      'kid': self.kid,
      24
      'alg': 'RS384',
      25
      'typ': 'JWT',
      26
      }
      27
      28
      assertion = jwt.encode(
      29
      {
      30
      'iss': self.client_id,
      31
      'sub': self.client_id,
      32
      'aud': self.auth_location,
      33
      'exp': int(expiration.strftime('%s')),
      34
      'iat': int(datetime.datetime.now().strftime('%s')),
      35
      'jti': uuid4().hex,
      36
      },
      37
      self.private_key,
      38
      algorithm='RS384',
      39
      headers=headers)
      40
      41
      print(f"assertion: {assertion}")
      42
      43
      payload = {
      44
      'grant_type': 'client_credentials',
      45
      'client_assertion_type':
      46
      'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
      47
      'client_assertion': assertion
      48
      }
      49
      try:
      50
      response = requests.post(self.auth_location, data=payload, timeout=30)
      51
      response.raise_for_status()
      52
      self.token = response.json()['access_token']
      53
      print(f"success! {response.json()}")
      54
      except requests.exceptions.RequestException as e:
      55
      if hasattr(e, 'response') and e.response is not None:
      56
      print(f"Request failed with status code: {e.response.status_code}")
      57
      print(f"Response body: {e.response.text}")
      58
      else:
      59
      print(f"Request failed: {e}")
      60
      61
      request.headers['Authorization'] = 'Bearer %s' % self.token
      62
      return request
      63
      64
      def main():
      65
      client_id = '<your client id>'
      66
      kid = '<the key id (kid) for your private key>'
      67
      auth_location = 'https://api.redoxengine.com/v2/auth/token'
      68
      auth = BackendServiceAuth(auth_location, client_id, kid, private_key)
      69
      70
      # Now you can use 'auth' as the authentication mechanism for your requests.
      71
      response = requests.post(
      72
      'https://api.redoxengine.com/fhir/R4/redox-fhir-sandbox/Development/Patient/_search',
      73
      auth=auth)
      74
      print(response.json()) # Example usage of the token to fetch patient data.
      75
      76
      if __name__ == "__main__":
      77
      main()
      Copy the code example above and make sure the axios and jose libraries are installed with npm i axios jose. Then, update the privateKeyPEM, clientId, and kid based on the signed request you generated in step 1.
  3. Redox validates the signature in the request using your public key, then sends back a response with the access token.
  4. Use the returned access token to make an API request to Redox.

JWT header and body definitions

In case you're interested in the definitions and expected values for header and parameters in the auth request code examples, check out the tables below. You can also view an example of a valid JWT with all of these values populated.

HTTP request body definitions

Initiate requests

Now you're ready to initiate API requests with the access token in the Authorization HTTP header using the Bearer authentication scheme like this:

Example: Initiating requests

1
curl \
2
-X POST https://api.redoxengine.com/endpoint \
3
-H "Content-Type: application/json" \
4
-H "Authorization: Bearer [TOKEN]" \
5
-d '{}'