Deep dive · Deploy fundamentals

JWT auth in 5 minutes

Why every deploy tool uses JSON Web Tokens — and why they beat API keys for automated clients.

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:

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

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:

  1. Reads the ES256 private key from the .p8.
  2. Constructs a payload with your Issuer ID and an exp 15 minutes in the future.
  3. Base64-encodes header + payload, hashes the result, signs with the key, appends the signature.
  4. Hands the whole thing to xcrun altool as 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.