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):
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!
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.
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 havev2/...
should we change the formatexpiry
is currently set5 minutes
from the moment we sign the request