Skip to main content

authentication-xsuaa

Invoke this skill to set up XSUAA authentication and Application Router. Detects <auth-method>FORM</auth-method> in web.xml, security-constraint definitions, or UserProvider usage in Java code. Replaces Neo built-in auth with CF XSUAA.

Source facts

Repository
SAP-samples/btp-neo-java-app-migration
Last source activity
July 29, 2026 at 12:47
Detected SKILL.md language
English
Stars
6
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
authentication-xsuaa
description
Invoke this skill to set up XSUAA authentication and Application Router. Detects <auth-method>FORM</auth-method> in web.xml, security-constraint definitions, or UserProvider usage in Java code. Replaces Neo built-in auth with CF XSUAA.
disable-model-invocation
false
allowed-tools
Read, Edit, Write, Bash, Grep, Glob
# Authentication and Authorization Set up XSUAA-based authentication and Application Router for Cloud Foundry. ## Purpose Replace Neo's built-in FORM authentication and `UserManagementAccessor` with Cloud Foundry's XSUAA service and Application Router for secure web application access. ## Detection This skill applies if any of these patterns are found: ### In web.xml ```xml <auth-method>FORM</auth-method> <!-- OR --> <security-constraint> <web-resource-collection> <web-resource-name>Protected</web-resource-name> <url-pattern>/protected/*</url-pattern> </web-resource-collection> </security-constraint> <!-- OR --> <security-role> <role-name>Everyone</role-name> </security-role> ``` ### In Java source files ```java import com.sap.security.um.user.UserProvider; import com.sap.security.um.user.User; // OR request.getUserPrincipal(); request.isUserInRole("Everyone"); ``` ## Prerequisites > **Working directory:** This skill must run inside the `-cf-migration` copy of your app, created by `jakarta-java25-migration` or `neo-to-cf-migration-orchestrator`. If your current directory does not end in `-cf-migration`, switch to it before proceeding. Before invoking this skill, ensure you have invoked: 1. **sdk-replacement** - `Use the sdk-replacement skill` - Sets up SAP Cloud SDK - REQUIRED before this skill ## Transformation Steps ### Step 1: Create xs-security.json Create `xs-security.json` in your project root (or `cf/` folder) - see [assets/xs-security.json](assets/xs-security.json) for template: ```json { "xsappname": "${app-name}", "tenant-mode": "dedicated", "scopes": [ { "name": "$XSAPPNAME.Everyone", "description": "Everyone scope for authenticated users" } ], "role-templates": [ { "name": "Everyone", "scope-references": [ "$XSAPPNAME.Everyone" ] } ], "role-collections": [ { "name": "${app-name}-Everyone", "role-template-references": [ "$XSAPPNAME.Everyone" ] } ] } ``` > **Customize:** Replace `${app-name}` with your application name and `Everyone` with each actual role name from your Neo app. The naming convention for role collections is `<app-name>-<role-name>` (e.g. `myapp-Admin`, `myapp-Viewer`). Add one entry per role-template in the `role-collections` array. `subaccount-roles-import` reads these names to link deployed role-templates and assign users. ### Step 2: Update web.xml Authentication Method **Before:** ```xml <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.html</form-login-page> <form-error-page>/login-error.html</form-error-page> </form-login-config> </login-config> ``` **After:** ```xml <login-config> <auth-method>XSUAA</auth-method> </login-config> ``` ### Step 3: Add Security Library Dependency Add to `pom.xml`: ```xml <!-- SAP Cloud Security Java API (non-Spring applications) --> <dependency> <groupId>com.sap.cloud.security</groupId> <artifactId>java-api</artifactId> </dependency> <!-- For Spring Boot applications, use instead: <dependency> <groupId>com.sap.cloud.security</groupId> <artifactId>resourceserver-security-spring-boot-starter</artifactId> </dependency> --> ``` > **Note:** The `java-api` artifact is managed by the `cf-tomcat-bom` BOM, so no version is needed. ### Step 4: Create Application Router Create `approuter/` directory with these files: #### approuter/package.json Copy from [assets/package.json](assets/package.json): ```json { "name": "approuter", "dependencies": { "@sap/approuter": "^16.0.0" }, "scripts": { "start": "node node_modules/@sap/approuter/approuter.js" } } ``` #### approuter/xs-app.json See [assets/xs-app.json](assets/xs-app.json) for a complete template: ```json { "authenticationMethod": "route", "routes": [ { "source": "^/protected(/.*)?$", "target": "/protected$1", "destination": "backend-app-destination", "authenticationType": "xsuaa", "scope": "$XSAPPNAME.Everyone", "csrfProtection": false }, { "source": "^/(.*)$", "target": "/$1", "destination": "backend-app-destination", "authenticationType": "none", "csrfProtection": false } ], "logout": { "logoutEndpoint": "/logout", "logoutPage": "/" } } ``` > **Scopes in routes:** Use `"scope": "$XSAPPNAME.<ScopeName>"` on each protected route to enforce XSUAA scope checks at the approuter level. The `$XSAPPNAME` placeholder is resolved at runtime to the bound XSUAA service's `xsappname`. Routes without a `scope` field only require authentication (when `authenticationType` is `xsuaa`). ### Step 5: Update User Access Code #### Neo UserProvider → CF XSUAA Token/TokenClaims mapping | Neo `UserProvider` / `User` API | CF XSUAA equivalent | |----------------------------------|---------------------| | `user.getName()` | `principal.getName()` — returns `user_uuid` (IAS) or `user_name` (XSUAA) | | `user.getAttribute("email")` | `token.getClaimAsString(TokenClaims.EMAIL)` | | `user.getAttribute("firstname")` | `token.getClaimAsString(TokenClaims.GIVEN_NAME)` | | `user.getAttribute("lastname")` | `token.getClaimAsString(TokenClaims.FAMILY_NAME)` | | `user.getAttribute("displayName")` | `token.getClaimAsString(TokenClaims.SAP_GLOBAL_USER_DISPLAY_NAME)` | | `user.getId()` (unique ID) | `token.getClaimAsString(TokenClaims.SAP_GLOBAL_USER_ID)` | | `request.isUserInRole("Admin")` | `request.isUserInRole("Admin")` — unchanged, maps to XSUAA scope | | Neo tenant / account name | `token.getClaimAsString(TokenClaims.SAP_GLOBAL_ZONE_ID)` | | `user.getAttribute("logon_name")` | `token.getClaimAsString(TokenClaims.USER_NAME)` | | XSUAA scopes list | `token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES)` — XSUAA-specific, not available for IAS tokens | **Before (Neo):** ```java import com.sap.security.um.user.UserProvider; import com.sap.security.um.user.User; @Resource private UserProvider userProvider; public void doGet(HttpServletRequest request, HttpServletResponse response) { User user = userProvider.getUser(request); String userName = user.getName(); String email = user.getAttribute("email"); String firstName = user.getAttribute("firstname"); boolean isAdmin = request.isUserInRole("Admin"); } ``` **After (Cloud Foundry — in JAX-RS endpoints or servlets):** ```java import com.sap.cloud.security.token.SecurityContext; import com.sap.cloud.security.token.Token; import com.sap.cloud.security.token.TokenClaims; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.security.Principal; import java.util.List; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. Basic identity — works with servlet security constraint in web.xml Principal principal = request.getUserPrincipal(); if (principal == null) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } String userName = principal.getName(); // user_uuid or user_name depending on IdP // 2. Read claims from the JWT token for richer user attributes Token token = SecurityContext.getToken(); if (token != null) { String email = token.getClaimAsString(TokenClaims.EMAIL); String firstName = token.getClaimAsString(TokenClaims.GIVEN_NAME); String lastName = token.getClaimAsString(TokenClaims.FAMILY_NAME); String userId = token.getClaimAsString(TokenClaims.SAP_GLOBAL_USER_ID); String logonName = token.getClaimAsString(TokenClaims.USER_NAME); // XSUAA scopes — only available for XSUAA tokens, null for IAS tokens List<String> scopes = token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES); // Tenant (zone ID) — identifies the subaccount String zoneId = token.getClaimAsString(TokenClaims.SAP_GLOBAL_ZONE_ID); } // 3. Role / scope check — unchanged from Neo boolean isAdmin = request.isUserInRole("Admin"); } ``` > **Why `SecurityContext.getToken()`?** The SAP Java Buildpack's `XSSecurityAuthenticator` Catalina valve validates the JWT and stores the parsed `Token` object in a thread-local via `SecurityContext`. This is the correct and recommended way to access the token. `request.getAttribute(Token.class.getName())` does NOT work in this context. > **`Token` vs `Principal`:** `request.getUserPrincipal().getName()` is sufficient for identifying the user. Only reach for `Token` when you need claims that are not exposed through the standard servlet API — email, given name, family name, zone ID. > **Null safety:** `token.getClaimAsString(...)` returns `null` if the claim is absent (e.g., the IdP did not include it). Always null-check before use in production code. ### Step 6: Create MTA Descriptor with Approuter Create or update `mtad.yaml`: ```yaml _schema-version: "3.2" version: 0.0.1 ID: ${app-name} parameters: enable-parallel-deployments: true modules: # Java Backend Application - name: ${app-name} type: java.tomcat path: target/<artifactId>.war # substitute literal artifactId from pom.xml; app serves at / parameters: buildpack: sap_java_buildpack_jakarta disk-quota: 1024M memory: 1024M properties: ENABLE_SECURITY_JAVA_API_V2: true JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']" JBP_CONFIG_SAP_MACHINE_JRE: "{ version: 25.+ }" TARGET_RUNTIME: tomcat SET_LOGGING_LEVEL: 'ROOT: INFO' requires: - name: ${app-name}-xsuaa - name: ${app-name}-destination provides: - name: ${app-name}-java-app properties: neo-app-url: '${default-url}' # Application Router - name: ${app-name}-approuter type: nodejs path: approuter parameters: disk-quota: 256M memory: 256M routes: - route: '${protocol}://${app-name}.${default-domain}' protocol: http1 properties: XS_APP_LOG_LEVEL: debug TENANT_HOST_PATTERN: '(.*).cfapps.sap.hana.ondemand.com' CF_NODEJS_LOGGING_LEVEL: "info" requires: - name: ${app-name}-xsuaa - name: ${app-name}-java-app group: destinations properties: name: backend-app-destination url: '~{neo-app-url}' forwardAuthToken: true resources: # XSUAA Service - name: ${app-name}-xsuaa type: org.cloudfoundry.managed-service parameters: service: xsuaa service-plan: application path: ./xs-security.json # Destination Service - name: ${app-name}-destination type: org.cloudfoundry.managed-service parameters: service: destination service-plan: lite ``` > **Key points:**
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub