Skip to the content.

Integrate MSTR with Okta via SAML

MicroStrategy’s directions for enabling single sign-on with SAML are actually pretty good. MSTR bundles Spring Security with SAML support and provides directions for how to enable it by editing web.xml.

On the Okta side, a few things that were non-obvious. First, configuring a SAML application in Okta requires going switching to the “classic” UI, which you might miss if you’re reading the directions too quickly.

Second, Okta does not give you a way to upload SP metadata. So I had to copy the fields from the MSTR-generated SP metadata into the Okta form fields:

  • “Single sign on URL” in Okta should come from the AssertionConsumerService in the SP metadata. This will be a URL ending in /saml/SSO.
  • “Audience URI” in Okta will come from the entity ID in the SP metadata and will look like the base URL of your application.
  • Under “advanced settings” the signature algorithm should be set to “RSA-SHA256” to match MstrSamlConfig.xml

Okta does give you IdP metadata that you can paste into the IDPMetadata.xml, which will include the Okta IdP URLs and the certificate containing the public key to validate the SAML response signature.

Admin screens

Another important thing to remember: MSTR SAML integration will take over all authentication including admin screens (/mstrWebAdmin). All requests are intercepted by Spring Security before they get to MSTR. This means

  • your MSTR Web admin users need to be full-fledged Okta users and authenticate through Okta rather than tomcat-users.xml (this is on the whole, an improvement in terms of security)

  • MSTR Web admins need to belong to a group called “MSTRAdmin” in Okta and you need to add the same group name to the adminGroups element in MstrSamlConfig.xml

  • Additionally, you need to make sure Okta exposes the user groups under “group attribute statements”, and you need to edit the groupAttributeName element in MstrSamlConfig.xml to match the attribute coming from Okta.

These points about configuring groups were not so well documented.

Customization

I found that I could continue to use ESM modules with SAML to inject custom behavior at login, such as creating or updating MSTR users on demand – for example, changing group memberships dynamically. I overloaded handlesAuthenticationRequest to inject my custom behavior, and return super.handlesAuthenticationRequest at the end. The default security handler will create the session properly based on the SSO user; MSTR’s handlers registered with Spring Security (SAMLProcessingFilterWrapper) will set the SSO user in the HTTP session for MSTR’s default trusted auth handlers to see.

I also experimented with dynamically changing the IdP URL in Okta to delegate authentication to a separate third-party IdP (federated SSO) instead of Okta handling the username and password. Effectively what I was trying to do was replace the URL in the IDPMetadata.xml at runtime with a slightly different URL - pre-pending /sso/saml2/:idpId into the Okta URL before /app/:app-location/:appId/sso/saml

In order to do this, I made a subclass of WebSSOProfileImpl and overloaded sendMessage to look at the HTTP request parameters and conditionally replace the Endpoint with a wrapped object that replaces the URL specified in the IdP metadata. There is probably a better way to do this, though.

public class WebSSOProfile extends org.springframework.security.saml.websso.WebSSOProfileImpl {
       
       @Override
       protected void sendMessage(SAMLMessageContext context, boolean sign)
                     throws MetadataProviderException, SAMLException, MessageEncodingException {
              String param = ((HttpServletRequestAdapter)context.getInboundMessageTransport()).getParameterValue("foo");
              final Endpoint endp = context.getPeerEntityEndpoint();
                           
              if (param != null && param.equals("bar")) {
                     String newUrl = endp.getLocation().replace("/app", "/sso/saml2/my_idp_id/app");
                     context.setPeerEntityEndpoint(new EndpointWrapper(endp, newUrl));    
              }
              
              super.sendMessage(context, sign);
       }
       
       private static class EndpointWrapper implements Endpoint {
              private final Endpoint delegate;
              private final String overrideLocation;
              
              public EndpointWrapper(Endpoint delegate, String overrideLocation) {
                     this.delegate = delegate;
                     this.overrideLocation = overrideLocation;
              }
              public String getLocation() {
                     return this.overrideLocation;
              }
                // ... more delegate methods ...
        }
}
Written on April 24, 2018