Authentication

Authentication

Cortex employs session-based (using cookies) and signature-based authentication. Within each org, applications can be created which provide the necessary key and secret for API access. See Generate API Key for more details.

Session-Based Authentication

A typical use of session-based authentication would be for a client application, such as a mobile or web app, whereby individual users authenticate using their credentials to initiate a new session.

The session is unique to the authenticated user and will timeout after a period of inactivity, based on org settings. Account registration or logging in both initiate authenticated sessions.

When employing session-based authentication, Two Factor Authentication also applies.

Signature-Based Authentication

A signing key allows a third-party to make privileged calls to the API using a secure request signing mechanism. These keys can be configured for single or multiple user accounts, timed expiry, etc. Such a mechanism can be useful for scenarios such as back-end system integrations (e.g. Integrations with an EMR, or HL7 messaging appliance).

Signed Request Headers

Name

Value

Medable-Client-Signature

The calculated request signature.

Medable-Client-Timestamp

Client Unix Timestamp in milliseconds. The server issues a Medable-Server-Time responsed header with each operation which clients can use to synchronize.

Medable-Client-Nonce

A client-generated nonce matching /^[a-z\d]{16}$/i, used to prevent replay attacks.

Medable-Client-Nonce

Optionally sets the calling principal by account id (if the app is configured to allow principal override).

Example signed request

Node.js // Example signature-authenticated request using nodejs

'use strict';

function generateNonce() {
    var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
        maxC = chars.length-1,
        nonce = '',
        length = 16;

    while(length--) {
        nonce += chars[Math.round(Math.random() * maxC)];
    }
    return nonce;    
}

var crypto = require('crypto'),
    request = require('request');

var command = '/connections', // use only the path component (no query strings)
    method = 'GET', // uppercase http method
    apiKey = 'GsAqlhnIMzrDeD8V2MBQWq',
    apiSecret = 'Kzc8nwCQiKOgiwTIobaI3H6ryK4C5fC0QcYsuXCSbEMKcJPz8EU2phcj6akDB9VP',
    timestamp = Date.now(),
    hmac = crypto.createHmac('sha256', apiKey+apiSecret),
    signature;

hmac.update(command + ';' + method + ';' + timestamp);
signature = hmac.digest('hex');

var options = {
    url: 'https://api.dev.medable.com/example/v2/connections',
    headers: {
        'Medable-Client-Key': apiKey,
        'Medable-Client-Signature': signature,
        'Medable-Client-Timestamp': String(timestamp),
        'Medable-Client-Nonce': generateNonce()        
    }
};

request(options, function(error, response) {
    // ...
});

Authentication

Token Authentication and Scoping

Applications can leverage JSON Web Tokens (JWT) to access Cortex. Once a key-pair has been generated, tokens can be generated from either session-based or signature-based apps.

  • To enable token signing, Generate RSA key pairs for each app you wish to authenticate by clicking the "Generate Key Pair" link in the right-hand column of your app.

  • The default limit of permanent/limited-use authentication tokens per account in the app is 10.

  • Check Expose Keys to add this app's JWK to the list produced in GET /auth/certs/[jwk/pem]. Useful if you want third-parties to validate tokens generated by your app.

To invalidate all tokens for an app you must regenerate the key pair or delete the app altogether. You can invalidate permanent/limited-use tokens per subject using Account.revokeAuthToken() and Account.revokeSubjectTokens(). Ephemeral tokens (those without a jti) can only be revoked by deleting the application or regenerating the key-pair.

The following table outlines the JWT claim set supported in Cortex.

Claim

Description

aud

The audience. This is always script.env.url

iss

The issuing application's api key.

iat

The unix timestamp when the token was generated.

nbf

When present, the unix timestamp when the token becomes usable.

exp

When present, the unix timestamp when the token expires.

sub

The account identifier of the token's calling principal. The token will authenticate as this account when used with Cortex.

jti

When present, represents the unique token identifier. This will exist for permanent and limited-use tokens.

cortex/scp

The token scope. Scopes limit token access to sections listed in the claim.

cortex/rls

An array of roles granted to the token, and merged with the token subject's existing roles.

cortex/skp

When set, the token skips access controls for the purpose of limiting matches. The scripting analogue to this claim is skipAcl .

cortex/skc

When set, the token skips access controls for instance creation. The scripting analogue to this claim is bypassCreateAcl .

cortex/gnt

When set, the token grants this level of access to any and objects accessible to the token. The scripting analogue to this claim is granted.

cortex/cnt

When set, represents the maximum number of times the token can authenticate before invalidating.

Scoping

Scoping allows you to limit access to a subset of functionality, like object access, deployments, script and view execution, and administration functions.

All access tokens are scoped, and adding scope is done by adding one or more scope chains to the token in the cortex/scp claim, or inheriting them from subject roles and those added with cortex/rle claims.

Valid scope examples:

  • object.read.account.*.name

  • script.execute (same as script, script.execute.*)

  • view.execute.c_daily_report

Invalid scope examples:

  • object.read.account.name (missing identifier or * before property)

  • deployment.create (use object.create.deployment instead)

Scope Chain Level

Claim

Description

*

The token can perform all operations as the calling subject.

object [, create, read, update, delete] [, object[#type]] [*, identifier] [fqpp]

Object operations, such as reading accounts, creating connections, etc. The last element in the chain (fqpp) can be used to limit access to individual properties (e.g. c_set[]#c_foo.c_string_arr[]). You can see the fqpp (fully-qualified property path) in your schemas (GET /schemas). Examples:object.read.c_step_response#c_boolean. .c_value allows reading c_value from all c_step_response objects of the c_boolean type. object..c_comments.5953f7dc749219f1a2eee1ee allows full access to a specific instance of c_comments (assuming the subject principal already has access).

script [, execute] [, route, runner] [identifier]

This scope is required if the token is used to run script routes or use the ad-hoc script_runner system route. For routes, an Identifier can be added to the chain to limit use to a single route.

view [*, execute] [code, identifier]

This scope is required if the token is used to run script routes. An Identifier can be added to the chain to limit use to a single view.

deployment [*, execute] [identifier]

This scope is required if the token is used to execute deployments via scripted deployment automation.An Identifier can be added to the chain to limit use to a single deployment.

admin [*, read, update]

This scope is required if the token is used to call administrative routes, such as user provisioning, deactivating accounts, etc.

Scopes in Views

Views execute without scope; only view.execute.[view name] is required to run a view, supporting object.read scopes are not required.

However, any other claims (grant, skipAcl, roles, etc.) are still applied.

Views execute without scope; only view.execute.[view name] is required to run a view, supporting object.read scopes are not required.

However, any other claims (grant, skipAcl, roles, etc.) are still applied.

Scopes have been added to custom roles. Tokens inherit the scopes from roles held by the principal, as well as any roles granted by the cortex/rls claim. In the image below, the Scoped role add the ability to execute any view and a single scripted route.

Create Token Example

When making requests using JSON Web Tokens (JWT), the Medable-Client-Key header is not required as it is embedded in the token. You may still send the client key header without issue provided it matches the client key used in the creation of the token. If it does not, a key mismatch error will be returned.

const Account = org.objects.account;

const account_id = Account
  .find({email: 'sample@medable.com'})
  .skipAcl()
  .grant(4)
  .next()
  ._id;

// create an ephemeral token that allows the caller to act as account_id and
// read the account name and the subject of all c_message instances, skipping
// access control.
return org.objects.account.createAuthToken(
  'yk8XJmrMKWzhinCl4Ww99A',
  account_id,
  {
    scope: [
      `object.read.account.${account_id}.name`,      
      "object.read.c_messages.*.c_subject"
    ],
    skipAcl: true,
    grant: consts.accessLevels.read
  }
)
'use strict';
const request = require('request'),
      token = 'eyJhCiJSUz...c4DiCkVvNA',
      options = {
        url: 'https://api.dev.medable.com/example/v2/c_messages',
        headers: {
          Authorization: `Bearer ${token}`
        }
      };

request(options, function(error, response) {
    // ...
});

Last updated