Skip to Content
DocsSecurityRequest Signing

Request Signing

Block users from sending requests directly to your GraphQL API by verifying that requests have passed through Stellate first and haven’t been tampered with.

When your request includes a X-AMZ-security-token we won’t try and sign the request.

How to use it

Set up a secret in your Stellate service’s configuration

This is ideally a long random string (don’t worry, you won’t have to type it):

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    name: 'my-app',
    requestSigning: {
      secret: 'my-secret',
    },
  },
}
export default config

Verify that requests arriving at your backend have been signed

The example below is written for Node.js, but the cryptography is generic and will work the same in any backend language. If you want to use request signing with a different language, please reach out to our support team and we’re happy to help translate it!

stellate.ts
import crypto from 'crypto'
import { GraphQLError } from 'graphql'
 
const serverHandler = async (req, res) => {
  const stellateSignature = req.headers['stellate-signature']
 
  if (!stellateSignature) {
    return res.status(401).json({
      errors: [new GraphQLError("You aren't authorized to request this API.")],
    })
  }
 
  const payload = JSON.stringify({
    query: req.body.query,
    variables: req.body.variables,
    operationName: req.body.operationName,
  })
 
  const values = stellateSignature.split(',')
  const obj = {
    signature: '',
    expiry: '',
  }
 
  values.forEach((val) => {
    if (val.startsWith('v1:')) {
      obj.signature = val.replace('v1:', '')
    } else if (val.startsWith('expiry:')) {
      obj.expiry = val.replace('expiry:', '')
    }
  })
 
  const sig = crypto
    .createHmac('sha256', 'my-secret')
    .update(payload)
    .digest('base64')
 
  if (
    !obj.signature ||
    !crypto.timingSafeEqual(
      Buffer.from(obj.signature, 'base64'),
      Buffer.from(sig, 'base64'),
    )
  ) {
    return res.status(401).json({
      errors: [new GraphQLError('Missmatch in the signature.')],
    })
  }
 
  if (Date.now() > Number(obj.expiry)) {
    return res.status(401).json({
      errors: [new GraphQLError('Signature has expired.')],
    })
  }
 
  // Handle request
}

How it works

The secret will be used to sign a base64 SHA-256 HMAC of a stringified version of { query: body.query, variables: body.variables, operationName: body.operationName }.

For GET and APQ requests we will apply the same principle as in we derive the query, ... and will encode these properties. When one of these properties isn’t present we’ll omit the key from the JSON-object instead of using null or an empty string.

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    name: 'my-app',
    requestSigning: {
      secret: 'my-secret',
    },
  },
}
export default config

When this configuration is pushed from then onwards you will receive an additional header with every request coming from the Stellate CDN, this header is named stellate-signature and will look like the following

v1:hash,expiry:timestamp

A few words about the properties

  • v1 is the current iteration of the hash, in the future we could have v2/... should we change the format
  • expiry is currently set 5 minutes from the moment we sign the request
Last updated on