Skip to content

Commit

Permalink
read SAML Assertion from Entra ID
Browse files Browse the repository at this point in the history
  • Loading branch information
gregorwolf committed Dec 21, 2023
1 parent b64c79a commit 7411361
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 0 deletions.
121 changes: 121 additions & 0 deletions srv/AuthClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* AuthClient.js provides a class offering features for Microsoft Graph and SAP BTP authentication
*
* This class provides helpfull features required for authenticating Microsoft Graph and SAP BTP requests.
* Requests to Microsoft Graph are either authenticated via delegate permissions or application permissions.
* Requests to SAP BTP are authenticated using a SAML assertion obtained from Azure AD (see details below).
*
*/

const qs = require("qs");
const axios = require("axios");
const xsenv = require("@sap/xsenv");

class AuthClient {
constructor() {
// Read configuration from VCAP_SERVICES environment variable
xsenv.loadEnv();
var services = {};
try {
services = xsenv.getServices({ azuread: { tag: "azure-ad" } });
} catch (error) {
console.error(chalk.red("[azure-ad-auth-client] - " + error.message));
console.error(
"[azure-ad-auth-client] - maintain default-env.json or provide the environment variable VCAP_SERVICES"
);
throw new Error(error.message);
}

// Azure Active Directory tenant id
this.aadTenantId = services.azuread.tenantID;

// Application registration id and secret
this.appId = services.azuread.clientID;
this.appSecret = services.azuread.clientSecret;

this.ApplicationIDuri = services.azuread.ApplicationIDuri;

// V2 AAD path for On-behalf-of flow
this.pathOAuth = `/${this.aadTenantId}/oauth2/v2.0/token`;
// On behalf of token use
this.tokenUseValue = "on_behalf_of";
// JWT-Bearer token grant type
this.grantTypeJwtBearer = "urn:ietf:params:oauth:grant-type:jwt-bearer";
// Token type SAML2
this.tokenTypeSaml = "urn:ietf:params:oauth:token-type:saml2";
// AAD hostname
this.aadBasUrl = "https://login.microsoftonline.com";

this.headerUrlEncoded = "application/x-www-form-urlencoded";
}

// Get SAML Assertion for BTP Access (on behalf of flow )

/**
* This method allows to request a SAML assertion for SAP BTP access
*
* Using an valid OAuth token (including a custom scope) of the extension application registration,
* a SAML assertion can be requested from Azure AD. This is possible due to the fact that the extension
* application has been added as trusted client to the application registration created when setting
* up the trust between SAP BTP and Azure Ad. This allows us to use the on-behalf-of flow also in this
* scenario. The resulting SAML assertion can then be used to obtain a valid oAuth token from XSUAA.
*
*/
async getSamlAssertionForBtpTokenExchange(token) {
if (!token || !token.trim()) {
throw new Error("Invalid token received.");
}

const data = qs.stringify({
assertion: token,
grant_type: this.grantTypeJwtBearer,
client_id: this.appId,
client_secret: this.appSecret,
scope: this.ApplicationIDuri,
requested_token_use: this.tokenUseValue,
requested_token_type: this.tokenTypeSaml,
});

const aadTokenEndpoint = this.aadBasUrl + this.pathOAuth;

let res = await (async () => {
try {
let resp = await axios.post(aadTokenEndpoint, data, {
headers: {
"Content-Type": this.headerUrlEncoded,
},
});
return resp;
} catch (err) {
console.error(err);
}
})();

if (res.fstatus == 200) {
// test for status you want, etc
console.log(res.status);
}

if (res.data && res.headers["content-type"].includes("application/json")) {
const responseBody = res.data;
let samlAssertion = " ";
try {
samlAssertion = responseBody["access_token"].toString();
return samlAssertion;
} catch (err) {
console.error("No JSON response. SAML Token request failed");
}
} else {
console.error("HTTP Response was invalid and cannot be deserialized.");
}
}

// Helper method to extract the token from an request header.
_getAuthDataFromRequest(req) {
const authHeader = req.headers.authorization;
const token = authHeader.split(" ")[1];
return token;
}
}

module.exports = { AuthClient };
7 changes: 7 additions & 0 deletions srv/catalog-service.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const cds = require("@sap/cds");
const LOG = cds.log("catalog-service");
const { AuthClient } = require("./AuthClient");

function getAuthToken(req) {
const authHeader = req._.req.headers["authorization"];
Expand All @@ -19,7 +20,13 @@ module.exports = async function (srv) {

srv.on("getOAuth2SAMLBearerAssertion", async (req) => {
const token = getAuthToken(req);
const authClient = new AuthClient();
LOG.debug("Token: " + token);
const samlAssertion = await authClient.getSamlAssertionForBtpTokenExchange(
token
);
LOG.debug("SAML Assertion: " + samlAssertion);
// https://login.microsoftonline.com/{{AAD tenant ID}}/oauth2/v2.0/token
});

srv.on("readSAPLogonTicket", async (req) => {
Expand Down

0 comments on commit 7411361

Please sign in to comment.