Installation and Upgrade > Advanced Deployment Considerations > Authentication > Configuring an Alternative Authentication in Windchill > Single Sign-on Authentication > Configure OAuth Delegated Authorization
  
Configure OAuth Delegated Authorization
Windchill can participate in a single sign-on (SSO) as a resource provider to applications and mashups built on the ThingWorx platform. In this use case, a user can sign in to an SSO-enabled ThingWorx-based application and authorize the application they are interfacing with to access their Windchill data. This implementation of SSO is known as delegated authorization because the user authorizes the service provider (application) to act on their behalf to retrieve their information from the resource provider (Windchill), and the user needs to log in once. For a full description of supported SSO use cases and the configuration steps required for setting up a PTC product SSO federation, refer to the PTC Product Platform Single Sign-on Guide.
The PTC product platform SSO solution relies on the exchange of access tokens based on the OAuth standard to manage delegated authorization between product applications. Two Windchill files, securityContext.properties file and Web.xml, must be edited to enable OAuth in Windchill.
Configurations Common to any OAuth Delegated Authorization
Update the property wt.oauth2.token.userNameAttribute value to the user attribute name that is specified in the IDP configuration. For example, if pingfederate is the IDP, then the value is Username.
Update the property wt.oauth2.token.tokenType to switch the token validation between OPAQUE and JWT. ThedDefault value is OPAQUE.
Update the property wt.oauth2.token.scopeAttribute to configure the scope attribute for both OPAQUE and JWT token. The default value is scope.
OAuth Delegated Authorization Configuration for an OPAQUE Token
To configure a connection to the central authorization server and specify what system resources are protected by access tokens and scopes, edit the securityContext.properties file as described below. The path to the property file is <Windchill>\codebase\WEB-INF\security\config\securityContext.properties.
1. Identify what resources should be protected by OAuth 2 tokens. Protected resources will require a valid OAuth 2 token from a service provider for access to be granted to it. To do this, list the resources in an ant-style pattern after the following string: com.ptc.eauth.identity.oauth2.rs=/oauth/**
For example, to protect Windchill documents, the string would be configured like this:
com.ptc.eauth.identity.oauth2.rs=/oauth/documents/**
Only a single pattern, that is, only one line can be specified in the properties file. Therefore, you need to define a top-level URL that sufficiently encompasses all of the resources that you want protected by OAuth 2 tokens. In step 4 you will specify the scopes that should be attached to the OAuth tokens, which provides additional granularity for controlling access to the resource.
2. Identify the endpoint for the token validation. This value should be set to the CAS server that will be validating access tokens.
org.springframework.security.oauth2.provider.token.RemoteTokenServices.checkTokenEndpointUrl=https://{cas-server}
.ptc.com:9031/as/introspect.oauth2
3. Provide the client ID and client secret specified in the PingFederate (CAS) OAuth client that was created for a resource provider. Contact your CAS administrator for information about the OAuth client for Windchill.
org.springframework.security.oauth2.provider.token.RemoteTokenServices.clientId=rs_client
org.springframework.security.oauth2.provider.token.RemoteTokenServices.clientSecret={client secret}
4. Add at least one property that pairs the WINDCHILL_READ scope with the Windchill resources that you want made available to a service provider.
To configure the resources that are exposed by the WINDCHILL_READ scope, you must add properties defining this access to the securityContext.properties file. In Windchill, configuring a single scope is supported. Testing was conducted with a scope named WINDCHILL_READ. Scopes that are registered in this file must also be registered in the central authorization server that manages the exchange of access tokens in your SSO network, and in the service provider application that will be requesting the data.
Each scope property is composed of the following parts
A URL prefix that indicates the resource protected by OAuth and allows access if a valid access token is presented. For example:
com.ptc.eauth.identity.oauth2.rs.InMemoryResourceScopeService.resourceScopes.
A directory path to the resources that will be exposed. For example, /Windchill/oauth/documents.
An Ant-style path pattern. For example, /** exposes resources from all directories under the path provided before the task.
The scope value (name). For example, =WINDCHILL_READ. Scope values cannot contain spaces. Spaces are used to separate multiple scope values; however, multiple scope scenarios are not currently supported.
The following is a sample scope property to require an OAuth access token to have the WINDCHILL_READ scope to permit passing data for /Windchill/oauth/documents resources
com.ptc.eauth.identity.oauth2.rs.InMemoryResourceScopeService.resourceScopes.
/Windchill/oauth/documents=WINDCHILL_READ
Incoming requests must have an access token that has the required scopes that are defined in properties for a given resource. These properties are additive, meaning if you define additional scopes for the same resource, then incoming requests for the protected data must contain all of the scopes that have been defined for that resource. For example, if you create a second property in addition to the example above that requires the scope “ABC” for /Windchill/oauth/documents resources, then an incoming access token would need to have the scopes “WINDCHILL_READ” and “ABC” to gain access to the resource.
OAuth Authorization Configuration for JWT Token
For configuring OAuth authorization for Azure Active Directory using JWT token, follow the steps mentioned here:
1. Add the following properties in wt.properties file through site.xconf:
wt.jwt.oauth2.token.issueAtTime
Specify the time at which the JWT access token was issued. This can be used to determine the age of the JWT access token. The default value is 900000 millisecond/15min.
wt.jwt.oauth2.token.azure.tenantId
Specify the Tenant ID that is a globally unique identifier (GUID) and is other than your organization name or domain.
wt.jwt.oauth2.token.audience
Specify the client ID.
wt.jwt.oauth2.token.tokenIssuer
Specify the value as — https://sts.windows.net/<tenant_id>/.
wt.jwt.oauth2.token.algorithm
Specify the algorithm used to validate signature of JWT access token. The default value is RS256.
wt.jwt.oauth2.token.kidUrl
Specify the value as — https://login.microsoftonline.com/<tenant_id>/discovery/v2.0/keys.
2. Update the property wt.jwt.oauth2.token.jwtIdpType value in securityContext.properties file to the supported third-party IDP identifier that will be sending the JWT token for example wt.jwt.oauth2.token.jwtIdpType = azure. You may choose not to provide any value for this property if Azure special validations are not required. Only standard JWT claims validation is done in this case.
Editing the Web.xml file
Perform two edits to the Web.xml file:
1. Add the location of the securityContext.properties file to the WEB-INF/web.xml file.
The WEB-INF/web.xml file must reference the securityContext.properties file. The Web.xml file is located at <Windchill install location>/Codebase/WEB-INF/Web.xml.
Add the location to the parameter value for the location of the Spring root web application context. The following example is valid if you have kept the securityContext.properties file saved in its default directory. Add the path WEB-INF/security/config/securityContext.xml to <param-value>config/mvc/applicationContext.xml</param-value>. The structure of the context parameter should be the following:
<context-param>
<description>Location of Spring root web application context</description>
<param-name>contextConfigLocation</param-name>
<param-value>config/mvc/applicationContext.xml
WEB-INF/security/config/securityContext.xml
</param-value>
</context-param>
* 
The location of the securityContext.properties file is also referenced in securityContext.xml. If you changed the directory location of securityContext.properties, then you must update the references to the new location in Web.xml and securityContext.xml.
2. Add a SpringSecurityFilterChain code snippet.
Locate the first <filter-mapping> tag within the Web.xml file and add the following code before that tag:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/oauth/*</url-pattern>
</filter-mapping>
Locate the first <servlet-mapping> tag within the Web.xml file, and add the following code before that tag:
<servlet>
<description>Bridge Servlet for Oauth Access</description>
<servlet-name>OauthAuthBridgeServlet</servlet-name>
<servlet-class>wt.servlet.AuthBridgeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>OauthAuthBridgeServlet</servlet-name>
<url-pattern>/oauth/*</url-pattern>
</servlet-mapping>
Restart Windchill and Apache Server
After editing the securityContext.properties and Web.xml files, restart the Windchill and Apache servers and test to verify that Windchill participates in delegating the authorization with your SSO network.
Custom UserAuthenticationConverter Interface
Depending on the response from the introspection end point, you are required to implement UserAuthenticationConverter to convert and extract user information to be used in an access token. For example:
If the response format is a nested user name claim as shown below, use com.ptc.eauth.identity.oauth2.rs.PingFederateUserAuthenticationConverter in securityContext.xml file.
{
"access_token":
{
"USERNAME":"wcadmin" }
"scope":"WINDCHILL_READ"
"token_type":"urn:pingidentity.com:oauth2:validated_token",
"expires_in";5289,
"client_id";"TestJWTClient",
}
If the response format is a flat structure as below, use OOTB com.ptc.eauth.identity.oauth2.rs.IntrospectionUserAuthenticationConverter, no update is required in the file.
{ "aud":"TEST_AUD",
"scope":"WINDCHILL_READ",
"iss":"TEST_ISS",
"active":true,
"USERNAME":"wcadmin",
"token_type":"Bearer",
"exp":1640609559,
"client_id":"TestJWTClient"
}
For any other format, follow the guidelines from the above examples to read the username claims appropriately from the response. You are required to change the code in extractAuthentication method to extract the proper claim details. Custom UserAuthenticationConverter must be updated in <WT_HOME>/codebase/WEB-INF/security/config/ securityContext.xml file at:

<!--Customize this depending on the exact nature of the Authorization server's "check token" endpoint -->
<bean_id="userTokenConverter" class="com.ptc.eauth.identity.oauth2.rs.PingFederateUserAuthenticationConverter">
<property name="userNameAttribute" value="${wt.oauth2.token.userNameAttribute}"/>
</bean>
Use the sample code below:
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class IntrospectionUserAuthenticationConverter implements UserAuthenticationConverter {
public static final String DEFAULT_USERNAME_ATTR = "username";
private String userNameAttribute = DEFAULT_USERNAME_ATTR;
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication) { Map<String, String> map = new HashMap<>(); map.put(this.userNameAttribute, userAuthentication.getName()); return map; }
/** * This method extracts user defined claim name from the token * @param map - token returned from introspection end point * @return - user details extracted from token */
public Authentication extractAuthentication(Map<String, ?> map){
Object principal = null;
for (String key : map.keySet()) {
if (key.equalsIgnoreCase(this.userNameAttribute)) { principal = map.get(key);
break;
}
}
if (principal != null)
{
PreAuthenticatedAuthenticationToken response = new PreAuthenticatedAuthenticationToken(principal, null);
response.setDetails(map);
return (Authentication)response; }
return null; }
public void setUserNameAttribute(String userNameAttribute) {
this.userNameAttribute = userNameAttribute;
}
}