ASC API key auth fails with Apple 401 because @expo/apple-utils signs JWTs with exactly the 1200 s maximum lifetime
Maintainers usually reply within 3 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 68/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- ios, node.js, typescript
- Domain
- authentication, mobile
Research direction
The fault is in Token.sign in @expo/apple-utils/build/index.js, where o=1200 and Math.min(o, duration ?? o) set exp = iat + 1200. Start by reproducing with apple.BundleId.getAsync and new Token({ duration: 1200 }) from eas-cli/node_modules/@expo/apple-utils, using the issue's probe table. Done when a token signed at the default duration returns 200 from GET /v1/bundleIds?limit=1.
Written by the indexing model from the issue text.
Description
Build/Submit details page URL
https://expo.dev/accounts/isaaceast/projects/MyApp/builds/42ae2260-bdef-4ba5-86af-e698dc3e9659 (the build itself succeeded after falling back to Apple ID login; the failure is in the credentials step before it)
Summary
With a valid App Store Connect team API key provided via EXPO_ASC_API_KEY_PATH, EXPO_ASC_KEY_ID, EXPO_ASC_ISSUER_ID (+ EXPO_APPLE_TEAM_ID, EXPO_APPLE_TEAM_TYPE), eas build -p ios fails during "Linking bundle identifier" with:
Auth error: 'm', 'Apple 401 detected - You are either not logged in, your account doesn't have access to the requested data, or the page doesn't exist
Authentication credentials are missing or invalid. - Provide a properly configured and signed bearer token, and make sure that it has not expired.'. Login in again (remaining: 3)
Log in to your Apple Developer account to continue
and then falls back to the Apple ID login flow (which hangs in --non-interactive).
The key is valid: a JWT signed by hand from the same .p8/Key ID/Issuer ID returns 200 from GET /v1/bundleIds.
Root cause
@expo/apple-utils Token.sign uses o=1200 and Math.min(o, duration ?? o), so every token has exp = iat + 1200, which is Apple's hard maximum lifetime. Apple appears to evaluate exp - serverNow <= 1200 with second resolution. If the client clock is even ~1 s ahead of Apple's (my machine: local 05:44:36 vs Apple Date: header 05:44:35), the token is 1201 s from Apple's point of view and is rejected.
Probing with hand-signed tokens against GET /v1/bundleIds?limit=1:
| lifetime | result |
|---|---|
| 600 s | 200 |
| 1140 s | 200 |
| 1199 s | 200 |
| 1200 s | 401 |
1200 s with iat set 5 s in the past |
200 |
| 1201 s | 401 |
Calling apple.BundleId.getAsync({ token: new Token({ key, issuerId, keyId, duration: 1200 }) }, ...) directly from eas-cli/node_modules/@expo/apple-utils reproduces the 401; patching the constant to 1140 makes the same call succeed 3/3 and lets eas submit:status -p ios --non-interactive read App Store Connect.
Suggested fix
Sign with a safety margin below Apple's limit (for example 1140 s or min(duration, 1200) - 60), or set iat slightly in the past. Apple's own documentation only guarantees tokens valid for at most 20 minutes, so signing exactly at the limit is fragile against any positive clock skew.
Environment
- eas-cli 24.13.0, Node 24.20.0, Windows 11 (clock within ~1 s of time.windows.com)
- Expo SDK 54, Individual Apple Developer team
- Reproduces with two different keys (App Manager and Admin roles)
Workaround
Patch @expo/apple-utils/build/index.js in the installed eas-cli: o=1200 -> o=1140.
- Dominant language
- TypeScript
- Stars
- 1.4k
- Forks
- 254
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 109
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from expo/eas-cli
-
metadata:push: `apple.info.restrictedWords` flags "beta" inside other words (e.g. Danish "Betal")Possibly taken @DYNOSuprovo claimed this 12 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 3 days
-
workflow:validate crashes with "Cannot read properties of undefined (reading 'const')" when the published schema has a job entry without properties.typePossibly taken @douglowder claimed this 14 days ago. Openissue accepted
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
expo/eas-cli#4482 · 3 comments · 3 reactions · 1 assignee ·
Maintainers usually reply within 3 days
-
needs review
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 3 days
-
`--no-auto-testflight-setup` is ignored when the submit profile has no `ascAppId`: `Team (Expo)` is still created and all Admins enrolled (opt-out lost in 22.0.0)Possibly taken @tahakocal claimed this 27 days ago. Openstale
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
expo/eas-cli#4397 · 1 comment ·
Maintainers usually reply within 3 days
-
Handle missing node_modules betterPossibly taken @LizunovSergey claimed this 39 days ago. Openneeds review
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 3 days
Similar issues
-
afk-ok area:data-quality importer size:S
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
enorm-labs/event-junkie#3301 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
one-zero-eight/apply-bot#39 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
bigbluebutton/bbb-ui-components-react#108 ·
Maintainers usually reply within 2 days
-
area: app type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
ExpidetGames/code-reviewer#308 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day