DocumentationRelease Notes
Log In
Documentation

Okta SCIM provisioning

With AWS Lambda Function

[email protected]

Why introducing AWS Lambda function

The main reason the integration is leveraging a Lamdba function is to overcome the lack of support in Okta for OAuth client credentials.

Source:

https://developer.okta.com/docs/concepts/scim/faqs/

Q: How should I be managing authentication to my SCIM API?

Okta recommends using the OAuth 2.0 Authorization Code grant flow. Okta doesn't support the Client Credentials or Resource Owner Password Credentials Authorization grant flows for SCIM. The Authorization Code grant flow is more common in SaaS and cloud integrations and is also more secure.

Comment: Okta is stating that Authorization Code grant flow is more secure, but it means that when Okta SCIM App needs to authenticate to Password Safe, it would actually be Password Safe that would have to authenticate back to Okta, using Client ID and Client Secret generated by Okta. This translate into PAM trusting the Directory, when it should be the other around, from a Security Best Practices point of view.

Another reason is to enable the support for Okta Group Push.  Password Safe requires a description attribute at creation time, and Okta only provides a displayName attribute, which makes the Okta Push provisioning request to fail, being an invalid request missing the mandatory attribute description.

⚠️

Important

Third-party documentation is subject to change. Updates might not be reflected in BeyondTrust documentation. For the most up-to-date information, visit https://docs.aws.amazon.com/lambda/latest/dg/welcome.html.

AWS Lambda function

Prerequisites and configuration

import axios from 'axios';

export const handler = async (event) => {
  // ####### Extract host and endpoint from Request Url ####################
  console.log('event INCOMING =  '+JSON.stringify(event));
  const method = event.requestContext.http.method;
  console.log('event method = '+method);
  const endpoint = 'https:/'+event.rawPath;
  let url = '';
  if(event.rawQueryString){
    url = endpoint+'?'+event.rawQueryString;
  } else{
    url = endpoint;
  }
  var host = event.rawPath.substring(1);
  host = host.substring(0,host.indexOf('/'));
  console.log('host = '+host+'   endpoint = '+endpoint);
  
  // ####### Extract values from base64 token ####################
  const base64token = event.headers.authorization.substring(7);
  const base64payload = Buffer.from(base64token, 'base64').toString('ascii'); 
  console.log('base64token = '+base64token+'.  base64payload = '+base64payload);
  const base64obj = JSON.parse(base64payload);
  const authUrl = base64obj.authURL;
  const client_id = base64obj.client_id;
  const client_secret = base64obj.client_secret;
  const target = base64obj.target;
  console.log('target = '+target+'  authUrl =  '+authUrl+'. client_id = '+client_id+'   client_secret = '+client_secret);
  // Change Header host value for gateway
  event.headers.host = host;
  
  // ####### Authenticate ####################
    const data = 'grant_type=client_credentials';
    const authorization = 'Basic ' + Buffer.from(client_id+':'+client_secret).toString('base64');
  const authConfig = {
        method: 'post',
        rejectUnauthorized: false,
        url: authUrl,
        data: data,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };
  console.log('authConfig = '+JSON.stringify(authConfig));
    let authRes = await axios(authConfig);
  console.log('authRes = '+JSON.stringify(authRes.data));
  const access_token = authRes.data.access_token;
  
  // ####### GET/POST/PATCH/PUT/DELETE data from target #########
If (target == "PasswordSafe" && endpoint == 'https://'+host+'/scim/v2/Groups' && method == 'POST'){
    console.log('We have a Password Safe Create Group call body : '+JSON.stringify(event.body));
  const body = JSON.parse(event.body);
  const configG = {
    method: method,
    url: url,
    headers: {
      'content-type': event.headers['content-type'],
      accept: event.headers.accept,
      authorization: 'Bearer '+access_token,
    },
    "data": {
        "schemas": [
            "urn:ietf:params:scim:schemas:core:2.0:Group"
        ],
        "displayName": body.displayName,
        "description": body.displayName,
        "members": []
    }
  };
  console.log('Password Safe Create Group: configG = '+JSON.stringify(configG));
      let resG = await axios(configG);

    // Response - For testing only
    resG.headers['content-type'] = 'application/scim+json';
    console.log('axios res data  = '+JSON.stringify(resG.data));
    console.log('axios res headers = '+JSON.stringify(resG.headers));
    const response = {
    statusCode: 200,
    body: JSON.stringify(resG.data),
    headers: resG.headers
    };
    return response;

  } else{
  if(event.headers['content-type']) {'Content-Type =  '+console.log(event.headers['content-type']);}
  let config = {};
  if(event.body) {
    console.log('body = '+JSON.stringify(event.body));
  config = {
    method: method,
    url: url,
    headers: {
      'content-type': event.headers['content-type'],
      accept: event.headers.accept,
      authorization: 'Bearer '+access_token,
    },
      data: JSON.parse(event.body)
  };
  } else {
  config = {
    method: method,
    url: url,
    headers: {
        'Accept': 'application/json',
        'Authorization': 'Bearer '+access_token
    }
  };
  }
    console.log('axios config = '+JSON.stringify(config));
      let res = await axios(config);

    // Response – For Okta
    res.headers['content-type'] = 'application/scim+json';
    console.log('axios res data  = '+JSON.stringify(res.data));
    console.log('axios res headers = '+JSON.stringify(res.headers));
    const response = {
    statusCode: 200,
    body: JSON.stringify(res.data),
    headers: res.headers
    };
    return response;
  }
};

AWS Lambda function

The Lambda function code includes multiple console.log() items to facilitate troubleshooting via AWS CloudWatch.

ℹ️

Note

You must configure a Function Url with authentication set to NONE.

Before configuring Okta SCIM

Before configuration Okta SCIM App

SCIM 2.0 Base Url and OAuth Bearer Token

The SCIM 2.0 Base Url is the Function URL with the Password Safe Url (minus https://) appended to it, for example:

https://7zevosnxabcdwul77yt1234567a.lambda-url.us-east-1.on.aws/pws\_instance.ps.beyondtrustcloud.com/scim/v2

  1. We need to create a SCIM Service Account for Okta in Password Safe, and add it to a group with the following Features and Smart Group permissions:
  2. Read-only permissions to all Managed Account Smart Groups is required.

ℹ️

Note

Only the Managed Account Smart Group with also the Category defined as Managed Account are visible via SCIM:

To create the Bearer Token for Okta, we must first create a JSON document:

{"target":"PasswordSafe","authURL":"https://pws_instance.ps.beyondtrustcloud.com/scim/oauth/token","client_id":"b0d00123456abcde4a7611b9","client_secret":"63eee16d6b3456789012vfdes1bc5e6"}

Then we need to create a base64 encoded version, e.g. by using https://base64encode.org:

eyJ0YXJnZXQiOiJQYXNzd29yZFNhZmUiLCJhdXRoVVJMIjoiaHR0cHM6Ly9wd3NfaW5zdGFuY2UucHMuYmV5b25kdHJ1c3RjbG91ZC5jb20vc2NpbS9vYXV0aC90b2tlbiIsImNsaWVudF9pZCI6ImIwZDAwMTIzNDU2YWJjZGU0YTc2MTFiOSIsImNsaWVudF9zZWNyZXQiOiI2M2VlZTE2ZDZiMzQ1Njc4OTAxMnZmZGVzMWJjNWU2In0=

Configure Okta SCIM app

SCIM 2.0 Base Url and OAuth Bearer Token

  1. Under Applications, select Browse Catalog, and select SCIM 2.0 Test App (OAuth Bearer Token).
  2. Under Provisioning/Integration, provide the URL and token.
  3. Under Settings/To App, check all the boxes and select the Password Sync option.

You will have to modify the Password Policy under Security|Authentication to match the Password Safe password policy for Users:

  1. Under Sign On, select Email prefix for Username.
  2. You may need to modify the Profile Mapping for the App.

Supported use cases

Import and create in Okta.

  1. Push group.
  2. Assign Okta Users (Create in Password Safe).
  3. Import Password Safe Groups and add/remove members that exist on both sides.

©2003-2025 BeyondTrust Corporation. All Rights Reserved. Other trademarks identified on this page are owned by their respective owners. BeyondTrust is not a chartered bank or trust company, or depository institution. It is not authorized to accept deposits or trust accounts and is not licensed or regulated by any state or federal banking authority.