Skip to main content
More in Learn

Performing Signature Verification

Signature verification allows users to be sure that it's really Crystallize calling their endpoint with a valid request. This is important for (among other things) protecting against man-in-the-middle attacks on things like webhooks, orders, and Apps, and also ensuring that frontend previews aren't visible to everyone who happens to know or guess the preview URL.

Security is the user's responsibility. Crystallize will sign its requests, and it's on every user to verify them, or not, as they see fit.

Signature Access

Crystallize signs its requests using a secret that's unique to your tenant so your app can more confidently verify whether requests from Crystallize are authentic.

The Crystallize signature is a generated string (actually a JWT Token) attached to HTTP requests (for webhooks, Apps, or frontend previews) to prove and ensure those requests were made from Crystallize.

You can access the signature by getting:

  • the HTTP header with the name X-Crystallize-Signature for webhooks.
  • the query parameter with the name crystallizeSignature for apps and frontend previews. 

Notes:

  • The signature should not be used for authentication or authorization.
  • It doesn't contain any secret information.
  • The token expires the next second to make it almost unique for even more security.

Format

The signature format is JSON Web Token (JWT) using the HS256 algorithm. JWT is an industry standard, and these resources provide good information about the JWT format: 

The payload has the following structure:

interface CrystallizeSignature {
  // Registered claims...
  aud: "webhook" | "app" | "frontend"
  sub: "signature"
  iss: "crystallize"
  exp: number // NumericDate
  iat: number // NumericDate
  
  // Additional data...
  userId: string
  tenantId: string
  tenantIdentifier: string
}

const payload = {
  "iss": "crystallize",
  "iat": 1516239022,
  "exp": 1516239022,
  "aud": "webhook",
  "sub": "signature",
  "tenantIdentifier": "my-awesome-tenant",
  "tenantId": "123",
  "userId": "123"
}

Verifying the Signature

The signature uses a secret for encryption and decryption. The Crystallize API generates a signatureSecret for each tenant:

# https://pim.crystallize.com/graphql
query GetSignatureSecret {
  tenant {
    get(identifier: "my-awesome-tenant") {
      signatureSecret
    }
  }
}

While everyone can use Apps, only Admin-level users can read secrets. Non-admin users will receive an empty string if they try to read signatureSecret or staticAuthToken. You can read more about Admin-level access and promoting users to Admin on our Inviting Users page.

We recommend storing your secret securely server-side and regenerating the signatureSecret in case of accidental exposure (more detail on this below).

Request signing follows this pattern:

  • Your App receives a request from Crystallize.
  • Your App retrieves the token, and with the help of the signature secret, it verifies the token.

There are many libraries in different programming languages for decoding JWTs: https://jwt.io/libraries. The following example is a validation function in TypeScript using the jsonwebtoken library:

import jwt from 'jsonwebtoken'

const CRYSTALLIZE_SIGNATURE_SECRET = process.env.CRYSTALLIZE_SIGNATURE_SECRET || null
const VALID_TENANTS = ['my-awesome-tenant']

function isCrystallizeSignatureValid(signatureJwt: string): boolean {
  const signature = decodeCrystallizeSignature(signatureJwt)

  if (!signature) {
    return false
  }

  // Check all properties you need...
  return VALID_TENANTS.includes(signature.tenantIdentifier) && new Date() < new Date(signature.exp)
}

function decodeCrystallizeSignature(signatureJwt: string): CrystallizeSignature | null {
  if (!CRYSTALLIZE_SIGNATURE_SECRET) {
    return null
  }

  try {
    const payload = jwt.verify(signatureJwt, CRYSTALLIZE_SIGNATURE_SECRET)

    // Valid payload means content could not be changed...
    return payload as CrystallizeSignature
  } catch (error) {
    return null
  }
}

Regenerating the Signature Secret

The signature secret for decoding signatures should be stored securely. Anyone with access to this secret can create valid signatures. There is a mutation to regenerate the signatureSecret for a tenant: 

# https://pim.crystallize.com/graphql
mutation RegenerateSignatureSecret {
  tenant {
    regenerateSecrets(tenantId: "123", input: { signatureSecret: true }) {
      signatureSecret
    }
  }
}

Only Admin-level users can regenerate secrets. Non-Admin users will receive an error if they try to run this mutation.

People showing thumbs up

Need further assistance?

Ask the Crystallize team or other enthusiasts in our slack community.

Join our slack community