Verify calls to your target
Turn on signed calls, then check in your target that each call came from the scheduler and from a job you expect.
Anyone who knows your target’s address can call it. With signing on, every call carries a token that proves it came from the scheduler, so your target can refuse everything else.
Turn signing on
Set signing.enabled to true on the job. Each attempt then sends:
Authorization: Bearer <token>You cannot set your own Authorization header on a job that has signing on.
The token
The token is a JSON Web Token signed with RS256, and it expires 10 minutes after it is made.
| Claim | Holds |
|---|---|
iss | https://scheduler-api.digitospace.com |
aud | The job’s audience: the target URL without its query, unless you set signing.audience on the same site |
sub | projects/{project}/jobs/{job}, the job that made the call |
exp | When the token expires |
The public keys are published at https://scheduler-api.digitospace.com/.well-known/jwks.json, with a discovery document at https://scheduler-api.digitospace.com/.well-known/openid-configuration.
Check a call
Fetch the public keys
Fetch the key set and cache it for about 5 minutes. Most JWT libraries do this for you.
Check the signature
Use the key named by the token’s
kidheader, and accept only RS256.Check the claims
Check that
issishttps://scheduler-api.digitospace.com,audis your job’s audience, andexphas not passed.Check the job
Read
suband accept only the jobs you expect. A token from another project or job is not meant for your target, even when its audience matches.
This example uses the jose library.
import { createRemoteJWKSet, jwtVerify } from "jose"
const schedulerUrl = "https://scheduler-api.digitospace.com"
const keys = createRemoteJWKSet(
new URL("/.well-known/jwks.json", schedulerUrl)
)
export async function verifySchedulerCall(request: Request, jobs: string[]) {
const header = request.headers.get("authorization") ?? ""
const token = header.replace(/^Bearer /, "")
const { payload } = await jwtVerify(token, keys, {
issuer: schedulerUrl,
audience: "https://mail.example.com/tasks/report",
algorithms: ["RS256"],
})
if (!payload.sub || !jobs.includes(payload.sub)) {
throw new Error("This job may not call this target")
}
return payload.sub
}Key rotation
The scheduler rotates its signing keys from time to time. A retired key stays published for 24 hours, so tokens already sent still check out. Fetch keys by kid rather than keeping one key in your code.
Last updated