Quicklinks:
Quick Guide
Sample Code
"scopes": [{
"name": "$XSAPPNAME.backendscope"
}],
"role-templates": [{
"name": "BackendRole",
"scope-references": ["$XSAPPNAME.backendscope"]
}],
"role-collections": [{
"name": "Backend_Roles",
"role-template-references": ["$XSAPPNAME.BackendRole"]
app.get('/endpoint', passport.authenticate('JWT', {session: false}), (req, res) => {
const authInfo = req.authInfo
console.log(`===> [AUDIT] backendapp accessed by user '${authInfo.getGivenName()}' from subdomain '${authInfo.getSubdomain()}' with oauth client: '${authInfo.getClientId()}'`)
const isScopeAvailable = authInfo.checkScope(UAA_CREDENTIALS.xsappname + '.backendscope')
if (! isScopeAvailable) {
// res.status(403).end('Forbidden. Missing authorization.') // Don't fail during prototyping
}
res.json({
'message': `Backend app successfully called. Scope available: ${isScopeAvailable}`,
'jwtToken': authInfo.getAppToken()})
})
#clientKey=<< Existing password/certificate removed on export >>
#tokenServicePassword=<< Existing password/certificate removed on export >>
Description=Destination pointing to backend app endpoint in backend account
Type=HTTP
authnContextClassRef=urn\:oasis\:names\:tc\:SAML\:2.0\:ac\:classes\:PreviousSession
audience=https\://backendsubdomain.authentication.ap21.hana.ondemand.com
Authentication=OAuth2SAMLBearerAssertion
Name=destination_to_backend
tokenServiceURL=https\://backendsubdomain.authentication.ap21.hana.ondemand.com/oauth/token/alias/backendsubdomain.azure-ap21
ProxyType=Internet
URL=https\://backend.cfapps.ap21.hana.ondemand.com/endpoint
nameIdFormat=urn\:oasis\:names\:tc\:SAML\:1.1\:nameid-format\:emailAddress
tokenServiceURLType=Dedicated
tokenServiceUser=sb-backendxsuaa\!t7722
{
"xsappname": "backendxsuaa",
"tenant-mode": "dedicated",
"scopes": [{
"name": "$XSAPPNAME.backendscope"
}],
"role-templates": [{
"name": "BackendRole",
"description": "Role required for Backend Application",
"scope-references": ["$XSAPPNAME.backendscope"]
}],
"role-collections": [{
"name": "Backend_Roles",
"role-template-references": ["$XSAPPNAME.BackendRole"]
}
]
}
---
applications:
- name: backend
path: app
memory: 64M
routes:
- route: backend.cfapps.ap21.hana.ondemand.com
services:
- backendXsuaa
{
"dependencies": {
"@sap/xsenv": "latest",
"@sap/xssec": "latest",
"express": "^4.17.1",
"passport": "^0.4.0"
}
}
const xsenv = require('@sap/xsenv')
const UAA_CREDENTIALS = xsenv.getServices({myXsuaa: {tag: 'xsuaa'}}).myXsuaa
const express = require('express')
const app = express();
const xssec = require('@sap/xssec')
const passport = require('passport')
const JWTStrategy = xssec.JWTStrategy
passport.use('JWT', new JWTStrategy(UAA_CREDENTIALS))
app.use(passport.initialize())
app.use(express.json())
// start server
app.listen(process.env.PORT)
app.get('/endpoint', passport.authenticate('JWT', {session: false}), (req, res) => {
const authInfo = req.authInfo
console.log(`===> [AUDIT] backendapp accessed by user '${authInfo.getGivenName()}' from subdomain '${authInfo.getSubdomain()}' with oauth client: '${authInfo.getClientId()}'`)
const isScopeAvailable = authInfo.checkScope(UAA_CREDENTIALS.xsappname + '.backendscope')
if (! isScopeAvailable) {
//res.status(403).end('Forbidden. Missing authorization.') // Don't fail during prototyping
}
res.json({
'message': `Backend app successfully called. Scope available: ${isScopeAvailable}`,
'jwtToken': authInfo.getAppToken()})
})
{
"xsappname": "frontendxsuaa",
"tenant-mode": "dedicated",
"role-templates": [{
"name": "uaaUserDefaultRole",
"description": "Default role uaa.user required for user centric scenarios",
"scope-references": ["uaa.user"]
}],
"role-collections": [{
"name": "Frontend_Roles",
"role-template-references": [ "$XSAPPNAME.uaaUserDefaultRole" ]
}
]
}
---
applications:
- name: frontend
path: app
memory: 64M
routes:
- route: frontend.cfapps.us10.hana.ondemand.com
services:
- frontendXsuaa
- frontendDestination
- name: frontendrouter
routes:
- route: frontendrouter.cfapps.us10.hana.ondemand.com
path: approuter
memory: 128M
env:
destinations: >
[
{
"name":"destination_frontend",
"url":"https://frontend.cfapps.us10.hana.ondemand.com",
"forwardAuthToken": true
}
]
services:
- frontendXsuaa
{
"dependencies": {
"@sap/destinations": "latest",
"@sap/xsenv": "latest",
"@sap/xssec": "^3.2.13",
"express": "^4.17.1",
"node-fetch": "2.6.2",
"passport": "^0.4.0"
}
}
const xsenv = require('@sap/xsenv')
const INSTANCES = xsenv.getServices({
myXsuaa: {tag: 'xsuaa'},
myDestination: {tag: 'destination'}
})
const XSUAA_CREDENTIALS = INSTANCES.myXsuaa
const DESTINATION_CREDENTIALS = INSTANCES.myDestination
const fetch = require('node-fetch')
const xssec = require('@sap/xssec')
const passport = require('passport')
const JWTStrategy = xssec.JWTStrategy
passport.use('JWT', new JWTStrategy(XSUAA_CREDENTIALS))
const express = require('express')
const app = express();
app.use(passport.initialize())
app.use(express.json())
// start server
app.listen(process.env.PORT)
// calling destination service with user token and token exchange
app.get('/homepage', passport.authenticate('JWT', {session: false}), async (req, res) => {
const userJwtToken = req.authInfo.getAppToken()
// instead of fetching token for destination service with client creds, we HAVE to use token exchange, must be user for princip propag
const destJwtToken = await _doTokenExchange(userJwtToken)
// read destination
const destination = await _readDestination('destination_to_backend', destJwtToken)
const samlbearerJwtToken = destination.authTokens[0].value
// call backend app endpoint
const response = await _callBackend(destination)
const responseJson = JSON.parse(response)
const responseJwtTokenDecoded = decodeJwt(responseJson.jwtToken)
// print token info to browser
const htmlUser = _formatClaims(userJwtToken)
const htmlDest = _formatClaims(destJwtToken)
const htmlBearer = _formatClaims(samlbearerJwtToken)
res.send(` <h4>JWT after user login</h4>${htmlUser}
<h4>JWT after token exchange</h4>${htmlDest}
<h4>JWT issued by OAuth2SAMLBearerAssertion destination</h4>${htmlBearer}
<h4>Response from Backend</h4>${responseJson.message}. The token: <p>${JSON.stringify(responseJwtTokenDecoded)}</p>`)
})
/* HELPER */
async function _readDestination(destinationName, jwtToken, userToken){
const destServiceUrl = `${DESTINATION_CREDENTIALS.uri}/destination-configuration/v1/destinations/${destinationName}`
const options = {
headers: { Authorization: 'Bearer ' + jwtToken}
}
const response = await fetch(destServiceUrl, options)
const responseJson = await response.json()
return responseJson
}
async function _doTokenExchange (bearerToken){
return new Promise ((resolve, reject) => {
xssec.requests.requestUserToken(bearerToken, DESTINATION_CREDENTIALS, null, null, null, null, (error, token)=>{
resolve(token)
})
})
}
async function _callBackend (destination){
const backendUrl = destination.destinationConfiguration.URL
const options = {
headers: {
Authorization : destination.authTokens[0].http_header.value // contains the "Bearer" plus space
}
}
const response = await fetch(backendUrl, options)
const responseText = await response.text()
return responseText
}
function decodeJwt(jwtEncoded){
return new xssec.TokenInfo(jwtEncoded).getPayload()
}
function _formatClaims(jwtEncoded){
// const jwtDecodedJson = new xssec.TokenInfo(jwtEncoded).getPayload()
const jwtDecodedJson = decodeJwt(jwtEncoded)
console.log(`===> The full JWT: ${JSON.stringify(jwtDecodedJson)}`)
const claims = new Array()
claims.push(`issuer: ${jwtDecodedJson.iss}`)
claims.push(`<br>client_id: ${jwtDecodedJson.client_id}</br>`)
claims.push(`grant_type: ${jwtDecodedJson.grant_type}`)
claims.push(`<br>scopes: ${jwtDecodedJson.scope}</br>`)
claims.push(`ext_attr: ${JSON.stringify(jwtDecodedJson.ext_attr)}`)
claims.push(`<br>aud: ${jwtDecodedJson.aud}</br>`)
claims.push(`origin: ${jwtDecodedJson.origin}`)
claims.push(`<br>name: ${jwtDecodedJson.given_name}</br>`)
claims.push(`xs.system.attributes: ${JSON.stringify(jwtDecodedJson['xs.system.attributes'])}`)
return claims.join('')
}
{
"dependencies": {
"@sap/approuter": "latest"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}
{
"authenticationMethod": "route",
"routes": [
{
"source": "^/tofrontend/(.*)$",
"target": "$1",
"destination": "destination_frontend",
"authenticationType": "xsuaa"
}
]
}
#clientKey=<< Existing password/certificate removed on export >>
#tokenServicePassword=<< Existing password/certificate removed on export >>
#
#Fri Jun 10 07:09:11 UTC 2022
Description=Destination pointing to backend app endpoint in backend account
Type=HTTP
authnContextClassRef=urn\:oasis\:names\:tc\:SAML\:2.0\:ac\:classes\:PreviousSession
audience=https\://backendsubdomain.authentication.ap21.hana.ondemand.com
Authentication=OAuth2SAMLBearerAssertion
Name=destination_to_backend
tokenServiceURL=https\://backendsubdomain.authentication.ap21.hana.ondemand.com/oauth/token/alias/backendsubdomain.azure-ap21
ProxyType=Internet
URL=https\://backend.cfapps.ap21.hana.ondemand.com/endpoint
nameIdFormat=urn\:oasis\:names\:tc\:SAML\:1.1\:nameid-format\:emailAddress
tokenServiceURLType=Dedicated
tokenServiceUser=sb-backendxsuaa\!t7722
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
26 | |
15 | |
12 | |
11 | |
9 | |
9 | |
6 | |
6 | |
5 | |
5 |