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:
Check out the diagram for a visual version of these steps:
Now let's get into the nitty gritty for each step.
First, you have to have an API key with a private/public key pair.
If you're using the Redox Platform API, you need to create a user-level API key instead. Navigate to the user-level API key page to create and manage Platform API keys.
If you're an existing user with legacy keys, you may also see radio buttons display for Legacy and OAuth options. If these are visible, select the OAuth radio button.
kid
property in the JWT to find the right public key. If you provide a JWKS URL and entry, we only use the URL, so you don't need to provide both.
Next, you must request an access token with an auth request. There are two options for this:
Either way, the authentication steps are the same. If you choose to use Postman, complete the prep steps. Otherwise, skip to the authentication steps.
For Postman, there's a little bit of setup to do before running the authentication requests.
If you download the Postman environment from the Settings tab (when you're generating keys), the value of the private key automatically populates. But if you download the Postman environment from the DevTools tab instead, the private key is empty. Make sure to manually enter the private key in the Postman environment if you download it at this step.
If you provided your public key with a JWKS URL, you also need to populate the kid
value.
Now that Postman is ready, you can proceed to the auth request steps.
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.
If you're operating in Canada, you must tweak the Redox hostname slightly. All you need to do is add a ca
in that URL like this: https://api.ca.redoxengine.com
.
curl --location --request POST 'https://api.redoxengine.com/v2/auth/token' \--header 'Content-Type: application/x-www-form-urlencoded' \--data-urlencode 'grant_type=client_credentials' \--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \--data-urlencode 'client_assertion={{signed_assertion}}'
const jose = require('jose');const axios = require('axios');const randomBytes = require('crypto').randomBytes;const qsStringify = require('querystring').stringify;const https = require('node:https');// import your private key as PEM or JWKconst privateKeyPEM = ``; // INSERT PRIVATE PEM KEY HEREconst clientId = '<INSERT CLIENT ID HERE>';const iat = Math.floor(new Date().getTime()/1000); // Current timestamp in seconds (undefined is valid)const aud = 'https://api.redoxengine.com/v2/auth/token';const kid = '<INSERT KID HERE>';const scope = 'fhir:development'; // valid scopes are 'fhir:development', 'fhir:staging', or 'fhir:production'async function getSignedAssertion(clientId, privateKeyPEM, kid, aud, iat, scope) {const privateKey = await jose.importPKCS8(privateKeyPEM, 'RS384');const payload = {scope,};const signedAssertion = await new jose.SignJWT(payload).setProtectedHeader({alg: 'RS384',kid: kid}).setAudience(aud).setIssuer(clientId).setSubject(clientId).setIssuedAt(iat).setJti(randomBytes(8).toString('hex')) // a random string to prevent replay attacks.sign(privateKey);return signedAssertion;}async function requestJwtAccessTokenAxios(signedAssertion, scope) {const requestBody = qsStringify({grant_type: 'client_credentials',client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',client_assertion: signedAssertion,scope});try {const result = await axios.post("https://api.redoxengine.com/v2/auth/token", requestBody, {headers: {'content-type': 'application/x-www-form-urlencoded'}});// return response with keys: access_token, scope, token_type, and expires_inreturn result.data;}catch(e) {return e.response.data;}}async function requestJwtAccessTokenNoLibrary(signedAssertion, scope) {const requestBody = qsStringify({grant_type: 'client_credentials',client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',client_assertion: signedAssertion,scope});const options = {method: 'POST',headers: {'content-type': 'application/x-www-form-urlencoded'}};return new Promise(function(resolve, reject) {try {const req = https.request('https://api.redoxengine.com/v2/auth/token', options, (res) => {console.log('statusCode:', res.statusCode);console.log('headers:', res.headers);let data = '';res.on('data', (d) => {data += d;});res.on('end', (d) =>{const parsed = JSON.parse(data);resolve(parsed);});res.on('error', (error) => {throw error;})});req.write(requestBody);req.end();}catch(e) {reject(e);}});}(async() => {const signedAssertion = await getSignedAssertion(clientId, privateKeyPEM, kid, aud, iat, scope);const accessTokenAxios = await requestJwtAccessTokenAxios(signedAssertion, scope);const accessTokenNoLibrary = await requestJwtAccessTokenNoLibrary(signedAssertion, scope);console.log({accessTokenAxios});console.log({accessTokenNoLibrary});})();
npm i axios jose
. Then, update the the privateKeyPEM
, clientId
, and kid
based on the signed request you generated in step 1.Just note that access tokens expire after 5 minutes, so if a request fails, it's likely that your access token has expired.
We don't support refresh tokens. If you have low traffic, we recommend requesting an access token before making any API request. But if you have higher traffic, you can get as many access tokens as you need, so you could use different access tokens for multiple API requests.
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.
For the header, you should use content-type application/x-www-form-urlencoded
and these values:
Parameter | Required | Description |
---|---|---|
alg | Y | The JWA algorithm used for signing the authentication JWT. Note that we currently only support RS384 . |
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 | This is 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. |
Parameter | Required | Description |
---|---|---|
grant_type | Y | Populate this with client_credentials . |
client_assertion_type | Y | If using the Postman collection, this value automatically populates. If using your own application, populate this with urn:ietf:params:oauth:client-assertion-type:jwt-bearer . |
client_assertion | Y | If using the Postman collection, this value automatically populates. If using your own application, populate this with the signed assertion you generated with your private key. |
Parameter | Required | Description |
---|---|---|
iss | Y | The issuer of the JWT. This should be populated with the client_id of the key you created in the Dashboard. Keep in mind that this is the same value that's provided for the sub claim. |
sub | Y | This should be populated with the client_id of the key you created in the Dashboard. Keep in mind that this is the same value that's provided for the iss claim. |
aud | Y | The audience of the JWT. This should be populated 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 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 | This is a nonce string value that uniquely identifies the JWT. Redox denies requests if the jti is reused within the lifetime of the JWT. |
Now you're ready to initiate API requests with the access token in the Authorization HTTP header using the Bearer authentication scheme like this:
curl \-X POST https://api.redoxengine.com/endpoint \-H "Content-Type: application/json" \-H "Authorization: Bearer [TOKEN]" \-d '{}'