how iOS and Android apps actually become trustworthy
Code signing decides whether a React Native app can even be installed on a test device, let alone published to the App Store or Play Store. Anyone who does not understand how a certificate, a provisioning profile, an app ID and a team fit together loses hours to cryptic build errors. This guide explains the full signing system for iOS and Android and shows how EAS Credentials and Fastlane match replace most of the manual effort.
Table of contents
- 1. Why code signing is unavoidable for React Native apps
- 2. Apple's certificate system: certificates, CSRs and team ID
- 3. Provisioning profiles: binding app ID, devices and certificate
- 4. Android keystores and signing configs
- 5. EAS Credentials: letting a tool manage code signing
- 6. Fastlane match: syncing certificates across a team
- 7. Manual signing in Xcode: when it still makes sense
- 8. Common signing errors and how to fix them
- 9. Comparison: manual signing, Fastlane match and EAS Credentials
- 10. Summary
- 11. FAQ
1. Why code signing is unavoidable for React Native apps
Code signing is the mechanism iOS and Android use to cryptographically verify that an app really comes from the claimed developer identity and has not been tampered with since it was signed. For React Native apps nothing changes about this principle compared to a purely native app: the JavaScript bundle is built by Metro, but the end result is still a native iOS or Android package subject to exactly the same signing rules as an app written in Swift or Kotlin. Anyone who treats code signing as an afterthought will trip over cryptic error messages at the very latest during the first TestFlight upload or the first Play Store submission.
The difference between iOS and Android is fundamental, though. Apple requires a centrally managed system of certificates, app IDs and provisioning profiles controlled through the Apple Developer account. Android, by contrast, is built around a self-generated keystore that the developer is responsible for safeguarding. Both systems pursue the same goal: making sure updates to an app can only ever come from the same identity as the initial installation. The following sections explain both systems in detail and show how tools like EAS and Fastlane now automate most of the manual code signing work.
2. Apple's certificate system: certificates, CSRs and team ID
At the center of iOS code signing sits the certificate. It is requested via a Certificate Signing Request (CSR) based on a private key generated locally in the Keychain (macOS) or encrypted in a credentials store (EAS). Apple signs this CSR and issues a public certificate bound to that private key. There are Development certificates for development builds and Distribution certificates for release builds, and both types are tied to a Team ID that uniquely identifies the Apple Developer account.
A common misconception: the certificate alone is not enough to install an app on a device or submit it to the App Store. It is only half of the code signing process; the other half is the provisioning profile, explained in the next section. It is also worth noting: if the private key belonging to a certificate is lost, for example due to a machine change without a Keychain export, an entirely new certificate must be created and re-linked in every affected provisioning profile.
# Generate a Certificate Signing Request locally (manual workflow)
# 1. Open Keychain Access -> Certificate Assistant -> Request a Certificate
# 2. Save the .certSigningRequest file, upload it in the Apple Developer portal
# 3. Download the resulting .cer file and double-click to install it
# Equivalent, fully automated via EAS credentials manager
eas credentials
# Select platform: iOS
# Select: Build Credentials -> Set up a new Distribution Certificate
# EAS generates the CSR, uploads it to Apple, stores the private key encrypted
3. Provisioning profiles: binding app ID, devices and certificate
A provisioning profile is the file that ties three elements together into a valid signing configuration: the app ID (the app's bundle identifier), one or more certificates, and, for Development and Ad Hoc profiles, a list of registered test devices identified by their UDID. Without a matching provisioning profile, iOS refuses to install an app even if the certificate itself is valid. For code signing in production environments there are three relevant profile types: App Store profiles for release, Ad Hoc profiles for testing on registered devices outside TestFlight, and Enterprise profiles for internal distribution outside the App Store.
Provisioning profiles expire, typically after one year, and must be renewed. In practice this leads to one of the most common code signing problems: an app that has built without issue for months suddenly fails because the profile quietly expired in the background. Xcode renews expired profiles automatically for manual signing when "Automatically manage signing" is enabled; for CI pipelines and EAS builds, the respective credentials management handles this instead, which is why it pays to check profile expiry dates ahead of larger releases.
{
"build": {
"production": {
"ios": {
"credentialsSource": "remote",
"distribution": "store"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "developer@example.com",
"ascAppId": "1234567890"
}
}
}
}
4. Android keystores and signing configs
Android forgoes a centralized certificate system like Apple's and instead relies on a self-generated keystore. A keystore is a file that holds a private key and a self-signed certificate used to sign every APK or AAB. For code signing on Android the rule is: the keystore used to publish the very first version of an app to the Play Store must be reused for every future update. Google checks on every upload whether the signature matches the original certificate. If the keystore is lost, the existing app identity in the Play Store can no longer be updated without Google's help.
Since the introduction of Play App Signing this risk has been mitigated: developers upload their apps signed with an upload key, and Google internally signs the final package with a separate app signing key kept securely on Google's side. If the upload key is lost, a new one can be registered through Play Console support without losing the app's identity. For React Native projects built with Expo, EAS handles this entire process, including keystore generation and storage, which makes manually handling `.jks` files unnecessary for most teams.
# Generate an Android keystore manually (only needed for non-EAS workflows)
keytool -genkeypair -v \
-keystore release.keystore \
-alias upload \
-keyalg RSA -keysize 2048 -validity 10000
# Inspect an existing keystore's fingerprint (SHA-256), useful for
# verifying it matches the one registered in Play App Signing
keytool -list -v -keystore release.keystore -alias upload
5. EAS Credentials: letting a tool manage code signing
The EAS Credentials manager (`eas credentials`) is, for most React Native and Expo teams today, the most pragmatic way to handle code signing. For iOS it can automatically generate certificates and provisioning profiles, register test devices for Ad Hoc builds, and store private keys encrypted within Expo's infrastructure. For Android it generates keystores or imports existing ones and links them directly to the relevant build profile in `eas.json`. The big advantage: developers no longer need to click through the Apple Developer portal manually to renew an expired profile.
Even so, code signing via EAS is not a black box. Running `eas credentials` at any time shows exactly which certificate, profile, and keystore are currently active for a given build profile, and existing credentials can be exported if a team later switches to a different CI setup. For teams running multiple apps or app variants (say, staging and production as separate bundle IDs), it is worth naming credentials explicitly per build profile in `eas.json` so a staging certificate never accidentally ends up in a production build.
6. Fastlane match: syncing certificates across a team
Fastlane `match` takes a different approach from EAS Credentials: it stores certificates, private keys and provisioning profiles encrypted in a private git repository (or alternatively a cloud storage bucket) and synchronizes them via a shared passphrase across every team member and CI runner. For teams that run their own CI pipeline, say with GitHub Actions or GitLab CI instead of EAS Build, `match` is the established solution for keeping code signing reproducible without every developer generating their own local certificates.
The key advantage of `match` over individually generated per-developer certificates: there is exactly one set of credentials per environment (Development, Ad Hoc, App Store) shared by everyone. That drastically reduces the number of active certificates in the Apple Developer account, which matters since Apple caps the number of simultaneously valid distribution certificates at three anyway. The downside: the match repository itself becomes a critical asset that needs careful protection, and its passphrase must never end up in plain text inside a pipeline configuration.
// Fastfile: sync iOS signing credentials from the match repository
// before building, so CI never generates its own certificates
platform :ios do
lane :beta do
match(
type: "appstore",
readonly: is_ci,
app_identifier: "com.example.myapp"
)
build_app(scheme: "MyApp", export_method: "app-store")
upload_to_testflight
end
end
7. Manual signing in Xcode: when it still makes sense
Despite EAS and Fastlane, manual code signing directly in Xcode remains relevant for some scenarios, such as debugging native modules that require their own Xcode workspace, or small teams without any CI infrastructure. Xcode offers two modes for this: "Automatically manage signing", where Xcode generates and renews certificates and profiles on its own using the signed-in Apple account, and manual signing, where developers explicitly pick a specific provisioning profile and certificate.
Automatic signing is convenient for a single developer, but quickly becomes messy in teams because each developer may generate their own development certificates that overwrite one another. For projects that also work with EAS or Fastlane in parallel, it is therefore worth enabling manual signing in Xcode and explicitly referencing the same profiles the CI pipeline uses, so that code signing stays consistent between local development and automated builds instead of running two parallel, conflicting certificate systems.
8. Common signing errors and how to fix them
The most common code signing error reads roughly "No signing certificate found" or "Provisioning profile doesn't match the entitlements file". In most cases one of three causes is behind it: the provisioning profile has expired, the app ID in the profile does not match the bundle identifier in the Xcode project, or the app's entitlements (such as Push Notifications or App Groups) are not enabled in the profile. A look at the Apple Developer account, under Certificates, Identifiers & Profiles, usually reveals immediately which of the three issues applies.
A second common error involves the team ID: when a project switches between multiple Apple Developer accounts, for example from a personal to a company account, stale team ID references often remain in the Xcode project file. Code signing then fails even though every certificate in the new account is valid. The third classic case is an expired push certificate, which is managed separately from the app certificate and is easy to overlook if push notifications were only added to the app later.
// build.gradle (app module): signing config referencing a keystore
// via environment variables, so the keystore itself never lives in git
android {
signingConfigs {
release {
storeFile file(System.getenv("ANDROID_KEYSTORE_PATH") ?: "release.keystore")
storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD")
keyAlias System.getenv("ANDROID_KEY_ALIAS")
keyPassword System.getenv("ANDROID_KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
9. Comparison: manual signing, Fastlane match and EAS Credentials
Choosing the right code signing strategy depends heavily on team size, CI infrastructure, and whether a project relies on Expo/EAS or a custom build pipeline. The following overview compares the three most common approaches along the criteria that matter most in practice.
| Criterion | Manual Xcode signing | Fastlane match | EAS Credentials |
|---|---|---|---|
| Team synchronization | None, per developer | Git repository, shared passphrase | Centralized via Expo account |
| Requires own CI pipeline | Yes, mostly local | Yes (GitHub Actions, GitLab CI, Jenkins) | No, EAS Build handles this |
| Setup effort | Low, but error-prone in teams | Medium, requires repository setup | Low, one command |
| Control over raw material | Full, everything local in Keychain | Full, own repository | Limited, encrypted at Expo |
| Recommendation | Solo developers, native debugging sessions | Teams with their own CI, without Expo | Expo/EAS projects, small to mid-size teams |
For most new React Native projects that already rely on Expo and EAS Build, EAS Credentials is the lowest-maintenance path for code signing. Teams that must run their own CI infrastructure for regulatory or historical reasons will find Fastlane match more reliable, since it keeps full control over the credentials repository. Manual signing in Xcode remains a useful complement for local development, but it should not be the primary strategy for production releases.
Mironsoft
React Native development, app distribution and store release
Code signing without stress at every release?
We set up EAS Credentials or Fastlane match once, properly, document certificate management for your team, and make sure provisioning profiles get renewed before they expire.
Signing setup
Set up and document EAS Credentials or Fastlane match from scratch
Migration
Move existing manual signing workflows into automated pipelines
CI integration
Make signing reliable and reproducible in GitHub Actions or EAS Build
10. Summary
Code signing for React Native apps follows clearly structured, if different, rules on iOS and Android. On iOS, the certificate, the app ID and the provisioning profile together form the valid signing configuration; if any one of the three is missing or expired, the build fails. On Android, a self-generated keystore takes on this role, backed by Play App Signing, which keeps the critical app signing key safely on Google's side. EAS Credentials automates both systems almost entirely for Expo projects, while Fastlane match remains the established alternative for teams running their own CI infrastructure.
The biggest lever against recurring signing problems is settling on a single, documented strategy early on, rather than running manual Xcode signing, EAS Credentials and Fastlane match in parallel and uncoordinated within the same project. Anyone who treats code signing as a fixed part of the release pipeline and keeps an active eye on certificate and provisioning profile expiry dates avoids most short-notice build failures ahead of important releases.
Code signing for React Native, the essentials at a glance
iOS signing
Certificate, app ID and provisioning profile must all match. Check each element separately when a build fails.
Android signing
Never lose the keystore. Play App Signing separates the upload key and the app signing key, making recovery possible.
Automation
EAS Credentials for Expo projects, Fastlane match for custom CI pipelines, both replace error-prone manual signing.
Maintenance
Actively monitor certificate and profile expiry dates, expired credentials are the most common cause of build failures.