47 · Security Basics & IAS vs XSUAA

Understand IAS vs XSUAA, OAuth 2.0 flows, JWT structure, and the req.user API in CAP.

Episode 47 — BTP Security Fundamentals

What you'll learn

Why this matters

Security in BTP is a layer cake. Misunderstanding one layer causes either a broken app (users can't log in) or an insecure one (the wrong users can). Starting with the architecture means you can debug auth issues systematically instead of guessing.

BTP identity layer

┌─────────────────────────────────────────────────────────┐
│                 BTP Identity Flow                        │
│                                                          │
│  Browser ──login──► IAS ──SAML──► XSUAA ──JWT──► CAP    │
│                                                          │
│  IAS:   Corporate identity provider (SSO, MFA)           │
│  XSUAA: BTP authorization server (OAuth 2.0, roles)     │
│  CAP:   Reads JWT, populates req.user                   │
└─────────────────────────────────────────────────────────┘

OAuth 2.0 flows in BTP

Flow Use case Who calls it
Authorization Code + PKCE Browser SPA (Fiori) Fiori app / AppRouter
Client Credentials Service-to-service CAP → external service
JWT Bearer Principal propagation CAP → S/4HANA
Password Legacy / testing only CLI tools

JWT token structure

{
  "iss":  "https://mysubdomain.authentication.eu10.hana.ondemand.com",
  "sub":  "user-uuid-abc123",
  "exp":   1726524000,
  "iat":   1726520400,
  "jti":  "unique-token-id",

  "user_name":    "alice@company.com",
  "given_name":   "Alice",
  "family_name":  "Smith",
  "email":        "alice@company.com",
  "zid":          "tenant-id",       // tenant identifier

  "scope": ["bookshop.Admin", "bookshop.Buyer", "openid"],

  "xs.user.attributes": {
    "region":     ["EMEA"],
    "costCenter": ["CC-1001"]
  }
}

What req.user provides

this.on('READ', 'Orders', (req) => {
  console.log(req.user.id)           // 'alice@company.com'
  console.log(req.user.name)         // 'Alice Smith'
  console.log(req.user.locale)       // 'en-US'
  console.log(req.user.tenant)       // 'my-subaccount-id'
  console.log(req.user.tokenInfo)    // raw JWT claims (when needed)

  console.log(req.user.is('Admin'))      // true if scope includes bookshop.Admin
  console.log(req.user.is('Buyer'))      // true if scope includes bookshop.Buyer
  console.log(req.user.attr.region)      // ['EMEA']
  console.log(req.user.attr.costCenter)  // ['CC-1001']
})

Token validation by CAP

When a request arrives:
1. CAP extracts Authorization: Bearer <token> header
2. Validates signature using XSUAA's public key (fetched from JWKS endpoint)
3. Checks expiry (exp claim)
4. Populates req.user from validated claims
5. Rejects with 401 if token is missing/invalid

// .cdsrc.json — enables JWT validation
{
  "requires": {
    "auth": {
      "[production]": {
        "kind": "xsuaa"
      },
      "[development]": {
        "kind": "mocked",
        "users": {
          "alice": { "roles": ["Admin"] },
          "bob":   { "roles": ["Buyer"] }
        }
      }
    }
  }
}

AppRouter — the front door

Browser ──► AppRouter (Node.js) ──► XSUAA (auth code flow)
                   │
                   └──► CAP backend (attaches JWT, forwards request)

The AppRouter handles:
- Redirecting unauthenticated users to XSUAA login page
- Exchanging auth code for JWT
- Attaching JWT to upstream requests to CAP
- Session management (stores JWT in server-side session)

Common auth debugging steps

# 1. Decode the JWT (without verifying signature)
echo '<jwt>' | cut -d '.' -f 2 | base64 -d | jq .

# 2. Check XSUAA JWKS endpoint is reachable
curl https://mysubdomain.authentication.eu10.hana.ondemand.com/.well-known/openid-configuration

# 3. Check CAP logs for auth errors
cf logs bookshop-srv --recent | grep -i 'auth\|401\|403'

# 4. Inspect req.user in a handler
this.before('*', '*', (req) => console.log('User:', JSON.stringify(req.user)))

Hands-on exercise

  1. Add "auth": { "[development]": { "kind": "mocked", ... } } to .cdsrc.json with 3 users
  2. Start cds watch and call an endpoint with Authorization: Basic alice:alice
  3. Add a this.before('*', '*', ...) that logs req.user.id and req.user.roles
  4. Verify different users get different scopes

Checkpoint ✓

Sign in for progress tracking & Q&A →