SSO with ThingWorx 9.3 and Azure Active Directory WITHOUT Ping Federate
I am currently trying to configure SSO with Azure AD in ThingWorx 9.3 without Ping Federate. Support for this was one of the new features introduced with version 9.2
I am following the guide provided by PTC here https://support.ptc.com/help/identity_and_access_management/en/index.html#page/iam/AzureADasCASandIdP.html
But I was not successful. I am ending up with error messages in security log telling me that the SAML response is invalid.
This comes from AuthLog.log:
2022-03-01 09:01:28.160+0100 [L: INFO] [O: o.s.s.s.l.SAMLDefaultLogger] [I: ] [U: ] [S: ] [P: ] [T: https-openssl-nio-443-exec-9] AuthNResponse;FAILURE;10.2.5.4;https://server.domain.com/Thingworx;https://sts.windows.net/8c3fd900-8b7b-45cb-b2a0-aea95fa20530/;;;org.opensaml.common.SAMLException: Response doesn't have any valid assertion which would pass subject validation__ at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:265)__ at com.ptc.eauth.identity.saml2.PTCWebSSOProfileConsumerImpl.processAuthenticationResponse(PTCWebSSOProfileConsumerImpl.java:25)__ at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:88)__ at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)__ at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:92)__ at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)__ at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)__ at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)__ at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)__ at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)__ at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)__ at org.springframework.security.saml.metadata.MetadataGeneratorFilter.doFilter(MetadataGeneratorFilter.java:87)__ at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)__ at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)__ at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)__ at com.thingworx.security.authentication.sso.ThingworxSSOAuthenticator.authenticate(ThingworxSSOAuthenticator.java:849)__ at com.thingworx.security.authentication.sso.ThingworxSSOAuthenticator.validateAuthenticationRequest(ThingworxSSOAuthenticator.java:1382)__ at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)__ at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)__ at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)__ at java.base/java.lang.reflect.Method.invoke(Method.java:566)__ at com.thingworx.security.authentication.AuthenticationUtilities.validateSSOAuthenticationRequest(AuthenticationUtilities.java:674)__ at com.thingworx.security.authentication.AuthenticationUtilities.validateAuthenticationRequest(AuthenticationUtilities.java:623)__ at com.thingworx.security.authentication.AuthenticationFilter.authenticate(AuthenticationFilter.java:488)__ at com.thingworx.security.authentication.AuthenticationFilter.doFilter(AuthenticationFilter.java:260)__ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)__ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)__ at com.thingworx.security.contenttype.ContentTypeFilter.doFilter(ContentTypeFilter.java:143)__ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)__ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)__ at com.thingworx.security.filter.ValidationFilter.doFilter(ValidationFilter.java:22)__ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)__ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)__ at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:176)__ at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:145)__ at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:92)__ at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:389)__ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)__ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)__ at com.thingworx.security.filter.ClickjackFilter.doFilter(ClickjackFilter.java:298)__ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)__ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)__ at com.thingworx.security.filter.HttpResponseHeadersFilter.doFilter(HttpResponseHeadersFilter.java:172)__ at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)__ at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)__ at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)__ at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)__ at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:667)__ at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)__ at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)__ at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)__ at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)__ at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)__ at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)__ at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)__ at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)__ at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)__ at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)__ at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)__ at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)__ at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)__ at java.base/java.lang.Thread.run(Thread.java:829)__Caused by: org.springframework.security.authentication.InsufficientAuthenticationException: Response doesn't contain any of the requested authentication context class or declaration references__ at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAuthnContext(WebSSOProfileConsumerImpl.java:638)__ at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAuthenticationStatement(WebSSOProfileConsumerImpl.java:583)__ at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAssertion(WebSSOProfileConsumerImpl.java:342)__ at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:250)__ ... 61 more__#
What I could not find out is how the regular expression in "validation.properties" mentioned here should look like for correctly validating the SAML response for Azure AD.

The default REGEX is meant to work with Ping Federate:
Validator.HTTPParameterValue_SAMLResponse=^[a-zA-Z0-9+\/=]*$
Does anyone have a clue if this REGEX needs to be customized to fit for SAML responses from Azure AD? Maybe this helps me to get a working configuration.
Thank you.

