Scripted Inbound Security Module
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.
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.
This section describes the API used for the authenticate
function.
This object contains the credentials of the authenticating user. This object is of type AuthenticationRequest.
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.
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.
This section shows several examples of authentication scripts.
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;
}
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;
}