Wire XSUAA and AppRouter in mta.yaml

Add XSUAA and AppRouter modules and resources to mta.yaml, understand the provides/requires URL passing pattern, and deploy the full authenticated stack to BTP.

Wire XSUAA and AppRouter in mta.yaml

This is where everything comes together. After this topic you have a complete, deployable, authenticated BTP application.

The full picture

Internet
  │
  ▼
AppRouter (CF app)
  │  ① redirects to XSUAA for login
  │  ② gets JWT token back
  │  ③ forwards request + token to CAP backend
  │
  ▼
CAP Backend (CF app)
  │  ④ validates token against XSUAA
  │  ⑤ applies @restrict rules
  │
  ▼
SAP HANA (HDI container)

All three arrows — AppRouter → CAP, AppRouter → XSUAA, CAP → XSUAA — are wired via mta.yaml.


Complete annotated mta.yaml

_schema-version: "3.1"
ID: bookshop
version: 1.0.0
description: Bookshop — authenticated CAP app on BTP

modules:

  # ── 1. CAP Backend ───────────────────────────────────────────
  - name: bookshop-srv
    type: nodejs
    path: gen/srv
    parameters:
      buildpack: nodejs_buildpack
      memory: 256M
      disk-quota: 512M
    requires:
      - name: bookshop-hana      # HANA HDI container
      - name: bookshop-xsuaa     # XSUAA service instance
    provides:
      - name: srv-api             # exports the backend URL
        properties:
          srv-url: ${default-url} # CF resolves this to the real URL at deploy time

  # ── 2. HANA Deployer ─────────────────────────────────────────
  - name: bookshop-db-deployer
    type: hdb
    path: gen/db
    requires:
      - name: bookshop-hana

  # ── 3. AppRouter ─────────────────────────────────────────────
  - name: bookshop-app
    type: approuter.nodejs
    path: app/router              # the folder with xs-app.json + package.json
    parameters:
      memory: 128M
      disk-quota: 256M
    requires:
      - name: bookshop-xsuaa     # needs XSUAA for login
      - name: srv-api             # consumes the URL exported by bookshop-srv
        group: destinations       # injects it as a named destination
        properties:
          name: srv-api           # destination name used in xs-app.json
          url: ~{srv-url}         # ~{property} resolves from srv-api.provides
          forwardAuthToken: true  # appends the user's JWT to proxied requests

resources:

  # ── HANA HDI container ───────────────────────────────────────
  - name: bookshop-hana
    type: org.cloudfoundry.managed-service
    parameters:
      service: hana
      service-plan: hdi-shared

  # ── XSUAA service instance ───────────────────────────────────
  - name: bookshop-xsuaa
    type: org.cloudfoundry.managed-service
    parameters:
      service: xsuaa
      service-plan: application
      path: ./xs-security.json    # the security descriptor from cds add xsuaa

The provides / requires URL pattern explained

This is the part that trips up most people:

# bookshop-srv EXPORTS its URL:
provides:
  - name: srv-api
    properties:
      srv-url: ${default-url}   # CF magic: resolves to https://bookshop-srv.cfapps.eu10...

# bookshop-app CONSUMES it:
requires:
  - name: srv-api
    group: destinations
    properties:
      url: ~{srv-url}           # ~{} reads from the srv-api.provides block

This means AppRouter's xs-app.json destination name srv-api resolves automatically — you never hard-code the backend URL.


forwardAuthToken: true

Without this flag, AppRouter proxies the request without the user's token. Your CAP service receives an unauthenticated request and returns 401 even though the user logged in.

Always set forwardAuthToken: true on any backend destination that uses @requires.


Build and deploy the full stack

# 1. Compile
cds build --production

# 2. Package
mbt build

# 3. Deploy
cf deploy mta_archives/bookshop_1.0.0.mtar -f

The deployer will:
1. Create the bookshop-hana HDI container (if new)
2. Create the bookshop-xsuaa service instance using xs-security.json (if new)
3. Deploy HANA artifacts via bookshop-db-deployer
4. Start bookshop-srv bound to both HANA and XSUAA
5. Start bookshop-app (AppRouter) with the destination pointing at the live backend URL


Assign role collections to your user

After first deployment:

  1. Open BTP Cockpit → your subaccount → Security → Users
  2. Find your user → Assign Role Collection
  3. Assign the role collection you defined in xs-security.json (e.g. "Bookshop Admin")
  4. Wait ~30 seconds for the token cache to expire, then open the app URL

If the user still gets 403, check:

cf logs bookshop-srv --recent | grep -i "scope\|403\|unauthorized"

Common mta deploy errors with XSUAA

Error Cause Fix
Error creating service bookshop-xsuaa: invalid scope name Scope doesn't start with $XSAPPNAME. Fix xs-security.json
403 on all endpoints after login forwardAuthToken: true missing Add it to AppRouter destination
redirect_uri_mismatch XSUAA redirect-uris in xs-security.json doesn't match app URL Add *.hana.ondemand.com/** wildcard
AppRouter fails to start: Cannot find module... @sap/approuter not in app/router/package.json cd app/router && npm install
JWT token expired during testing Default token lifetime is 12 hours Re-login; for CI use client_credentials grant
Sign in for progress tracking & Q&A →