Skip to Content
Technical Articles
Author's profile photo Shlomi Lachmish

SAP BTP – Safely consume your app secrets with Credential Store 🕵️

Assuming you are a developer, you probably ran into the need to access some secret from your app. For example a user/password/key to connect a remote system.

In SAP BTP you of course have the destination service that cover some use cases with a good security approach.

Unfortunately the destination service does not fit all needs and sometimes you need to store and access secrets.


Let’s say we have this “myVerySecretMessage” string that we need to access from the application run time.

So what can we do:

  1. Store it directly in the code – BAD idea 👎 it can be access by anyone with access to your code, plus it may leaks to your git repo and you don’t want that (tip: open source tool that checks your git repo for hardcoded secrets- )
  2. Store it as an environment variable – this is definitely better then storing it hardcoded in the code, but still has its soft belly
    1. It is stored as plan text
    2. No security around it – any one that can access the env can see it
    3. Might be exposed in log files like history file
  3. Finally, we can use the “Credential Store” service in the BTP 🤘🤘🤘
    1. Secured very well
    2. All data is Encrypted


Let me share with you the small experience I have with that nice BTP service:

  1. Make sure your space has the entitlement to use this service
  2. From the left navigation menu, choose Services > Service Marketplace > Choose the Credential Store tile
  3. During service creation :
    1. you should choose the plan (in my config I choose “standard” plan)
    2. give it a name e.g. “mycredstore”
  4. Bind the service instance to your application (can be done from BTP cockpit or from the terminal)
    1. The terminal way: cf bind-service myapp mycredstore
  5. After a success binding, you can find some credstore connection props via the VCAP_SERVICES environment variable
    1. username: this property is used for authentication. It contains the unique binding ID.
    2. private_key: this property contains the private key used to sign JWT tokens for authentication. See: Encrypting Payloads
    3. oauth_token_url: SAP Credential Store supports OAuth client credentials flow for authentication. This property contains the token URL of the OAuth authorization server, which accepts either signed JWT tokens, or username/password and issues access tokens.
    4. url: this property contains the URL of the SAP Credential Store REST API. The URL can be accessed only with a valid access token issued by the OAuth authorization server (see property oauth_token_url).
    5. parameters: this property contains either the default binding options, or the custom ones defined during the binding creation. Currently, it contains only an authorization section with access permissions.


Now we come to the application integration, I’m showing the credstore api from a CAP application based on nodeJS:

I modified a bit the Credential Management – SAP Doc Example (Node.js) so it can be called with the require

some thing like this:

const {readCredential, writeCredential, deleteCredential} =  require('./utils/cred')

Here is the modified cred.js api that  gives the read/create/update/delete  credential elements from the credstore (again for the original – SAP Doc Example (Node.js)):

const jose = require('node-jose');
const fetch = require('node-fetch'); // use version 2  when working with commonJS modules 

const xsenv = require("@sap/xsenv");
const binding = xsenv.getServices({ credstore: { tag: 'credstore' } }).credstore;

function checkStatus(response) {
    if (!response.ok) console.log(`Please verify that you CredStore binding info is up to date
    Unexpected status code: ${response.status}`);
    return response;

async function decryptPayload(privateKey, payload) {
    const key = await jose.JWK.asKey(`-----BEGIN PRIVATE KEY-----${privateKey}-----END PRIVATE KEY-----`,
        { alg: "RSA-OAEP-256", enc: "A256GCM" }
    const decrypt = await jose.JWE.createDecrypt(key).decrypt(payload);
    const result = decrypt.plaintext.toString();
    return result;

async function encryptPayload(publicKey, payload) {
    const key = await jose.JWK.asKey(`-----BEGIN PUBLIC KEY-----${publicKey}-----END PUBLIC KEY-----`,
        { alg: "RSA-OAEP-256" }
    const options = {
        contentAlg: "A256GCM",
        compact: true,
        fields: { "iat": Math.round(new Date().getTime() / 1000) }
    return jose.JWE.createEncrypt(options, key).update(Buffer.from(payload, "utf8")).final();

function headers(binding, namespace, init) {
    const headers = new fetch.Headers(init);
    headers.set("Authorization", `Basic ${Buffer.from(`${binding.username}:${binding.password}`).toString("base64")}`);
    headers.set("sapcp-credstore-namespace", namespace);
    return headers;

async function fetchAndDecrypt(privateKey, url, method, headers, body) {
    return fetch(url, { method, headers, body })
        const resCheckStatus = checkStatus(resFetch)
    .then((response) => {
        const resText = response.text()
        return resText

        .then(payload => decryptPayload(privateKey, payload))
            `opps :( we have an issue: ${JSON.stringify(e)}`

async function readCredential(namespace, type, name) {
    const headersToSend = headers(binding, namespace)
    const resData = await fetchAndDecrypt(
async function writeCredential( namespace, type, credential) {
    const headersToSend = headers(binding, namespace, { "Content-Type": "application/jose" })
    const bodyToSend = await encryptPayload(binding.encryption.server_public_key, JSON.stringify(credential))
    const resData = await  fetchAndDecrypt(

    async function deleteCredential(namespace, type, name) {
        await fetch(
                method: "delete",
                headers: headers(binding, namespace)

module.exports = {readCredential, writeCredential, deleteCredential}

Now after calling this module, we can do all CRUD functionality on the Credential Store, for example:

    const {readCredential, writeCredential, deleteCredential} =  require('./utils/cred')
    const testItem = {
      name: "password1",
      value: "myVerySecretMessage",
      username: "user1",
      metadata: "if you see this text, the cred store is working :)"
    const writeRes = await writeCredential("test-ns", "password", testItem)
    const readRes = await readCredential("test-ns", "password", "password1")
    await writeCredential("test-ns", "password", testItem)
    const readAfterUpdate = await readCredential("test-ns", "password", "password1")
    await deleteCredential("test-ns", "password", "password1")
    res.json({writeRes, readRes, readAfterUpdate})

Finally! We can try to see if it all works. this is the result I got on the browser:

// http://localhost:4004/testCredStore

  "writeRes": {
    "id": "95dd3b54-0e3f-40e5-8a9f-517a01df5dd1",
    "name": "password1",
    "modifiedAt": "2022-06-27T12:38:08.540Z",
    "metadata": "if you see this text, the cred store is working :)",
    "status": "enabled",
    "username": "user1",
    "type": "password"
  "readRes": {
    "id": "95dd3b54-0e3f-40e5-8a9f-517a01df5dd1",
    "name": "password1",
    "modifiedAt": "2022-06-27T12:38:08.540Z",
    "metadata": "if you see this text, the cred store is working :)",
    "value": "myVerySecretMessage",
    "status": "enabled",
    "username": "user1",
    "type": "password"
  "readAfterUpdate": {
    "id": "19e64f55-a6b6-48dc-bd36-db638ef610cd",
    "name": "password1",
    "modifiedAt": "2022-06-27T12:38:09.688Z",
    "metadata": "if you see this text, the cred store is working :)",
    "value": "secret2",
    "status": "enabled",
    "username": "user1",
    "type": "password"

YAY  🎉🎉 we got the secret message from the nodeJS app in a secure way


More information about the SAP Cloud Platform Credential Store can be found here in the SAP Help Documentation: Credential Store – SAP Help Portal

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Uwe Kunzak
      Uwe Kunzak

      very nice explanation.
      thank you Shlomi


      Author's profile photo Slim TRABELSI
      Slim TRABELSI

      At SAP we have our own Open source Secret Code Scanner called Credential Digger  that detects non structured tokens like passwords

      Author's profile photo Ranjithkumar Ayyavu
      Ranjithkumar Ayyavu


      Thanks for sharing the information

      I have created the credentials store instance with plan type as "free". But getting "401" error.

      Author's profile photo MASSIMILIANO CARDOSI

      service plans available under BTP don't use Basic authentication, unfortunately: default authentication is "oauth:key" and client_id and client_secret are not generated when service is bound to an application, so question is: how to authenticate any API call NOT in Basic mode??
      Thanks, regards

      Author's profile photo Yann MIQUEL
      Yann MIQUEL

      you can choose basic if you want at the creation with this parameter

       "authentication": {
       "type": "basic"
      Author's profile photo Allen Gong
      Allen Gong

      If I understand the scenario correctly, we can store our secret data by "Credential Store" service on BTP, but my question is how we store the credential which we use it access to "Credential Store"? If someone steals it, he can use it access BTP service and get all your secret data, right?