Skip to Content
Technical Articles
Author's profile photo Piotr Tesny

How to generate SAML bearer assertion token for OAuth2SAMLBearerAssertion flow?

Abstract.

This instalment is to help understand what it takes to implement the OAuth2SAMLBearerAssertion Flow without SAP BTP Destination service. Or, in other words, how to manually generate your own SAML Bearer Assertion token.

Albeit that’s not the recommended approach there is still some strong DIY demand.

Disclaimer.

  • As with any DIY work you are on your own when it comes to the nitty-gritty details.
  • From my recent DIY experience: replacing a corroded kitchen sink tap that looked like a simple 30 minutes job turned out to be a serious piece of work even for an experienced professional handy man.
  • Please note all the code snippets below are provided “as is”.
  • All the x509 certificates, SAML assertions, bearer access and refresh tokens and the likes have been redacted.

Good to know:

  • This approach is officially documented in SAP SuccessFactors help pages.

Deja vu.

I already covered the OAuth2SAMLBearerAssertion flow with the following mini series of blogs, where the SAP BTP Destination Service acts both as the trusted IdP and a user propagation broker for the OAuth2SAMLBearerAssertion flow, namely:

But what, if for any reason, you would not want to rely on SAP BTP Destination Service and rather do it on your own? 

 

Putting it all together.

Per aspera ad astra. Generate your own OAuth2-SAML-Bearer-Assertion token.

From my own DIY experience that’s the hardest part of the job.

Even, if this is not a recommended approach, doing this will definitely help you understand the OAuth2SAMLBearerAssertion flow mechanism at all and ultimately and very likely make you appreciate the value proposition of SAP BTP Destination service.

SAP SuccessFactors brief.

The public SAP SFSF documentations offers a thorough guidance on how to implement Authentication Using OAuth 2.0, namely:

and describes the following three options to generate a SAML assertion:

a. Use a third-party IdP that you trust.

That’s the recommended approach. Like for instance using SAP BTP Destination Service as our trusted IdP (Identity Provider).

Please note that if you are using the destination service trust it will be used to sign the saml assertion. You still will need to somehow provide the user claim for the saml assertion. If using the destination service the recommended approach is to provide the user’s JWT token in the X-user-token header of the find destination call.

b. Use the offline SAML bearer assertion generation tool.

see SAP note – 3031657
or you could create the assertion programmatically as shown below.

c. Use built-in SAP SuccessFactors IdP SAML bearer assertion generation endpoint.
This option is the least recommended as you would need to provide the private key of your certificate in the call to the above endpoint.
If this is the option you need to adopt you might consider using the SAP API Management’s SuccessFactor connectivity policy.

Good to know:

  • You may also refer to these two excellent external blogs covering
    SAP SuccessFactors and SAP Jam OAuth2 configuration
    with SAML authentication, namely:
SAP SuccessFactors SAML Authentication in Python
SAP Jam SAML Authentication Using Python

SAP Analytics Cloud brief.

The access to resources and assets of SAP Analytics Cloud tenant is protected with SAML2.0 protocol with the so-called SP-initiated WebSSO flow.

This flow requires a user to enter her or his credentials every time the SAML assertion has expired (or has been removed from the browser cache). Also SAC tenant imposes a time limit on the duration of the HTTP session.

While this is OK for the interactive usage of SAC tenant this may be not OK if a custom client application needed remote access to SAC resources or assets without user interaction. Indeed, there will be no one to acknowledge and dismiss the SAML assertion login pop-up window etc…

This is where the SAP Analytics Cloud App Integration with OAuth 2.0 using the SAML bearer assertion flow comes into the scene.

Arguably there is not much insight across the SAP help pages on how this can be done with the likes of SAP Analytics Cloud. Let’s try to bridge this gap.

 

OAuth2SAMLBearerAssertion Flow with manually signed SAML Bearer Assertion token.

 

Disclaimer. The below code snippets are provided as is.

The plan for this brief is to cover the steps c, d and e, namely:

a. Configuring SAP Analytics Cloud for OAuth2 authentication with SAML bearer assertion flow – already covered here.

b. Generating SAML bearer assertions offline: Using our own XML-signing code based on the following SAP note – 3031657

c. Generating SAML bearer assertions manually [in nodejs] d. Obtaining a bearer access token. e.i. Authenticating SAC API requests.

d. Obtaining a bearer access token.

e. Authenticating SAC API requests.

ad c). Generating SAML bearer assertions manually [in nodejs]

Let’s create a simple nodejs generateSAMLBearerAssertion function:

//
// Create SAML assertions. Supports SAML 1.1 and SAML 2.0 tokens.
//
var saml = require('saml').Saml20; // https://www.npmjs.com/package/saml

async function generateSAMLBearerAssertion() {

const  cert = '-----BEGIN CERTIFICATE-----\nMIIFGzCCAwMCBGBb1dwwDQYJKoZIhvcNAQELBQAwUjELMAkGA1UEBhMCVVMxDDAK\nBgNVBAoMA1NBUDEVMBMGA1UECwwMY
..........................(truncated)............................
i\ne3pZsV0QGgSCMZ8kNQobunEPnfkXysLhUvWzniY0UI9uLY7F9934p3PLnZAJOhLJ\nO0X6cHCFbMC+6GxXTdisQVivIOKUURdaHVX6B270SUDiP6TDPApn9E+IaISzPRpk\nXT6c0QNVYg37DBU/qhSN\n-----END CERTIFICATE-----\n'; 
const key = '-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDFz/eQv30tj5oC\nLjT1Im7OtVAVo6mB/wQbEpbOh3LSI
..........................(truncated)............................
JGRqkeRIArXvScbLwq62ViESgOIOU8TdR0n3fachXehZLgRUTa2IGI6zKuVSaXLq\nWBgr0UKz5CLYl4kvZ8ECFbb/I8psoa5LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7J\nAwx8lLYgANSJ7qkKPgPR/t5ZHrx/plY=\n-----END PRIVATE KEY-----\n';


var options = {
  cert: Buffer.from(cert, 'utf-8'), // x509 certificate
  key: Buffer.from(key, 'utf-8'),   // private key (it contains public key as well)

  issuer: 'quovadis/ateam-isveng', // CN of the public certificate key which is the Provider Name in a Trusted SAML Provider in SAC/System/Administration/App Integration
  lifetimeInSeconds: 3600,
  attributes: {
    'client_id': '<OAuth client id from SAC/System/Administration/App Integration>',
  },
  includeAttributeNameFormat: true, //false,
  sessionIndex: '_faed468a-15a0-4668-aed6-3d9c478cc8fa', 
  authnContextClassRef: 'urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession',
  nameIdentifierFormat: 'urn:oasis:names:tc:SAML:2.0:attrname-format:email', // urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified
  nameIdentifier: '<user name>', // user email address
  recipient: '<OAuth2SAML token URL from SAC/System/Administration/App Integration>',
  audiences: '<Audience from SAC/System/Administration/App Integration>>',
  // signatureAlgorithm: rsa-sha256',
  // digestAlgorithm: 'sha256',
  signatureNamespacePrefix: 'ds',
};

var unsignedAssertion = saml.createUnsignedAssertion(options);

var signedAssertion =   saml.create(options);
signedAssertion = btoa(signedAssertion);
console.log('btoa-ed signedAssertion: ', signedAssertion);
signedAssertion = encodeURIComponent(signedAssertion);

console.log('unsignedAssertion: ', unsignedAssertion);
console.log('signedAssertion: ', signedAssertion);
return signedAssertion; // unsignedAssertion;
}

 

// this is to create a base64 and URL encoded saml bearer assertion:
//
var samlassertion = await generateSAMLBearerAssertion();

------------------------------------------------------------------------------------
PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIFZlcnNpb249IjIuMCIgSUQ9Il81TEdreTZjVVF6UXVlWWFnb3BITkVIZXh6RkZnVmNuSiIgSXNzdWVJbnN0YW50PSIyMDIxLTA0LTA1VDA5OjE4OjM5LjIwM1oiPjxzYW1sOklzc3Vlcj5xdW92YWRpcy9hdGVhbS1pc3Zlbmc8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR
.................................... (truncated)......................................
M2E4OC00YmYwLWI4ODYtZWI5ZWM3YWY2OThhIWIxNjU4fGNsaWVudCFiNjU1PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2BPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPg%3D%3D

 

Good to know:

  • A SAML bearer assertion is an XML identity token. Typically it is base64-encoded.
  • You can decode the saml bearer assertion into plain XML with any online decoder like for instance the SAML Decoder from SSOCircle as illustrated below:

 

  • The cert and key in the code snippet above are a self-signed x509 certificate and a private key. For instance, you could generate a self-signed keypair using openssl. Here goes a short primer on how to generate a keystore with a keypair using portecle. There is also this excellent support article from our friends at Google, namely Generate Keys and Certificates for SSO.
  • As an example, the picture below shows how to leverage a SAP BTP Neo sub-account’s Local Provider to generate a keypair and how to use it in a Trusted SAML Provider with SAC App Integration. It is important to understand that the SAC App Integration will create a SAML Identity Provider with the x509 certificate from the generated keypair. You must keep the private key and use it to sign the saml assertion as depicted above in the nodejs code snippet.

 

 

ad d). Obtaining a bearer access token.

then you can use the above SAML assertion to generate your bearer access token to unlock the SAC APIs by calling the OAuth2SAML token URL endpoint as depicted in the below nodejs snippet:

 

async function getoauthaccesstoken(saml_bearer_assertion) {  

const credentials = { // OAuth client_id and secret from SAC OAuth client you have created 
    client: { 
        id: '<OAuth client_id>',
        secret: '<OAuth secret>' 
    },
    auth: {
        tokenHost: '<OAuth2SAML token URL>', 
    },
    options: {
        authorizationMethod: 'body'
    }
}

    const options = {
    headers: { "Authorization": "Basic <base64-encoded <client_id>:<secret>:>" }
    
    //headers: { 'Authorization': 'Basic ' + btoa('<client_id>:<secret>') }
    };    

    var params = new URLSearchParams();
    params.append('client_id', credentials.client.id);
    //params.append('client_secret', credentials.client.secret);
    params.append('grant_type',  "urn:ietf:params:oauth:grant-type:saml2-bearer");
    params.append("assertion", saml_bearer_assertion);
    params.append("scope", "openid uaa.user");

  let documents;
  try {
    const response = await axios.post(credentials.auth.tokenHost, params , options);
    documents = JSON.stringify(response.data, null, 2);
    console.log(documents);
    console.log(response.status);

  }
  catch(error) {
      console.log(error.message);
      documents = JSON.stringify(error, null, 2);
  };
  
  return documents;
}
https://localhost:8080/getoauthaccesstoken?assertion=PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24lPjwvc2FtbDpBdHRyaWJ1dGU%2BPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPg%3D%3D

 

and here goes the resulting (truncated) bearer access token:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vYXRlYW0taXN2ZW5nLmF1dGhlbnRpY2F0aW9uLnVzMTAuaGFuYS5vbmRltp6vkKH0PrZvg4e0otB59iG9MvCHV3mMEWzIxccIxrgEcem8_53l3fwzvMA5M",
  "token_type": "bearer",
  "refresh_token": "97c6b547a5ff4a40axxxxxxxxxxxxxx",
  "expires_in": 3599,
  "scope": "openid uaa.user",
  "jti": "c903263a1bdb411595aa103xxxxxxx"
}

Good to know:

  • OAuth2SAML token URL endpoint is the equivalent of the IDP endpoint. Calling into the OAuth2SAML token URL endpoint is triggering the IDP-initiated SAML SSO flow
  • The OAuth audience is the equivalent of the service provider (= application)

Here goes the same using Postman to call into OAuth2SAML token URL endpoint:

Please note the format of the payload (body) must be set to x-www-form-urlencoded .

A fairly common mistake being setting only Content-Type header to application/x-www-form-urlencoded.

 

The signed saml assertion is only valid for one hour. When its validity has expired you would receive the following response from calling into the OAuth recipient

{
“error”: “unauthorized”,
“error_description”: “Error validating SAML message”
}

ad e). Authenticating SAC API requests.

and last but not least it is  time to consume the bearer access token for instance to embed a story in an iframe or get access to the list of stories your user has access to…

 

__________

 

Additional resources.

 

Extending SAP SuccessFactors:

SAP Analytics Cloud REST API.

The SAP Analytics REST API gives us access to different assets in the SAC tenant .

The SAC APIs but one are based on the  RESTFUL API standard. Which means these API can be called from any programming language, framework or LCNC application securely over HTTPS.

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.