Authenticate an OAuth API key

Last updated: Jan 6, 2026
DEVELOPER
HEALTH TECH VENDOR

Authenticating your system with an OAuth API key is our recommended auth method. Review our authentication overview.

Prerequisites

  • A user must be assigned to an engineer role to manage OAuth API keys. Learn about user roles.
  • If using your own private/public key pair, generate your own key pair.
  • If using your own application for sending the auth request, make sure the axios and jose libraries are installed with npm i axios jose.

Overview of the OAuth flow

First, here’s a high-level overview of the authentication flow:

  1. Provide your own or generate a public key with Redox.
  2. Send an auth request for an access token from Redox, then store the access token in your system.
  3. Initiate any API request with the access token in the header.

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

Diagram of OAuth flow: Your system owns the private key and uses it to send a signed assertion. Redox uses the public key to validate signed assertion then generates an access token. You store the access token and use it to initiate API requests to Redox.
OAuth flow

Step 1: Provide or generate a public key

First, you have to have an API key with a private/public key pair. There are two options for this step:

  • Option 1: Generate a private/public key pair in the Redox dashboard.
  • Option 2: Provide your own public key to Redox and store your own private key.

The navigation steps are the same, but there are separate steps for each method provided below.

  1. Log in to the dashboard.
  2. From the navigation menu, select the Developer page.
  3. By default, the Sources tab opens and displays any configured sources and OAuth API keys. Click the New OAuth API key button. If you want to configure an existing OAuth API key, click the Edit button next to it. Then skip to step 6.
  4. A modal opens with the API key details. In the Name field, enter the API key name.
  5. Click the Add button.
    A user creates a new OAuth API key in the Redox dashboard.
    Create an OAuth API key
  6. The Settings page opens with the API key details:
    • The Name field contains the API key name you entered previously. Click the Rename button to change it.
    • The Client ID field shows the automatically generated ID for the API key. Copy and store the client ID to use when sending an auth request.
  7. Choose one of the following options for your key pair:

Step 2: Send an auth request

Next, send an auth request to receive an access token from Redox. The auth request steps are the same, but there are two options for completing them:

  • Option 1: Use an application of your choice.
  • Option 2: Use our Postman collection (You must complete the prep steps first).
  1. Generate a signed request in your system using your private key.
  2. Copy the relevant code example for the auth request. Check out the header and parameter definitions below these steps for expected values.
    1. Option 1: With your own application, the auth request should look like one of these:
      For JavaScript, update the privateKeyPEM, clientId, and kid based on the signed request you generated in step 1.
      Example: Auth request for OAuth (JavaScript)
      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
      })();
      For Python, update the clientId, and kid based on the signed request you generated in step 1, then save your private key in a file named private_key.pem in the same directory as your script.
      Example: Auth request for OAuth (python)
      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()
    2. Option 2: With Postman, the authentication request should look like this:
      Example: Auth request for OAuth (cURL)
      bash
      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}}'
  3. Send the populated auth request with the signed assertion to https://api.redoxengine.com/v2/auth/token via HTTP POST from your system.
  4. Redox validates the signature in the request using your public key, then sends back a response with the access token.
  5. Store the access token in your system.

JWT header and body definitions

Review the definitions and expected values for the JWT head and body. Or, view an example of a valid JWT with all of these values populated.

For the JWT header, you should use content-type application/x-www-form-urlencoded and these values:

Parameter
Required
Description
alg
Y
The JSON Web Alorgithm (JWA) used for signing the authentication JWT.
We currently only support RS384 and ES384.
kid
Y
The identifier of the key-pair used to sign this JWT. This identifier tells us which public key to use to verify the JWT.
typ
Y
The type of token request. Populate this with JWT.
jku
N
This field may be populated if you provided a JWKS URL in the Redox dashboard.
If you provide it here, the URL should match what’s saved in the dashboard. Populate this with the TLS-protected JWKS URL, which contains the public key(s) that are accessible without authentication or authorization.
If this field isn’t present, Redox reverts to what’s saved in the dashboard.

For the JWT body, use all these required parameters:

Parameter
Required
Description
iss
Y
The issuer of the JWT. Populate this with the client_id of the key you created in the dashboard (in the Settings page of the OAuth API key). The iss and sub contain the same values.
sub
Y
Populate this with the client_id of the key you created in the dashboard (in the Settings page of the OAuth API key). The iss and sub contain the same values.
aud
Y
The audience of the JWT. Populate this with https://api.redoxengine.com/v2/auth/token.
iat
Y
The UTC timestamp for when the JWT was created (e.g., 1970-01-01T00:00:00Z UTC). This time must not be greater than 5 minutes before exp.
exp
Y
The expiration UTC timestamp for the JWT (e.g., 1970-01-01T00:00:00Z UTC). This time must not be greater than 5 minutes in the future.
jti
Y
A nonce string value that uniquely identifies the JWT. Redox denies requests if you attempt to reuse the jti within the lifetime of the JWT.

HTTP request body definitions

For the HTTP request body, use all these required parameters:

Parameter
Required
Description
grant_type
Y
Populate this with client_credentials.
client_assertion_type
Y
With your own application, populate this with urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
With Postman, this value automatically populates when you download the Postman collection.
client_assertion
Y
With your own application, populate this with the signed assertion you generated with your private key.
With Postman, this value automatically populates.

Step 3: Initiate API 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
bash
1
curl \
2
-X POST https://api.redoxengine.com/endpoint \
3
-H "Content-Type: application/json" \
4
-H "Authorization: Bearer $API_TOKEN" \
5
-d '{}'