What’s the best way to prevent an app user from just copying a secure embed’s token generated link with a tool like Mozilla Firefox’s Inspect Element and sharing it with whomever they like?
|||||||||||||||||||||||||||||||||||||||| Not solved 07/18/2018
While password login protection of the root or sub-domain may work for limiting access to embed app users there is nothing (that I can see) preventing them from simply coping a “secure” embed’s token-ized url and sharing it. A user with a higher level of information access, for example, could share with users having a lower level of access. Expiring tokens as described below remains the best practice, but it isn’t a solution.
Posted to github as issue #8111, we’ll see what comes of it.
|||||||||||||||||||||||||||||||||||||||| research shared 07/10/2018
root domain: iframe src=“https:// rootdomain/embed/question/eyJ0eXAi…tVIOz5Eo”
page: iframe src=“https:// rootdomain/page/embed/question/eyJ0eXAi…tVIOz5Eo” – won’t work, Admin>General> Site URL field only takes a single entry
subdomain: iframe src=“https:// subdomain.rootdomain/embed/question/eyJ0eXAi…tVIOz5Eo”
As specified by the $metabaseSiteUrl variable in the code below and enforced by the app login… One login protected area per user service level, tier, or plan, for example.
So simple! Normally that should do it. If not or if there are other issues to consider, please feel free to comment. – Not so simple after all.
Definitely expire tokens, however, limit to a few hours at most otherwise they’re valid basically forever. And rotate the $metabaseSecretKey variable as needed (infrequent as this requires the republishing of ALL embeds).
So much for all this falderal! Well I’m feeling rather sheepish! At the very least the code below and the whole concept of expiring tokens will help others along the way who, like myself, may never have intended to become web app developers!
PS – Bonus tip – If you are using Metabase on your local machine along with a website testing environment employing something like xampp to setup example websites make sure you switch out “localhost” for your example root domain. In the case of a public question, for example,:
And for signed embeds use the $metabaseSiteUrl variable in the code below to do essentially the same thing.
For one it’s good practice for when you do go live and for another you may avoid cross-domain type issues when testing, for example, methods of listening for events and actions within the iframe that need to be passed up to the page in which iframe is embedded.
|||||||||||||||||||||||||||||||||||||||| Shared Research 07/09/2018
Please feel free to clarify, correct, or build upon the following as if any of the below is at all correct it may affect all Metabase app embed users. I have implemented expiring tokens but that does not totally solve the issue unless they can be made very short-lived (see 1c below). I am by no means an expert in any of these areas and would really appreciate the forum’s input before turning to Metabase’s github forum for a more formal exploration of this issue.
Metabase supports JWT in such a way as to ecode (for signing) not encrypt, there’s a difference. The former can be compromised to expose, for example, the resource (card or dashboard) id and locked parameters as suggested by this github post. That said the following does NOT address this directly but rather is concerned with preventing users from copy-pasting a signed embed’s token-ized iframe src url and sharing it with whomever they like.
- Expiration – The first and best method is to set JWT expiration which is easily done by specifying claims with a few lines of code such as shown below and modeled after the Laravel (PHP) Metabase example reference app using the lcobucci/jwt library:
$metabaseSiteUrl = 'https://rootdomain.com/'; // Url of the Metabase installation ( $metabaseSecretKey = '08c0a...6b3ec'; // Secret key generated by Metabase installation $signer = new Sha256(); $token = (new Builder()) ->setIssuedAt(time()) // Configures the time that the token was issue (iat claim) ->setNotBefore(time() + 60) // Configures the time that the token can be used (nbf claim) ->setExpiration(time() + 3600) // Configures the expiration time of the token (exp claim) ->set('resource', [ 'dashboard' => 1 // ID of the card or dashboard resource to implement ]) ->set('params', [ 'params' =>  // Corresponds to Metabase locked parameters (e.g. userid or some other filterable data category that can serve as a locked parameter) ]) ->sign($signer, $metabaseSecretKey) ->getToken();
This has it’s own issues, however.
a) The expiration period must be reasonable (15min, 1hr, etc.) as upon expiration embeds return a token expiration error and a new token must be issued (e.g. the end user must re-select the resource thereby refreshing the iframe). The problem with that is:
- It's annoying if not disruptive for app users requiring longer periods (presentations, meetings, etc.),
- If that means reloading the iframe (or the page) all the user's previously selected filter settings are lost,
- MOST importantly, the more sensitive the information the shorter the time period must be as however long the token is valid is however long sensitive information is exposed for exploitation. A savvy bad-actor, for example, could conceivably programmatically harvest token-ized iframe urls periodically within the expiration timeframe so as to always have a fresh url on hand and act with impunity.
b) Refresh tokens, sliding sessions, token revokation/blacklisting, and so forth are areas unto themselves and tend to become very complicated very fast.
c) Timing is everything, even by using methods to detect click or change actions within the iframe, reissuing an extremely short-lived token (expiration set to a few seconds) and replacing the token-ized iframe src url in the page must precede the client submission of the corresponding click or change request to the server in such a way as to NOT refresh the iframe or page if a seamless user experience is desired.
Obfuscation – While there are ways to make a webpage’s code somewhat harder to reverse engineer there is no way to make a webpage’s code unreadable or otherwise unusable.
Substitution – Substituting a variable for the primary iframe src url, refering to it using a function, posting it via ajax, etc. is a matter of semantics. In the end it must be rendered and viewable for a webpage to use it. And it is echoed at least twice in the iframe’s rendered embed code.
Single Sign-on Embedding – That only applies to a paid hosted service where access and security are handled by the analytics vendor where clients can embed in their own apps to give their end users various levels of service (and that has it’s own issues and implications).
Feel free to chime in, as again if any of the above is at all correct it probably affects all Metabase app embed users.