The problem JWTs solve
If an automated client — your CI pipeline, LingCode, a deploy script — needs to prove "I'm authorized to upload builds for team X," it has three options:
- Store a password. Rejected: passwords are for humans, sent over the wire, rotate rarely, and can't be scoped to a narrow capability.
- Store an API key. Better, but still a long-lived bearer secret — if it leaks from a log, anyone holding it can act as you until you rotate.
- Prove a signature from a private key you hold. The server gives you a public half; you sign short-lived assertions with the matching private half. If an assertion leaks, it expires in minutes. If your private key leaks, you rotate it.
Option 3 is what a JWT enables. Apple's App Store Connect API and Google's Play Console API both rely on it.
What a JWT is, physically
A JWT is three base64-encoded strings glued with dots:
Example JWT (abbreviated)eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2OWE2ZGU4Zi0wM2Y3LTQ3ZTMifQ.mBH4vWJqU...
Those three parts are:
1. Header — decoded{ "alg": "ES256", "typ": "JWT" }
Says how the token is signed. ES256 (ECDSA with P-256) is what Apple uses; RS256 (RSA-2048) is what Google service accounts use. These matter because the verifying server needs to use the matching algorithm.
2. Payload — decoded{ "iss": "69a6de8f-03f7-47e3-...", "aud": "appstoreconnect-v1", "exp": 1765000000 }
The claims. iss = who's making the claim (your Issuer ID for Apple, your service account email for Google), aud = intended recipient, exp = when it stops being valid (usually 5–60 minutes from now). The server ignores anything after exp.
3. SignaturemBH4vWJqUQxS7nR...
The first two parts, signed with your private key. Anyone with your public key (which Apple or Google already has on file, since you uploaded it as the .p8 or service-account key) can verify the signature matches. Anyone without your private key cannot forge a signature.
Why this is strictly better than an API key
- Your private key never crosses the network. You sign locally; the server verifies with the public half. Compare that to API keys, which travel in full on every request.
- Tokens expire. A leaked JWT from a log is useless in an hour. A leaked API key is dangerous until someone notices and rotates.
- Servers can't lie about what you authorized. The claims (
iss,aud, etc.) are signed by you — a compromised server can't re-issue tokens claiming you authorized something you didn't. - Key rotation is cheap. Revoke the old public key in Apple's or Google's console and generate a new keypair — no coordination with other systems that might be storing the old key.
Apple and Google use JWTs differently
Apple (App Store Connect API) wants a JWT on every request. You sign a fresh JWT (typically 15-min expiry) per call and put it in the Authorization: Bearer <jwt> header. There's no token exchange — Apple verifies and processes each request individually.
Google (Play Developer API) does a two-step dance. You sign a JWT once, POST it to oauth2.googleapis.com/token, and receive an OAuth 2.0 access token back (valid 1 hour). Then you use that access token as Authorization: Bearer on your API calls. It's more round-trips but gives Google a revocation point for the intermediate token.
Both flows prove the same thing: "I hold the private key matching the public key you have on file, and this request/session is authorized right now."
What this looks like in LingCode
When you paste a .p8 file into Magic Deploy for App Store upload, LingCode:
- Reads the ES256 private key from the
.p8. - Constructs a payload with your Issuer ID and an
exp15 minutes in the future. - Base64-encodes header + payload, hashes the result, signs with the key, appends the signature.
- Hands the whole thing to
xcrun altoolas an environment variable, which puts it on every API call during upload.
For Google Play, the service account's private_key (RSA) takes the place of the .p8, and LingCode handles the extra JWT → access-token exchange before hitting the Play Developer API.
The takeaway
JWTs aren't magic. They're a compact, standardized way to say "I can sign this, so you know it came from me, and it won't be valid in a few minutes." That property is what lets automated clients like LingCode talk to Apple's and Google's APIs without storing long-lived bearer secrets — exactly the property you want when a build server eventually gets compromised and someone reads the logs.