Two flows for two different goals:
- Embed - show a dashboard filtered to one viewer, no login required.
- Login - log an SSO user in without a password prompt. SSO users are created with the Single Sign-On API.
Both: you sign a short-lived token with your own private key. Knowi holds only the public key.
They differ in one step: the embed flow also encrypts the signed token, because the URL carries row-level filters that should not be readable. Terminology:
-
JWT - the claims object (
aud,exp, etc.). - JWS - that JWT, signed (3 segments). Readable but tamper-proof.
- JWE - that JWS, encrypted (5 segments). Unreadable without the key.
"SSO" means three things on this page. (1) The older Single Sign-On API. (2) The login flow below (`aud: knowi:sso`). (3) Your SSO token in Settings > Account - a shared secret used by the embed flow as the encryption key. The login flow never touches it.
| Embed flow | Login flow | |
|---|---|---|
| You want | A share link filtered to one viewer, no login | To log an SSO user in |
aud |
knowi:embed |
knowi:sso |
| Token you produce | JWE - signed, then encrypted (5 segments) | JWS - signed only (3 segments) |
| Secrets you need | Your private key and your SSO token | Your private key only |
| Carries filters | Yes - contentFilters inside the token |
No - the user's own role decides what they see |
| Where it goes | Last path segment of the secure share URL |
token parameter of /sso/user/login
|
| Maximum lifetime | 24 hours | 30 minutes |
Everything through Building a token applies to both flows.
Migrating from an older method? Same endpoints, same JS API parameters. You just mint the value yourself instead of asking Knowi for it, and it now expires.
Setting up the key
Note: The JWT public key option is not available by default. Contact your Knowi account manager to have it enabled for your account.
To find the setting:
- Navigate to User Settings and open the Account Settings tab.
- In the Customer Settings section, find JWT public key. It shows Not configured until a key is added.
Then add a key in one of two ways.
Option 1: Generate Key Pair in Knowi
- Click Generate Key Pair.
- Select a Key type: EC P-256 (signs
ES256, recommended) or RSA 2048 (signsRS256). - Click Generate.
The key pair is created in your browser. The private key downloads as knowi-jwt-private-key.pem, and Knowi stores only the public key. The private key is never sent to Knowi and is offered for download only once, so store it securely on the server that will sign your tokens. If you lose it, generate a new pair.
Key generation requires HTTPS. If it is not available in your browser, generate the pair yourself and use Option 2.
Option 2: Upload Public Key
Generate a key pair on your own system, for example with openssl:
openssl ecparam -name prime256v1 -genkey -noout | openssl pkcs8 -topk8 -nocrypt -out private.pem openssl pkey -in private.pem -pubout -out public.pem
Then click Upload Public Key, choose public.pem or paste its contents, and click Upload. The key must start with -----BEGIN PUBLIC KEY-----. Supported: EC P-256/P-384/P-521 and RSA 2048+.
The algorithm follows from the key type: P-256 ES256, P-384 ES384, P-521 ES512, RSA RS256. Anything else is rejected.
Managing the key
Once a key is saved, the JWT public key row shows the signing algorithm and a shortened key fingerprint.
- Click the eye icon to show the full fingerprint and public key. Click the crossed-out eye icon to hide them.
- Click Revoke and confirm to remove the key.
If the account has no SSO token, the row also shows Embed URLs require both a JWT public key and an SSO token. JWT alone enables SSO login only. To use the embed flow, generate an SSO token as described in Single Sign-On API.
One key per account. Uploading a new key or revoking the current one invalidates every existing token immediately - saved JWT share groups included. Only you can reissue them.
The claims
aud decides which flow the token belongs to.
Embed (knowi:embed). Filters ride inside the token:
{
"aud": "knowi:embed",
"customerId": 1234,
"iat": 1791855015,
"exp": 1791941415, // iat + 86400 = 24 hours (the max)
"contentFilters": [
{"fieldName": "tenant_id", "operator": "Equals", "values": ["acme"]}
]
}
Login (knowi:sso). No filters; sub names the user to log in as:
{
"aud": "knowi:sso",
"customerId": 1234,
"sub": "$SSO_USERNAME",
"iat": 1791855015,
"exp": 1791856815 // iat + 1800 = 30 minutes (the max)
}
iat and exp are Unix seconds. Compute them at mint time; never hard-code. claims in the examples below means one of these two objects.
| Claim | Required | Notes |
|---|---|---|
aud |
yes |
knowi:embed or knowi:sso. A token minted for one is rejected by the other. |
customerId |
yes | Your Knowi customer id, included in the claims the Create/Test Secure Links dialog builds. |
iat / exp
|
yes | Seconds since epoch. Max lifetime: 24h for embed, 30 min for login. On-prem can adjust. |
sub |
SSO only | Username of the SSO user to log in as. The user must already exist and must have been created with /sso/user/create. Regular Knowi users, such as users invited from User Settings, cannot log in with a JWT. |
contentFilters |
embed only | Row filters. Only take effect if signed into the token - see below. |
ctx |
optional | Opaque string passed through to the dashboard as share context. |
Filters must be signed into the token. The rows in the Create/Test Secure Links dialog only build the claim for you to copy - they are not sent to Knowi. Change the rows after signing > re-sign. No
contentFiltersat all > unfiltered data.
Building a token
One step for login, two for embed:
| Step | Key used | What it achieves | Embed flow | Login flow |
|---|---|---|---|---|
| 1. Sign (JWS) | your private key | Proves the claims are yours and unaltered. | yes | yes |
| 2. Encrypt (JWE) | your SSO token, as SHA-256(ssoToken)
|
Hides the claims and ties the link to a second server-side secret. | yes | no |
Why embed gets step 2: the URL is the credential and lives in browser history for up to 24h, with claims that name which rows to show. Encryption keeps them unreadable. A login token goes straight back to Knowi and carries nothing confidential, so signing alone is enough.
Your SSO token is in Settings > Account. Keep it server-side - it decrypts the embed token.
Python:
# pip install pyjwt cryptography jwcrypto
import jwt, base64, hashlib, json, time
from jwcrypto import jwe as jwe_mod, jwk
now = int(time.time())
claims = {
"aud": "knowi:embed",
"customerId": 1234,
"iat": now,
"exp": now + 86400, # any value up to the 24h maximum
"contentFilters": [
{"fieldName": "tenant_id", "operator": "Equals", "values": ["acme"]}
],
}
signed = jwt.encode(claims, private_key_pem, algorithm="ES256")
# For an SSO login token you are done here: `signed` is the token you send.
# Step 2 below is for embed tokens only. It wraps `signed` in an encrypted envelope.
cek = hashlib.sha256(sso_token.encode("utf-8")).digest()
key = jwk.JWK(kty="oct", k=base64.urlsafe_b64encode(cek).rstrip(b"=").decode())
token = jwe_mod.JWE(signed.encode(), json.dumps({"alg": "dir", "enc": "A256GCM"}))
token.add_recipient(key)
blob = token.serialize(compact=True)
print(blob) # 5 dot-separated segmentsNode.js:
// npm i jose
import { SignJWT, CompactEncrypt } from 'jose';
import { createPrivateKey, createHash } from 'crypto';
const now = Math.floor(Date.now() / 1000);
const claims = {
aud: 'knowi:embed',
customerId: 1234,
iat: now,
exp: now + 86400, // any value up to the 24h maximum
contentFilters: [
{fieldName: 'tenant_id', operator: 'Equals', values: ['acme']}
]
};
const key = createPrivateKey(privateKeyPem);
const jwt = await new SignJWT(claims)
.setProtectedHeader({ alg: 'ES256' }) // must match your key type
.sign(key);
// For an SSO login token you are done here: `jwt` is the token you send.
// Step 2 below is for embed tokens only. It wraps `jwt` in an encrypted envelope.
const cek = createHash('sha256').update(ssoToken, 'utf8').digest();
const blob = await new CompactEncrypt(new TextEncoder().encode(jwt))
.setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
.encrypt(cek);
console.log(blob); // 5 dot-separated segmentsJava:
// nimbus-jose-jwt
SignedJWT jwt = new SignedJWT(new JWSHeader(JWSAlgorithm.ES256), claims);
jwt.sign(new ECDSASigner(privateKey)); // RSASSASigner for an RSA key
// For an SSO login token you are done here: jwt.serialize() is the token you send.
// Step 2 below is for embed tokens only. It wraps the signed JWT in an encrypted envelope.
byte[] cek = MessageDigest.getInstance("SHA-256")
.digest(ssoToken.getBytes(StandardCharsets.UTF_8));
JWEObject jwe = new JWEObject(
new JWEHeader(JWEAlgorithm.DIR, EncryptionMethod.A256GCM),
new Payload(jwt.serialize()));
jwe.encrypt(new DirectEncrypter(cek));
String blob = jwe.serialize();
These samples mint an embed token (both steps). For a login token: set aud to knowi:sso, add sub, drop contentFilters, and stop after the signing call.
JWE details: alg=dir, enc=A256GCM, compact serialization (RFC 7516). The key derivation (SHA-256(ssoToken)) is the only Knowi-specific part.
Using an embed token
Embed links are created from the sharing settings of a dashboard or widget. JWT mode is available only once a JWT public key is set up (see Setting up the key).
- Open the sharing settings:
- Dashboard: click the menu icon in the top right of the dashboard and select Sharing Options.
- Widget: open the widget's Share option.
- Check Secure Shareable URL and click Save Changes. Secure links do not load until this setting is saved.
- Click Create/Test Secure Links.
- At the top of the dialog, select JWT. The dialog always opens on Knowi-encrypted.
- Under Filters, add a row for each filter (field, operator, value). Click Add New Param to add more rows.
- Click Build the claims to sign, then copy the Claims with the copy icon. If no filter rows are set, the dialog warns that a link signed from these claims shows unfiltered data.
- In your own service, sign and encrypt the claims as described in Building a token. The dialog shows which algorithm to sign with, for example Sign with ES256.
- Paste the token into the token box. Knowi checks it immediately and shows the result below the box, for example Token checked. Expires in 60 min. Valid for secure embed URLs (aud=knowi:embed). Click What's in this token? to see the filters the token carries.
- Click Generate Secure Link. The link appears under Secure Share URL. Click the copy icon to copy it.
- (Optional) Click Save Parameters to keep the group for the next time you open the dialog. See Testing a token.
The dialog is for building claims and testing. In production, have your service sign a fresh token each time it renders the page, rather than storing one link in static HTML, because the link expires when the token does.
The resulting link:
https://www.knowi.com/share/secure/{secureShareUrl}/{token}
Same hash parameter as Secure URL Embed (Knowi-encrypted in the UI):
Knowi.render('#container', {
type: 'secure',
url: 'https://www.knowi.com/',
dashboard: '{secureShareUrl}',
hash: blob // your JWE goes where the Knowi-encrypted hash would
});
Using a login token
Only SSO users can log in with a JWT. A regular Knowi user is rejected with BAD_CREDENTIALS, even if the username matches.
Create the SSO user once (this is the only place in the login flow where the SSO token appears):
POST https://www.knowi.com/sso/user/create user=<sub>&ssoCustomerToken=<your SSO token>&role=user
Then on every login, redirect the browser (no server call needed):
https://www.knowi.com/sso/user/login?token={jwt}
Append &dashboardId=123 to land on a specific dashboard. With the JS Embed API:
Knowi.render('#container', {
type: 'single', // GET: builds sso/user/login?token=...
url: 'https://www.knowi.com/',
token: signedJwt, // a fresh knowi:sso JWT, injected server-side
dashboardId: 123
});
type: 'single' sends a GET (token in the URL); type: 'sso' sends a POST (token in the body). Same endpoint. Prefer sso.
Testing a token
Paste a token into the Create/Test Secure Links dialog in JWT mode (see Using an embed token). Knowi checks it immediately: bad signature, failed decrypt, expired, time remaining, or missing filters. What's in this token? shows the filters it really carries.
Save is optional. The link works as soon as it appears. Save stores the group so reopening the dialog restores it. Each method (Knowi-encrypted / JWT) keeps its own groups; one Save stores both.
What gets stored. Embed tokens only (login tokens are never stored). The token is kept encrypted with your SSO token, re-checked on open. Your private key is never sent to Knowi. If you prefer Knowi holds nothing, skip Save.
Troubleshooting
| Message | Cause | Fix |
|---|---|---|
JWT has expired. |
exp is in the past. |
Mint a fresh token. |
JWT signature verification failed. |
Private key does not match the uploaded public key. | Sign with the matching private key. |
JWT algorithm mismatch |
The alg header does not match the key type. |
Use the algorithm the key implies - P-256 is ES256. |
JWE decryption failed |
SSO token mismatch or token altered in transit. | Re-encrypt with the current SSO token. Check for stray spaces or truncation if copied by hand. |
JWT audience does not include |
The token was minted for the other flow, for example an embed token sent to /sso/user/login. |
Use knowi:sso for login tokens and knowi:embed for embed links. |
exceeds the maximum |
Lifetime is over the cap for that audience. | Keep exp - iat within 24h (embed) or 30 min (login). On-prem can adjust. |
BAD_CREDENTIALS at /sso/user/login
|
The token is valid, but the user named in sub was not found: it does not exist, is not an SSO user, belongs to a different customer, or is deactivated. Signature, expiry, lifetime, and aud problems return their own messages instead. |
Create the user with /sso/user/create and check that sub and customerId match it. |
| Link loads but shows all rows | The token carried no contentFilters. |
Add filters to the claims and re-sign. |
DECODER routines::unsupported (Node) |
PEM is empty or not a private key. | Check the path and that the file starts with -----BEGIN PRIVATE KEY-----. |
| Login cookie dropped when testing locally |
file:// framing http://localhost; cookie blocked as cross-site. |
Serve the test page from the Knowi origin so the two share a site. HTTPS hosts are unaffected. |