Enroll a iOS device
The app makes an App Attest key over a challenge from Root Herald, your server relays it, and one first attestation gives your server the device's ID.
Without it, the SDK throws .notSupported from both enroll(to:) and respond(to:).
Get a challenge
An iPhone enrolls against a fresh challenge. On your server, use issueChallenge to get one, and send the challenge and its nonce to the app. You'll use the same route again in step 5 and for every attestation after.
app.post("/challenge", async (_req, res) => {
const { nonce, challenge } = await rh.issueChallenge({
ask: ["identity"],
});
res.json({ nonce, challenge });
});Make the App Attest key
In the app, pass the challenge to enroll(to:). It creates an App Attest key and has Apple attest it over the challenge, which vouches that the key is genuine and belongs to your app. Post what it returns to your server as it is.
let enrollment = try await rh.enroll(to: challenge().challenge)
try await post("/enroll", enrollment)Relay it to Root Herald
On your server, pass it to relayEnroll. Root Herald checks Apple's attestation and your key's identity policy, and records the key. There's nothing to send back but success.
If the identity policy on your key doesn't accept the device, relayEnroll throws AdmissionRefusedError instead. That's a final answer for this device under this policy, so don't retry it.
app.post("/enroll", async (req, res) => {
await rh.relayEnroll(req.body);
res.sendStatus(204);
});Mark the device enrolled
Once your server confirms, call markEnrolled(). Only call it after the relay succeeds: the SDK won't answer challenges until it's marked, and a key your server never relayed would only earn enrollmentRequired. If the relay fails, enroll again with a fresh challenge.
rh.markEnrolled()Learn the device's ID
An iPhone's ID first appears in a verdict, so finish by attesting once. In the app, answer a fresh challenge with respond(to:) and post the evidence with its nonce to your server. There, pass them to verify and keep device.ueid with whatever the device belongs to.
let c = try await challenge()
let evidence = try await rh.respond(to: c.challenge)
try await postEvidence("/enroll/confirm", c.nonce, evidence)app.post("/enroll/confirm", async (req, res) => {
const { nonce, evidence } = req.body;
const { device } = await rh.verify(evidence, { nonce });
await saveDevice(accountOf(req), device.ueid);
res.sendStatus(204);
});Enrolling again
On a device that's already enrolled, enroll(to:) throws .alreadyEnrolled. To give it a new identity on purpose, for example after a restore onto different hardware, call reset() first. Your server will then see a device it hasn't met.