Implement storefront SSO for account-based shoppers

If your Commerce store is configured to support account-based commerce, storefront SSO lets you easily create business-account contacts for shoppers who have profiles in an external customer data store or identity management tool.

You can add authenticated shoppers to any active Commerce business account. See Configure Business Accounts for details about accounts, roles, and contacts.

You implement storefront SSO for account-based stores in much the same way as you would for stores that support only individual shoppers. This section includes only the information related to account-based stores. Before you read this section, make sure you have read all the sections that precede it in Implement Storefront Single Sign-On.

Note: The account-based commerce feature may not be enabled in your environment. Contact your Oracle account manager for more details on how to activate this functionality.

Understand how storefront SSO determines a shopper’s account and role

Commerce uses the account ID supplied during login to determine how to associate the authenticated shopper with an account and an account-based role.

  • If the shopper is a contact for the target account, and the account is active, the shopper is logged into the account with their assigned role.
  • If the shopper does not have a profile on your store and the target account is active, a new contact is created for the shopper in that account and the shopper is logged in. If a role is specified in the identity provider entity descriptors, the new contact is assigned that role. If no role is specified, the new contact is assigned the default Buyer role.
  • If the shopper is not a contact for the target account (or if that account is not active) but is a contact for one or more other active accounts, the shopper is denied access to the store.
  • If the shopper has a profile on your store but is not a contact for the target account or any other active account, the shopper is denied access to the store.
  • If your store supports both account-based shoppers and individual shoppers, the way you configure your store determines whether the shopper is logged in:
    • If the shopper does not have a profile on your store and you set the fallbackToB2cUserCreation property to true when you upload the identity provider entity descriptors, a new individual shopper profile is created. If fallbackToB2cUserCreation is set to false, the shopper is denied access to the store.
    • If your storefront includes SSO elements for both types of shoppers, when a shopper who does not have a profile on your store logs in with the regular (that is, not account-based) SSO link, Commerce creates a profile for a new individual shopper.

This shopper cannot later log into an account via the link for account-based SSO. If the original SSO login was accidental and the shopper needs access to an account, they must contact the merchant, who can either manually add the shopper as a contact in the target account or simply delete the existing shopper profile.

Important: If you remove a shopper from your external identity management system, you must also deactivate or remove their associated contact from the Commerce business account they log into with SSO. Leaving the contact active allows the shopper to continue accessing your store and account features by logging in with their Commerce login credentials.

Configure account-based storefront SSO

Setting up storefront SSO for an account-based store involves the following steps:

  1. Configure Commerce to use storefront SSO. See Configure Commerce storefront SSO settings for more information.
  2. Once you have configured SSO on your Commerce instance, you can return service provider entity descriptors. See Download the service provider entity descriptor from Commerce for more information.
  3. Upload the service provider entity descriptor to the identity provider, then download the corresponding identity provider entity descriptor. See Register the service providers with the identity provider for more information.
  4. Upload the identity provider entity descriptors to Commerce, and map assertion attributes to profile properties. This mapping enables automatic creation of shopper profiles in Commerce. See Upload the identity provider entity descriptor to Commerce for more information, and see Identity provider entity descriptors for account based stores for additional properties specific to account-based shoppers.
  5. Configure CORS to enable the identity provider to access Commerce resources. See Configure CORS for more information.
  6. Modify the storefront so that the links for logging in and accessing an account direct the shopper to either the storefront or the identity provider, as appropriate. See Modify login layouts for account based shoppers for more information.

Identity provider entity descriptors for account based stores

Use the PUT /ccadmin/v1/samlIdentityProviders/default endpoint to upload the identity provider entity descriptors to Commerce, and to map assertion attributes to profile properties. This mapping enables automatic creation of shopper profiles and contacts in Commerce.

In addition to the properties described in Upload the identity provider entity descriptors to Commerce, the request body includes the following properties specifically for account-based stores:

  • organizationAttributeName -- The identity provider attribute that stores the contact’s account ID.
  • roleAttributeName – The identity provider attribute that stores the contact’s role in the account specified by organizationAttributeName.
  • fallbackToB2cUserCreation – If true, Commerce creates an individual shopper profile if the contact logs in with invalid account credentials. See Create Page Layouts that Support Different Types of Shoppers for more information.

For example:

PUT /ccadmin/v1/samlIdentityProviders/default  HTTP/1.1
Authorization: Bearer <access_token>

{

  "loginAttributeName": "uid",
  "emailAttributeName": "email",
  "organizationAttributeName": "organizationId",
  "roleAttributeName": "Role",
  "fallbackToB2cUserCreation": true,
  "encodedIdpMetadata": "<identity provider entity descriptor>",
  "requiredAttributeToPropertyMap": {
    "uid": "login",
    "email": "email",
    "firstName": "firstName",
    "lastName": "lastName"
  },
  "optionalAttributeToPropertyMap": {
    "addressFirstName": "address.firstName",
    "addressLastName": "address.lastName",
    "address1": "address.address1",
    "postalCode": "address.postalCode",
    "city": "address.city",
    "country": "address.country",
    "state": "address.state"
  }
}

Modify login layouts for account based shoppers

This section describes a sample Header widget that lets account-based shoppers access SSO on your storefront. See Modify the storefront for overview information about updating the storefront to enable SSO.

In this sample, the out-of-the-box Header widget has been modified to include a customized version of the Login/Register element that includes an SSO login link. When the contact clicks the link, they see a login modal where they enter an account ID. If the ID matches

For details about how to create widgets, see Create a Widget.

The element template file provides the HTML rendering code for the element:

<div id="CC-header-sso-login" class="col-md-6">
   <a href="#CC-headermodalpane" id="CC-linkSsoLogin"
data-original-title="ssoLogin"
     data-bind="click: $parent.showSsoLoginSection.bind($parent),
       widgetLocaleText: 'ssoLoginLinkText',
       event: { mousedown: $parent.handleMouseDown.bind($parent, $parents[1]),
         mouseup: $parent.handleMouseUp.bind($parent, $parents[1])}">
   </a>
</div>

The following code is for the SSO pane that appears when the shopper clicks the SSO login link:

<!--Pane for SSO Login-->
   <div id="CC-ssoLoginPane">
    <div class="modal-header CC-header-modal-heading">
     <h3 class="modal-title"
id="CC-sso-login-text-title" data-bind="widgetLocaleText: 'ssoLoginText'"></h3>
     </div>
     <div class="modal-body cc-modal-body">
      <div id="CC-sso-login-section" data-bind="with: $parent.user">
      <div class="form-group row">
        <div class="controls col-md-12">
          <label class="control-label inline" for="CC-sso-login-account-input"
                 data-bind="widgetLocaleText:'accountIdText'">
          </label>
          <input type="email" class="col-md-5 form-control"
id="CC-sso-login-account-input" aria-required="true"
                 data-bind="validatableValue: ssoLoginAccountName,
                   widgetLocaleText : { value:'accountIdText',
attr:'placeholder' }"/>
        </div>
      </div>
    </div>
   </div>
  <div class="modal-footer CC-header-modal-footer">
     <div class="center-block">
      <button type="button" id="CC-sso-login"
class="cc-button-primary" data-bind="widgetLocaleText: 'buttonLogin',
click: function(data, event) { doSsoLogin.bind($data, $parent, event)() },
event: { mousedown: handleMouseDown.bind($data, $parent),
mouseup: handleMouseUp.bind($data, $parent) }"></button>
      <button type="button" id="CC-sso-login-cancel"
class="cc-button-secondary" data-dismiss="modal"
data-bind="widgetLocaleText: 'buttonCancel', click: function(data, event)
{ handleCancelSsoLogin.bind(data, $parent, event)() },
event: { mousedown: handleMouseDown.bind($data, $parent),
mouseup: handleMouseUp.bind($data, $parent) }"></button>
    </div>
   </div>
</div>

The element’s JavaScript file includes a click handler for cancelling the SSO login modal.

/**
     * Click handler to cancel the SSO login modal.
     * @param data Data that is passed on the click event.
     * @param event jQuery event of the click event on the cancel button.
     */
    handleCancelSsoLogin: function(data, event) {
      if('click' === event.type ||
(('keydown' === event.type ||
 'keypress' === event.type) && event.keyCode === 13)) {
        notifier.clearError(this.WIDGET_ID);
        navigation.doLogin(navigation.getPath(), data.links().home.route);
      }
      return true;
    },

The following sample is an event handler for the log in with SSO link.

    /**
     * Event handler for the Log In With SSO link on the B2B login modal.
     * It shows the SSO login modal.
     * @param data Data passed when the link is clicked.
     */
    showSsoLoginSection: function(data) {
      this.hideAllSections();
      $('#CC-ssoLoginPane').show();
      $('#CC-sso-login-account-input').focus();
      data.ssoLoginAccountName('');
    },

The following sample is the click event handler for the Login button on the SSO Login modal.

    /**
     * Click event handler for the Login button in SSO Login modal.
     * @param data Data passed when the login button is clicked.
     * @param event jQuery event for the click event.
     */
    doSsoLogin: function(data, event) {
      if ('click' === event.type ||
(('keydown' === event.type || 'keypress' === event.type)
&& event.keyCode === 13)) {
        data.user().handleSamlLogin();
      }
      return true;
    },

    createOrganizationRequestSuccess: function(){
      this.hideAllSections();
      $('#CC-headermodalpane').children(".modal-dialog").css('top', '20%');
      $('#CC-organizationRequestSuccessPane').show();
    },

    createOrganizationRequestFailure: function(pResponse){
      this.modalMessageText(pResponse.message);
      this.showErrorMessage(true);
    },

During a SAML login, the ssoLoginAccountName variable is sent automatically as the relay_state. This variable is used for organization validation. If the relay_state is not passed in, no validation occurs when the customer logs into their parent organization. However, if the relay_state is passed in, the system validates that the customer has access to the account.

This is the event handler for the login with SSO link on the login modal.

    /**
     * Event handler for the Log In With SSO link on the B2B login modal.
     * It shows the SSO login modal.
     * @param data Data passed when the link is clicked.
     */
    showSsoLoginSection: function(data) {
      this.hideAllSections();
      $('#CC-ssoLoginPane').show();
      $('#CC-sso-login-account-input').focus();
      data.ssoLoginAccountName('');
    },