15.6.1Scripted Inbound Security

 

The Scripted Inbound Security module uses a JavaScript script to perform user authentication.

With this module, the script receives credentials (a username and password), and validates them. If the credentials are not valid, the script returns an error and the user is not permitted to authenticate.

If the credentials are valid, the script returns information about the user logging in.

15.6.2Script Format

 

Authentication scripts used by this module must implement a JavaScript function called authenticate. Scripts may take advantage of the full Smile CDR JavaScript Execution Environment.

15.6.2.1Method: authenticate

This section describes the API used for the authenticate function.

15.6.2.1.1Input Parameter: theRequest

This object contains the credentials of the authenticating user. This object is of type AuthenticationRequest.

15.6.2.1.2Input Parameter: theOutcomeFactory

This object is used as a factory for the return value that is returned from the authenticate function. It creates a success or failure object. This object is of type ScriptAuthenticationOutcomeFactory.

See returning failure for information on how to abort a login.

15.6.2.2HTTP Header Access

A user-supplied, comma-delimited allowed list of HTTP headers can be accessed through theRequest by using syntax such as theRequest.headers['foo'], where the foo header is desired and permitted. Note that keys in this headers map are case-sensitive but are standardized to lower-case for consistency.

15.6.3Script Examples

 

This section shows several examples of authentication scripts.

15.6.3.1Simple Hard-Coded Example

The following example shows a simple authentication function that uses a hardcoded username and password defined in the script. This is only useful for basic testing, and would generally not be considered secure for any real scenarios.

/*
 * This is an authentication script for the script-inbound-security
 * module. It is required to contain a function called "authenticate".
 *
 * This function takes two parameters:
 *   - theRequest: This object contains the incoming credentials.
 *   - theOutcomeFactory: This is a factory for the success/failure responses
 *              to be returned by this function.
 */
function authenticate(theRequest, theOutcomeFactory) {

   // Create a log entry
   var ip = theRequest.remoteAddress;
   Log.info("User " + theRequest.username + " is authenticating from " + ip);

   /*
    * If the username is "myadmin" and the password is "password123"
    *
    * This is an admin user with a hardcoded username and password.
    * In a real system you would probably want to do something more
    * secure and scalable, but this is a good first introduction.
    */
   if (theRequest.username == 'myadmin') {
      if (theRequest.password == 'password123') {

         // Create a successful login response
         var success = theOutcomeFactory.newSuccess();
         success.username = 'myadmin';
         success.familyName = 'Smith';
         success.givenName = 'John';

         // Grant admin user full authority
         success.addAuthority('ROLE_SUPERUSER');
         return success;

      }
   }

   // Create a failing response if we haven't successfully
   // authenticated
   var failure = theOutcomeFactory.newFailure();
   failure.message = 'Bad password!';
   failure.incorrectPassword = true;
   return failure;

}

15.6.3.2Calling an External REST Authentication Service

The following example shows a script that authenticates against an external authentication service. This example uses the Okta API for Primary authentication with trusted application to validate credentials, and it treats the logging-in username as a Patient ID for a Patient user who is logging in.

/*
 * This is an authentication script for the script-inbound-security
 * module. It is required to contain a function called "authenticate".
 *
 * This function takes two parameters:
 *   - theRequest: This object contains the incoming credentials.
 *   - theOutcomeFactory: This is a factory for the success/failure responses
 *              to be returned by this function.
 */
function authenticate(theRequest, theOutcomeFactory) {

   /*
    * Authenticate using Okta
    */
    var username = theRequest.username;
    var password = theRequest.password;

	// Prepare an HTTP POST (JSON payload)
	var post = Http.post('http://localhost:30000/api/v1/authn');
	var authRequest = new Object();
	authRequest.username = username;
	authRequest.password = password;
	post.setContentJson(authRequest);

	// Invoke the auth service
	post.execute();

    // If this method returns false, we either had a connection
    // failure, or a non-200 HTTP response
	if (!post.isSuccess()) {
	    var failure = theOutcomeFactory.newFailure();
	    failure.message = post.getFailureMessage();
	    return failure;
	}

	// Parse the response
	var responseJson = post.parseResponseAsJson();
	if (responseJson.status == 'SUCCESS') {

		// Create a success login
		var success = theOutcomeFactory.newSuccess();
		success.username = responseJson._embedded.user.profile.login;
		success.familyName = responseJson._embedded.user.profile.lastName;
		success.givenName = responseJson._embedded.user.profile.firstName;

		// We'll treat the username as the patient scope in this
		// example
		var patientId = success.username;

		// Set the current patient ID as the launch context so that
		// the "launch/patient" scope can be handled correctly by
		// the auth server
		success.addLaunchResourceId('patient', patientId);

		// Grant the user any authorities they should be allowed to
		// have. In this case, we're granting READ ONLY access to
		// anything in the Patient's own compartment.
		success.addAuthority('FHIR_READ_ALL_IN_COMPARTMENT', 'Patient/' + patientId);

		// Return the successful authentication
		return success;
	}

	// Fail by default
    var failure = theOutcomeFactory.newFailure();
    failure.message = 'Bad username!';
    failure.incorrectUsername = true;
    return failure;
}