JWT SSO Implementation

Developer-focused reference

This guide details how to implement JWT-based authentication for the Partner Fleet In-App Marketplace. Your application generates a signed JWT containing user and account context, and passes it to Partner Fleet via SSO. Partner Fleet uses the claims in the token to identify the user, personalize which listings they see (Audience Filtering), decide whether they can install (Permissions), and route them to the right destination on click.

📘

Where the JWT fits in the setup

The JWT identifies the user and their account when the marketplace loads. Field names and values you include here must match the configuration in your Partner Fleet admin — specifically Settings > In-App Marketplace > Permissions and Audience Filtering. Coordinate exact strings with the team configuring those tabs.


Note about integration status:

Integration status (Active, Pending, Error, etc.) is not passed via the JWT. It is passed through the embed script's integrations array when the iframe initializes. See the Embed Script Implementation guide for details. A future API route will provide an additional way to pass status — the embed script approach is the current canonical method.

Authentication flow

At a high level:

  1. Your end user navigates to the page in your application that hosts the embedded marketplace.
  2. Your frontend calls a backend endpoint to generate a fresh JWT for the current user.
  3. Your backend signs the JWT with the shared secret configured in Partner Fleet (Settings > In-App Marketplace > Developer Settings > JWT Identity Provider).
  4. Your frontend passes the JWT to the marketplace embed via the iframe initialization.
  5. Partner Fleet verifies the signature, decodes the claims, and renders the marketplace personalized for the user.

JWT Structure

The JWT payload requires specific fields for proper user and account identification:

{
  // Required User Fields
  "email": "[email protected]",     // Unique identifier for the user
  "ufn": "FirstName",              // User's first name
  "uln": "LastName",               // User's last name

  // Optional User Custom Fields
  "fields": {
    "user_type": "Admin"           // Maps to your user-level permissions
  },

  // Required Account Information
  "account": {
    "external_id": "123456789",    // Unique identifier for the account
    "name": "Account Name",        // Account display name

    // Optional Account Custom Fields
    "tier": "Enterprise",          // Maps to account-level permissions
    "region": "North America",     // Used for audience filtering
    "beta": "true",                // Used for audience filtering
    "can_see_hidden_listings": "true"
  },

  // Required JWT Expiration
  "exp": 1234567890                // Unix timestamp (current time + 5 minutes)
}

Where each field goes

  • Top-level claims (email, ufn, uln, exp): required on every JWT.
  • fields object: user-level custom values, used for user-level Permissions (for example, user_type for the Role permission).
  • account object: account-level values. external_id and name are required. All other account-level custom values (tier, region, beta, etc.) go inside the account object alongside them, not in the fields object.

Field names and values must match your Partner Fleet admin configuration exactly. If you've created an audience filter field called Region with options North America, Europe, and Asia Pacific, the JWT must use one of those exact strings. Casing matters.

Technical Requirements

  • Algorithm: HS256 (HMAC SHA-256). Other algorithms are not supported.
  • Token Expiration: Set expiration to more than 5 minutes from generation time.
  • Token Refresh: New token will be requested if within 5 minutes of expiration.
🚧

Important

Store your shared secret securely in your application's credentials system. Never expose the secret in client-side code.

Token refresh

The marketplace will request a new token when the current one is within 5 minutes of expiring. Implement your token-generation endpoint to be idempotent and fast — it's called multiple times per session.

Common pattern: cache the token for the user's session, regenerate when expiring soon, return immediately when valid.

Implementation Examples

Ruby

# Gemfile
# gem 'jwt'

class JwtAuthenticator
  def self.decode(token, secret = Rails.application.credentials.jwt_shared_secret)
    JWT.decode(token, secret, true, algorithm: 'HS256')[0]
  rescue JWT::ExpiredSignature
    nil  # Handle expired token
  rescue JWT::DecodeError
    nil  # Handle invalid token
  end

  def self.encode(payload, secret = Rails.application.credentials.jwt_shared_secret)
    JWT.encode(payload, secret, 'HS256')
  end
end

# Usage Example
def generate_marketplace_token
  data = {
    "email": current_user.email,
    "ufn": current_user.first_name,
    "uln": current_user.last_name,
    "fields": {
      "user_type": current_user.type
    },
    "account": {
      "external_id": current_user.account.id,
      "name": current_user.account.name,
      "tier": current_user.account.tier,
      "region": current_user.account.region
    },
    "exp": (Time.zone.now + 5.minutes).to_i
  }

  JwtAuthenticator.encode(data, ENV['PARTNER_FLEET_SECRET'])
end

Node.js

// Package required: jsonwebtoken
// npm install jsonwebtoken

const jwt = require('jsonwebtoken');

class JwtAuthenticator {
  static decode(token, secret) {
    try {
      return jwt.verify(token, secret);
    } catch (error) {
      if (error.name === 'TokenExpiredError') {
        return null;  // Handle expired token
      }
      return null;  // Handle invalid token
    }
  }

  static encode(payload, secret) {
    return jwt.sign(payload, secret, { algorithm: 'HS256' });
  }
}

// Usage Example
function generateMarketplaceToken(user, account) {
  const data = {
    email: user.email,
    ufn: user.firstName,
    uln: user.lastName,
    fields: {
      user_type: user.type
    },
    account: {
      external_id: account.id,
      name: account.name,
      tier: account.tier,
      region: account.region
    },
    exp: Math.floor(Date.now() / 1000) + 300  // Current time + 5 minutes
  };

  return JwtAuthenticator.encode(data, process.env.PARTNER_FLEET_SECRET);
}

Python

# Package required: PyJWT
# pip install PyJWT

import os
import jwt
from time import time
from typing import Dict, Optional

class JwtAuthenticator:
    @staticmethod
    def decode(token: str, secret: str) -> Optional[Dict]:
        try:
            return jwt.decode(token, secret, algorithms=['HS256'])
        except jwt.ExpiredSignatureError:
            return None  # Handle expired token
        except jwt.DecodeError:
            return None  # Handle invalid token

    @staticmethod
    def encode(payload: Dict, secret: str) -> str:
        return jwt.encode(payload, secret, algorithm='HS256')

# Usage Example
def generate_marketplace_token(user, account):
    data = {
        "email": user.email,
        "ufn": user.first_name,
        "uln": user.last_name,
        "fields": {
            "user_type": user.type
        },
        "account": {
            "external_id": str(account.id),
            "name": account.name,
            "tier": account.tier,
            "region": account.region
        },
        "exp": int(time()) + 300  # Current time + 5 minutes
    }

    return JwtAuthenticator.encode(data, os.environ.get('PARTNER_FLEET_SECRET'))

Best Practices

Security

  • Store the shared secret securely.
  • Never expose the secret in client-side code.
  • Implement proper error handling for token validation.

Performance

  • Only include necessary custom fields in the payload.
  • Consider caching the token for repeated requests.
  • Implement token refresh logic before expiration.

Error Handling

  • Handle expired tokens gracefully.
  • Provide clear error messages for invalid tokens.
  • Implement retry logic for failed token generation.

Validation

Use the Test Tools tab in your Partner Fleet admin (Settings > In-App Marketplace > Test Tools) to validate your implementation without standing up a test embed. You can:

  • Generate test tokens with arbitrary user/account properties to validate marketplace personalization.
  • Decode tokens your backend generates to verify field names, values, and signatures.

See the Test Tools article for details.

Validation Steps

  • Generate a test token with your implementation.
  • Verify all required fields are present.
  • Confirm the token can be decoded with your shared secret.
  • Test token expiration handling.
  • Verify custom fields are properly formatted.

Common Issues

Token rejected: signature invalid

Your application is signing with a different secret than the Partner Fleet secret. Verify the shared secret in Settings > In-App Marketplace > Developer Settings > JWT Identity Provider matches what your backend uses to sign.

End user sees no listings or wrong listings

Audience filter values in your JWT don't match the configured options. Use the Test Tools tab to decode a token and inspect the claim values. Field names and option values are case-sensitive.

Buttons don't appear correctly

Integration status data isn't being passed correctly. Status flows through the embed script's integrations array, not the JWT — see the Embed Script Implementation guide.

Restricted users don't see expected fallback

Permission field names or option labels in the JWT don't match the Permissions tab configuration. Casing matters: "Enterprise" and "enterprise" are different.

What's Next


Did this page help you?