PHP embedding with SECRET (HOW TO?)

Hi people,

Having recently discovered this awesome piece of open source software, we have used it to create multiple ā€˜Questionsā€™ and have created a dashboard.

Our main application is based on PHP backend with HTML front (smarty v2)

How can I edit your embed code for PHP purposes?

Thanks for reading :slight_smile:

If you need more info please let me know

ā€“EDITā€“

The embed code I am referring to is the one that requires secret key etc

bump?? if anyone has info to lead me on the right track i would appreciate it

Iā€™m not going to be too much help here, but have you looked at our reference apps yet?

Yeah i have, all stuff that isnā€™t that helpful. more rails and other frameworks, thank you tho

Iā€™ve implemented the embed code in a PHP application (a Laravel application):

  1. You need a jwt-package (I used https://github.com/lcobucci/jwt)
  2. In my application Iā€™ve signed the token with the following code and passed the iframe URL to my view
        $metabaseSiteUrl = 'http://your-metabase-installation.com';
        $metabaseSecretKey = 'YOUR_SECRET_KEY';

        $signer = new \Lcobucci\JWT\Signer\Hmac\Sha256();
        $token = (new \Lcobucci\JWT\Builder())
            ->set('resource', ['dashboard' => 1])
            ->set('params', ['your_custom_param' => 0])
            ->sign($signer, $metabaseSecretKey)
            ->getToken();

        $iframeUrl = "$metabaseSiteUrl/embed/dashboard/$token#bordered=true&titled=true";

In my HTML view I load the iframe like this:

    <iframe
        src="<?php echo $iframeUrl ?>" frameborder="0"
        width="100%"
        height="800"
        allowtransparency
    ></iframe>

Thanks man, I completely forgot I wrote this post!!

I will try your method, thanks again!

No problem :slight_smile:

Iā€™ve also open a PR with an example implementation in Laravel at the offical demo-repo if youā€™re interested: https://github.com/metabase/embedding-reference-apps/pull/1

Thanks for that, that saved me a lot of time.
One problem I am still running into though is if I try to embed a dashboard or question without parameters - does that work for you?

@stefanzweifel

Thanks for all your hard work with Metabase in getting the Laravel reference app in place!

Would this part of your PHP embed code work in a WordPress (WP) application? Being that itā€™s in PHP it would seem as if it would.

To provide my users with different levels of service per what they have signed up for, it appears necessary to apply secured embedding as described in the Embedding Metabase in other applications documentation.

My guess is Iā€™m going to have to create what WP calls a ā€œmust-useā€ plugin and somehow use a library or built-in capability directly or perhaps use another plugin (JWT Authentication for WP REST API?) to do what https://github.com/lcobucci/jwt does in your code.

But knowing only enough about all these various pieces to get by, Iā€™m struggling mightily with how to pull it all together. Any direction and or references to resources you could provide would be appreciated. Thanks!

Hey @mesquest

I havenā€™t touched Wordpress in a long time, but the code youā€™ve linked to (the thing in routes/web.php) should work in Wordpress to. The important part is, that use a JWT package/plugin (I work mostly in Laravel which uses composer to install packages from packagist).

I think the plugin you linked to (JWT Authentication for WP REST API) is not exactly the right thing. As I understand by quickly scanning the documentation, this just adds the possibility to authenticate via JWT with the Wordpress REST API.

I would suggest downloading lcobucci/jwt and then using it in a Wordpress plugin. (Itā€™s the same package which is used in the reference app).

The code should probaly look something like this:


use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha256;


function name_of_your_wordpress_function() {

    $metabaseSiteUrl = 'URL'; // URL to your Metabase installation
    $metabaseSecretKey = 'SECRET_KEY'; // The Secret Key you get from your ;etabase installation
    $signer = new Sha256();
    $token = (new Builder())
        ->set('resource', [
            'dashboard' => 1 // ID of the dashboard you want to implement
        ])
        ->set('params', [
            'params' => []
        ])
        ->sign($signer, $metabaseSecretKey)
        ->getToken();
    $iframeUrl = "{$metabaseSiteUrl}/embed/dashboard/{$token}#bordered=true&titled=true";

   // Somewhere you would have to create a new iframe like this:


  echo "<iframe src=\"$iframeUrl\" >";

}

If you want to embed something different, look at the examples in the reference app.

Best place for more information is as always the documentation.

Good luck :slight_smile:

@stefanzweifel
Thank you so much! A WP function! That meshes with what Iā€™ve been reading alright! That plugin and GitHubā€™s lcobucci/jwt are the same author I think, so thatā€™s encouraging. Thanks again for being generous with you time and talents, much appreciated!

WP function, plugin whatever suits you :wink:
Yeah, the plugin is the same. I wanted to link to it, but this forum prevents me from linking to anything and marks me as spam. :roll_eyes:

@stefanzweifel

Correction; got it to work!.. Re-installed from scratch and got the site config file to pick up the composer generated autoload file. Must have rearranged the file structure per the ā€œuseā€ statement not understanding that itā€™s a namespace not a pathā€¦ To get Metabase, Wordpress, Windows, PHP, Apache et. al., MySQL, Composer, lcobucci/jwt, etc. to all play nice together is a VERY messy business to say the least!

The next big challenge is how to divide users up into groups A, B, & C and make sure each can only see the charts & dashboards they are meant to see.

|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

How does one go from the class related namespace:
use Lcobucci\JWT\Signer\Hmac\Sha256;
to using it in a function:
$signer = new Sha256();

Keep getting this error which refers to the above two lines:
Fatal error: Uncaught Error: Class ā€˜Lcobucci\JWT\Signer\Hmac\Sha256ā€™ not found in C:\Users\Mesquest\Documents\Websites\www.example.dev.cc\wp-content\mu-plugins\MetabasePlugin.php:17

Where the Sha256.php is located at
C:\Users\Mesquest\Documents\Websites\www.example.dev.cc\wp-content\mu-plugins\Lcobucci\JWT\Signer\Hmac\Sha256.php

Does it have anything to with the Composer generated autoload.php located at:
C:\Users\Mesquest\Documents\Websites\www.example.dev.cc\wp-content\mu-plugins\Lcobucci\vendor\autoload.php

Happy you get it to work.
When other would find this thread I just wanted to point out that you have to import the autoload.php file, which composer generates for you, somehwere in your code

require __DIR__ . '/vendor/autoload.php';

Yeah, modern web development can be quite complex, but sometimes you have to see it from a different angle: With the embedded Metabase Dashboards and Questions youā€™re basically importing a different application into your existing site. This has to be secure, that why you use something like jwt.
composer is ā€œjustā€ the package manager, so you donā€™t have to search a download link, upload it to the server and make sure everything is tied together properly.
Wordpress, Windows, PHP, Apachae and MySQL are all massive projects which all just want to help you.

So yeah, welcome to web development 2018 :upside_down_face:

1 Like

@stefanzweifel, How can a user be prevented from simply copying the token generated link in the Laravel reference app with a tool like Mozilla Firefoxā€™s Inspect Element and sharing it completely outside of the app with whomever they like?

Looking at the way tokens are generated would some sort of expiration mechanism work rather than anything based on say client session, machine, or some such, is that the right approach?

What topics should I research to find resources on this kind of thing?

As you said, the $token is built via the Builder class from the JWT package. JWT stands for JSON Web Token and is a way to sign a URL (Similar to an API key).

These are some articles which describe JWT a bit better:

Iā€™ve tried just copying the iframe of dashboard from our production app into Codepen and it actually loads the entire dashboard.

I think JWT supports something like an Expiration Date, but I donā€™t know how to implement that. The metabase app probably would have to check that too ā€¦ :thinking:, but this wouldnā€™t solve the issue you described completely.

A user still could copy/paste the iframe code. You would have to set the expire date of the JWT token to ā€œsecondsā€, so that the iframe URL changes for every request.

Sorry I canā€™t help you here. Iā€™m just not an expert in JWT or how the embedding in Metabase works under the hood.

Thank you @stefanzweifel! That confirms my understanding. Per the lcobucci/jwt v3.2 documentation under ā€œToken signature>Hmacā€ I have implemented token expiration with the following modifications in bold:

$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 dashboard you want to implement
])
->set(ā€˜paramsā€™, [
ā€˜paramsā€™ => [] // Corresponds to Metabase locked parameters
])
->sign($signer, $metabaseSecretKey)
->getToken();

Although a significant deterrent to would-be unauthorized access it is, as youā€™ve noted, not a totally satisfactory solution.

For one it is not advisable to set the expiration time to something too short because upon expiration the embedā€™s Metabase filters become disabled and the entire embed disappears returning a token expired message. On the other hand setting the expiration even to something reasonable like 1 hour as shown here (3600 seconds) can still be annoying (imagine users in the middle of a conference presentation or a big board meeting and the expiration surprise suddenly scuttles the whole thing)! While I like the idea of Sliding Sessions to get past this Iā€™m not so keen on the Refresh Tokens requirement behind them or the ways of tweaking them as pointed out by lcobucci/jwtā€™s creator.

For another even with an expiration time of one hour thatā€™s one hour any determined party can gain unauthorized access. Metabaseā€™s powerful locked parameters feature can be used to provide user specific content provided the user or group of users is a filterable category in the data itself. But that is not intended as a security guarantee, any one of a group of users could share a token-ized url with parties outside the group.

The only way around this is to lock certain key parameters thereby removing them from the iframe entirely (e.g. products and geography) to vastly limit access. But that means oneā€™s app must feed these in directly via a dropdown completely outside of Metabase, for example. And that may be burdensome not to mention annoying for legitimate users if it implies having to constantly refresh and reload the iframe and loose all previously set Metabase filters.

Providing each of the appā€™s users with a unique key that is revoked when accessed by more than one party simultaneously, more than a couple of devices, or something of that nature may work. But Iā€™m afraid I havenā€™t even the vocabulary to start researching such a thing.

So for the time being, expiring tokens will just have to do.

2 Likes

we are another happy metabase user considering embedding, stuck at the same problem. :frowning:

I recently had to implement this feature in PHP. This is the method I have come up with (PHP 8 & lcobucci/jwt: 4.1)

	private function generateToken(int $dashboardId, array $config): string
	{
		$key = $config['key'] ?? '';

		$configuration = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($key));
		$token = $configuration->builder();

		// Expiration: Now + tokenLifetime minutes (DateTimeImmutable, default: 10 minutes).
		$token->expiresAt(new \DateTimeImmutable('+' . $config['tokenLifetime'] . ' minutes'));

		// Issued at: DateTimeImmutable, now.
		$token->issuedAt(new \DateTimeImmutable());

		$payload = [
			'resource' => [
				'dashboard' => $dashboardId,
			],
			'params' => new \ArrayObject(),
			// Note: Just putting `[]` here does not work (to create an empty object)
			// as `[]` would be serialized into an array, therefore we need to use ArrayObject.
		];

		foreach ($payload as $key => $value) {
			$token->withClaim($key, $value);
		}

		return $token->getToken(
			$configuration->signer(),
			$configuration->signingKey()
		)
			->toString();
	}
<?php use DateTimeImmutable; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Hmac\Sha256; $metabaseSiteUrl = 'METABASE_SITE_URL'; $metabaseSecretKey = 'METABASE_SECRET_KEY'; $now = new DateTimeImmutable(); $config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($metabaseSecretKey)); $token = $config->builder() ->issuedBy($metabaseSiteUrl) ->withHeader('iss', $metabaseSiteUrl) ->permittedFor($metabaseSiteUrl) ->issuedAt($now) ->canOnlyBeUsedAfter($now->modify('+1 minute')) ->expiresAt($now->modify('+1 hour')) ->withClaim('resource', ['question' => 15]) //METABASE resource (question, card, dashboard) ->withClaim('params', ['cod_usuario' => 1001]) //METABASE params variables metabase ->getToken($config->signer(), $config->signingKey()); $iframeUrl = $metabaseSiteUrl."/embed/question/".$token->toString()."#bordered=true&titled=true"; ?>