Security Bearer Tokens

Overview

A security bearer token is a type of access token commonly used in authentication protocols like OAuth 2.0. It is essentially a piece of data that grants access to a resource or service. The term "bearer" implies that whoever possesses the token can access the associated resources without further authentication.

Sisense bearer tokens are issued by an authentication server after a user successfully logs in or grants permission to the application. These tokens are then sent along with each request to access protected resources. The server receiving the request verifies the token's authenticity and permissions before granting access.

One of the primary advantages of bearer tokens is their simplicity and efficiency. Since the token itself carries all the necessary information for authorization, there is no need for the server to maintain session state or look up tokens in a database for each request. However, this simplicity also means that whoever possesses the token effectively has the same level of access as the original authenticated user, which underscores the importance of securely handling and transmitting bearer tokens to prevent unauthorized access.

Authentication and Expiration

The Security Bearer Token has been in Sisense since the product inception. However, in order to ensure high security standards, setting an expiration date is now enforced as part of the Bearer Authentication feature. The default setting (enabled/disabled) depends on the type of installation/upgrade performed; see Default Setting Per Installation/Upgrade Type. After upgrading to version L2024.1 or newer of Sisense, if this feature is enabled, all tokens that are currently in use and do not have an expiration date set will expire immediately. This means that custom integrations with your apps (such as embedding) which use tokens with no expiration, after upgrading Sisense, will no longer work until new tokens are created or the existing tokens are renewed. Post-upgrade, if this feature is enabled, the expirations will depend on cookie or session settings in Session Management. If a security bearer token is expired / not valid / not provided, then requests will not be authorized in Sisense and permissions/access will not be granted.

Default Setting Per Installation/Upgrade Type

The default setting for the expiration date feature depends on the type of installation/upgrade performed:

  • Fresh install of L2024.1+: The expiration date feature is enabled by default.

  • In-place upgrade:

    • From a pre-L2024 release to an L2024.1+ release: The expiration date feature is disabled - bearers are processed as they were processed before the upgrade.

    • From an L2024.1+ release to a newer release: The expiration date feature remains in the same state as it was before the upgrade.

  • Side by Side upgrade:

    • From a pre-L2024 release to a new L2024.1+ release: The expiration date feature is enabled;

    • From an L2024.1+ release to a newer release: The expiration date feature remains in the same state as it was before the upgrade (i.e., as in the backed-up instance).

If an upgrade to the enabled feature state causes any issues with your implementation, you can run the following command to temporarily disable it until the necessary adjustments are made (as explained below):

Copy
NAMESPACE=$(kubectl get all -A -l=sisense-version --no-headers | awk '{print $1}' | uniq)
Copy
zookeeperPod=$(kubectl -n "${NAMESPACE}" get pods -l app=zookeeper -o name | head -n 1)
Copy
kubectl exec -n "${NAMESPACE}" ${zookeeperPod} -- /opt/bitnami/zookeeper/bin/zkCli.sh set /Sisense/S1/configuration/production/base/authentication.apiTokenExpiration "false"

Actualizing the Sisense Bearer

Before enabling this feature, make sure to actualize the Sisense bearer in one of the two following possible ways:

  • Actualize the bearer every N minutes to make sure that there is a valid token by sending a GET request at /api/v1/authentication/tokens/api

  • Store the admin’s credentials in your application and use these credentials to retrieve the admin’s bearer by sending a POST request at /api/v1/authentication/login

Sample Implementation and Usage

Sample Actualization

  • GET at /api/v1/authentication/tokens/api

To utilize this approach you must have a valid bearer stored in the web-application database. Your application should have permission to update this bearer in the web application database.

Sample of the Service:

Copy
//API request service
class SisenseClient {
    constructor(url) {
        this.sisenseUrl = url;
        getBearerFromDB().then((bearer) => {
            this.bearer = bearer;
        })

        function getBearerFromDB() {
            //get bearer from your web-application database;
        }

        function saveBearerInDB(newBearer) {
            //save bearer in your web-application database;
        }

        this.getNewBearer = () => {
            return this.request(`/api/v1/authentication/tokens/api`, {
                method: 'GET',
                headers: {
                    authorization: `Bearer ${this.bearer}`
                }
            })
        }

        this.request = async (requestPath, opts = {}) => {
            if (opts.headers) {
                opts.headers.authorization = `Bearer ${this.bearer}`;
            } else {
                opts.headers = {
                    authorization: `Bearer ${this.bearer}`
                };
            }
            return new Promise((resolve, reject) => {
                fetch(`${this.sisenseUrl}${requestPath}`, opts).then((response) => {
                    if (response.status === 401) {
                        reject({error: 'InvalidSessionBearer'})
                    } else {
                        resolve(response);
                    }
                })
            })
        }

        this.getBearerAndSaveInDB = () => {
            this.getNewBearer().then((response) => {
                response.json().then((data) => {
                    const { token } = data;
                    this.bearer = token;
                    return saveBearerInDB(token);
                })
            })            
        }
    }
}

In this service, you must implement the following two functions for operations to update/read the bearer in/from your database:

  • getBearerFromDB

  • saveBearerInDB

This service should be called only once when your application is initialized.

Sample of the Service Initialization:

Validate the expiration date and set the "synchronizationInterval" variable accordingly.

Copy
const synchronizationInterval = 120; //Period of the token's update in minutes
const sisenseUrl = 'https://example.sisense.com'; //sisense Url

const sisenseClient = new SisenseClient(sisenseUrl); //Initializing this service

setInterval(() => {
    sisenseClient.getBearerAndSaveInDB();
}, synchronizationInterval * 60000); //Creating function to update bearer

This approach requires a valid admin’s bearer at the moment of initialization service SisenseClient.

  • POST at /api/v1/authentication/login

Sample of the Service:

Copy
class SisenseClient {
    constructor(url) {
        this.sisenseUrl = url;
        this.getCredentialsAndBearer = () => {
            return new Promise((resolve) => {
                getCredentials().then((credentials) => {
                    this.getNewBearer(credentials).then((response) => {
                        response.json().then((authenticationData) => {
                            const { access_token } = authenticationData;
                            this.bearer = access_token;
                            resolve();
                        })
                    }).catch((err) => {
                        reject(err);
                    })
                }) 
            })
        }
        
        this.getCredentialsAndBearer();

        function getCredentials() {
            //get admin's credentials from your web-application database;
        }

        this.getNewBearer = (credentials) => {
            return this.request(`/api/v1/authentication/login`, {
                method: 'POST',
                headers: {
                    "Content-Type": 'application/json'
                },
                body: JSON.stringify(credentials)
            }, false)
        }

        this.request = async (requestPath, opts = {}, appendBearer = true) => {
            if (appendBearer) {
                if (opts.headers) {
                    opts.headers.authorization = `Bearer ${this.bearer}`;
                } else {
                    opts.headers = {
                        authorization: `Bearer ${this.bearer}`
                    };
                }
            }
            return new Promise((resolve, reject) => {
                fetch(`${this.sisenseUrl}${requestPath}`, opts).then((response) => {
                    if (response.status === 401) {
                        if (!appendBearer) {
                            reject({error: 'InvalidAdminsCredentials'})
                        } else {
                            this.getCredentialsAndBearer().then(() => {
                                fetch(`${this.sisenseUrl}${requestPath}`, opts).then((response) => {
                                    resolve(response);
                                })
                            }).catch((err) => {
                                console.error(err);
                                reject(err);
                            })
                        }
                    } else {
                        resolve(response);
                    }
                })
            })
        }
    }
}

In this service, you must implement a function getCredentials(), that should return valid admin credentials from your web application’s database.

This service should be called only once when your application is initialized.

Sample of the Service Initialization:

Copy
const sisenseUrl = 'https://example.sisense.com'; //sisense Url

const sisenseClient = new SisenseClient(sisenseUrl); //Initializing this service

Once the bearer becomes invalid, this service will get a 401 response. After this, this service will reuse the credentials to regenerate the bearer.

Copy
db.getCollection(‘users’).find({“secrets.tokens.api”: {$exists: true}}).count();

Sample Usage (applicable to both services):

  • GET request

Copy
sisenseClient.request('/api/users/loggedin').then((response) => {
    response.json().then((user) => {
        console.log(user); //Returns the currently logged in user
    })
})
  • POST request

Copy
sisenseClient.request('/api/users', {
    method: 'POST',
    headers: {
        "Content-Type": 'application/json'
    },
    data: JSON.stringify(user) //[user] is the user’s object that should be created in Sisense.
}).then((response) => {
    response.json().then((user) => {
        console.log(user);
    })
})

These services provide two main functions:

  • Regenerate bearer

  • Run API request against Sisense

The first service regenerates the bearer every N minutes, where N can be defined. It requires a valid bearer to be stored in the web application database during the initialization. The second service generates the bearer on the initialization and regenerates a new bearer once it gets a 401 response from Sisense. This solution requires valid credentials to be stored in your web application database. Depending on the requirements, you can choose either of these approaches (or create your flow by combining these methods).