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
Step 1: 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):
import { Config } from 'stellate'
const config: Config = {
config: {
name: 'my-app',
requestSigning: {
secret: 'my-secret'
},
},
}
export default config
Step 2: 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!
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 principe 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.
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