Last active
July 9, 2024 20:32
-
-
Save schirrmacher/d9e0a1109d58aae1611f1725c7ec7f53 to your computer and use it in GitHub Desktop.
DeviceCheck Backend Example Implementation for Validating iOS Device Authenticity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import jwt from "jsonwebtoken"; | |
import uuid from "uuid"; | |
import config from "../../../config"; | |
import { DeviceCheckService, DeviceCheckParams } from "./DeviceCheckService"; | |
import BaseService from "./BaseService"; | |
export class AppleDeviceCheckService extends BaseService implements DeviceCheckService { | |
public loggingTag = "AppleDeviceCheck"; | |
public host = "https://api.devicecheck.apple.com/v1/query_two_bits"; | |
public async shouldProceed(params: DeviceCheckParams): Promise<boolean> { | |
const { token } = params; | |
const body = { | |
"device_token": token, | |
"transaction_id": uuid.v4(), | |
"timestamp": Date.now() | |
}; | |
// Create a private key for signing: https://help.apple.com/developer-account/#/devc3cc013b7 | |
const jwToken = jwt.sign({}, config.apple.deviceCheckPrivateKey, { | |
algorithm: "ES256", | |
keyid: config.apple.keyid, | |
issuer: config.apple.teamid | |
}); | |
const options = { | |
headers: { | |
"Authorization": "Bearer " + jwToken | |
} | |
}; | |
const deviceCheckResponse = await this.post({ url: this.host, body, options }); | |
const status = deviceCheckResponse.status; | |
switch (status) { | |
/* | |
200 OK - The transaction was successful | |
200 Bit State Not Found - The bit state wasn't found | |
400 Bad Device Token - The device token is missing or badly formatted | |
400 Bad Bits - The bits are missing or badly formatted | |
400 Bad Timestamp - The timestamp is missing or badly formatted | |
400 Bad Authorization Token - The authentication token is missing or badly formatted | |
400 Bad Payload - The payload is missing or badly formatted | |
401 Invalid Authorization Token - The authentication token can't be verified | |
401 Authorization Token Expired - The authentication token has expired | |
403 Forbidden - The specified action isn't allowed | |
405 Method Not Allowed - The endpoint was used incorrectly | |
429 Too Many Requests - Too many requests were sent to the server | |
500 Server Error - An error occurred on the server | |
503 Service Unavailable - The service is unavailable | |
*/ | |
// in some cases errors are thrown | |
// otherwise incoming requests are blocked for external reasons | |
case 200: | |
// do nothing, all good | |
break; | |
case 401: | |
throw Error(`${this.loggingTag}: we have to fix authorization`); | |
case 403: | |
case 405: | |
throw Error(`${this.loggingTag}: we made a programming mistake`); | |
case 429: | |
case 500: | |
case 503: | |
throw Error(`${this.loggingTag}: service not reliable`); | |
default: | |
// device check token was probably modified or is invalid | |
this.logger.error(`${this.loggingTag}: suspicious behavior detected: status ${status})`); | |
} | |
// 200 means that it is a real device | |
// we don't care about the bit states here | |
return status === 200; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment