Mobile APIs face constraints that web APIs do not: unreliable connectivity, bandwidth sensitivity, app versions you cannot force-update, and platform-specific auth flows. Where we take a position against common practice, such as certificate pinning, we say so and explain the trade-off rather than leaving it as a checklist item. This guide covers the practical patterns for building mobile backends that handle these realities. It is aimed at teams designing new mobile APIs or improving existing ones on Azure. Our mobile app backends service applies these patterns on Azure for enterprise mobile projects.
Why mobile APIs need different thinking
A web application and its API typically deploy together. If you change the API, you deploy the front end at the same time. The client and server are always in sync.
Mobile apps break this assumption.
You cannot force-update. Users control when (or whether) they update. At any given time, your backend serves requests from multiple app versions, some months old. Your API must handle this gracefully.
Connectivity is unreliable. Mobile devices switch between WiFi, 4G, and offline. APIs must support partial responses, retries, and offline-first workflows without creating duplicate data or inconsistent state.
Bandwidth costs money. On cellular networks, every unnecessary byte in a response costs the user (and increases latency). Payload optimisation is not a nice-to-have; it directly affects perceived performance and data usage.
Security is harder. The device is untrusted. You cannot store secrets on it. Auth flows and token storage need mobile-specific approaches, and certificate pinning needs a harder look than it usually gets before anyone commits to it.
These constraints shape every decision from protocol choice to error handling.
Choosing the right protocol
REST: the practical default
REST is the right choice for most mobile backends. The reasons are pragmatic:
- Every mobile HTTP library (URLSession, Retrofit, Axios) handles REST natively
- HTTP caching with ETags and Cache-Control reduces bandwidth and improves offline experience
- OpenAPI specifications generate mobile client code automatically
- Debugging is straightforward (readable JSON payloads, standard HTTP status codes)
Use REST unless you have a specific reason to add a different protocol.
GraphQL: when data shapes vary
Consider GraphQL if your mobile app has screens that display data from many related entities and the data requirements change frequently. A social feed, a marketplace browse page, or a data-rich dashboard can benefit from GraphQL’s ability to fetch exactly the fields needed in a single request.
The trade-off is complexity: schema management, query cost control, and the loss of HTTP-level caching. For a full comparison, see our guide on REST vs GraphQL vs gRPC.
gRPC: for internal paths only
gRPC is excellent for internal service-to-service communication behind your mobile API layer. It is not practical as the mobile client’s primary protocol because of limited browser tooling and HTTP/2 requirements. Use it behind a REST or GraphQL BFF layer.
Authentication and token management
OAuth 2.0 with PKCE
The Authorization Code flow with PKCE (Proof Key for Code Exchange), specified in RFC 7636, is the standard for native mobile apps. It replaces the implicit flow, which is now considered insecure. RFC 8252, OAuth 2.0 for Native Apps, is the wider best current practice and is worth reading in full: it is the document that requires native apps to use the system browser rather than an embedded web view.
The flow:
- The app generates a random
code_verifierand derives acode_challenge - The app opens the system browser to the authorization server with the
code_challenge - The user authenticates and consents
- The authorization server redirects back to the app with an authorization code
- The app exchanges the code and
code_verifierfor tokens
PKCE is critical because native apps cannot securely store a client secret. The code_verifier proves that the app that initiated the flow is the same one redeeming the code.
Azure Entra ID and Azure AD B2C both support PKCE. Use MSAL (Microsoft Authentication Library) for the implementation.
Token storage
- iOS: Store tokens in the Keychain with
kSecAttrAccessibleWhenUnlockedThisDeviceOnly - Android: Store tokens in the Android Keystore with biometric binding where appropriate
- Never store tokens in shared preferences, local storage, or plain files
The OWASP Mobile Application Security Verification Standard (MASVS) is the reference to test against here. MASVS-STORAGE covers credential handling and MASVS-AUTH covers the authentication requirements above.
Token refresh
Access tokens should be short-lived (5-15 minutes). Refresh tokens should be longer-lived but rotated on use (rotation prevents replay attacks if a refresh token is compromised). Implement silent token refresh in the app’s HTTP interceptor so API calls do not fail when the access token expires.
Biometric authentication
Biometric auth (Face ID, fingerprint) provides a smoother user experience for token re-authentication. It does not replace OAuth. The pattern is: authenticate fully with PKCE on first login, store the refresh token in the platform keychain bound to biometric, and use biometric to unlock the refresh token for subsequent sessions.
Designing for unreliable connectivity
Idempotency keys
When a mobile app submits a write operation (place order, make payment, create booking) and loses connectivity, it will retry. Without idempotency, the retry creates a duplicate.
The Idempotency-Key header is a widely adopted convention rather than a ratified standard. The IETF’s Idempotency-Key HTTP Header Field draft reached version 07 before expiring, so treat it as a well-documented pattern to copy rather than a specification to cite in a contract.
Require an Idempotency-Key header on all write operations. The server checks whether it has already processed a request with that key and returns the original response instead of processing again. Generate the key client-side (a UUID) and attach it to the request before the first attempt.
Optimistic updates with server reconciliation
For responsiveness, update the local UI immediately when the user takes an action. Queue the API call. When connectivity returns, send the request. If the server rejects it (conflict, validation failure), roll back the local state and notify the user.
This pattern requires the API to return clear, actionable error responses so the app can explain what happened and offer a resolution.
Delta sync
For list and feed endpoints, support delta synchronisation. The client sends its last-known sync token or timestamp. The server returns only items that have changed since then.
GET /api/v1/orders?since=2026-04-10T08:00:00Z
This reduces payload sizes dramatically for apps that sync frequently. Combine with ETags for cache validation on individual resource endpoints.
Retry strategy
Implement exponential backoff with jitter on the client:
- First retry: 1 second + random jitter (0-500ms)
- Second retry: 2 seconds + jitter
- Third retry: 4 seconds + jitter
- Maximum: 30 seconds between retries, then stop and notify the user
The server should return Retry-After headers on 429 (rate limited) and 503 (service unavailable) responses. The client should respect these.
Payload optimisation
Return only what the client needs
Design endpoints with mobile in mind. If the mobile app only needs a user’s name and avatar for a list view, provide a lightweight list endpoint rather than forcing the app to fetch full user profiles.
For REST APIs, use sparse fieldsets (?fields=name,avatar) or separate list and detail endpoints. For GraphQL, the client controls field selection natively.
Pagination
Always paginate list endpoints. Use cursor-based pagination rather than offset-based for mobile:
GET /api/v1/orders?limit=20&after=cursor_abc123
Cursor-based pagination is stable when items are added or removed between pages. Offset-based pagination (skip/take) breaks when the underlying data changes, causing duplicate or missing items.
Compression
Enable gzip or Brotli compression on all API responses. This is configured at the server or gateway level and reduces JSON payload sizes by 60-80%. Mobile clients handle decompression natively.
Image handling
Do not return full-resolution image URLs in API responses. Return image URLs with size parameters or use a CDN with on-the-fly resizing (Azure CDN with image optimisation, or Cloudflare Image Resizing). Let the mobile app request the resolution appropriate for its screen density.
Certificate pinning, and why we usually advise against it
Transport Layer Security (TLS) proves you are talking to a server holding a valid certificate for that hostname. It does not prove you are talking to your server. On a device where someone has installed their own trusted root certificate, a proxy can sit in the middle and read everything.
Certificate pinning closes that gap by making the app reject connections that do not present an expected certificate or public key.
Our position is that most business apps should not pin. The protection is real but narrow, and the operational cost is routinely underestimated at design time and paid for later by whoever is on call. We raise it here because it appears on security questionnaires and gets specified by people who will not be maintaining the app. It deserves a straight answer rather than a reflexive yes.
The problem is the users who never update
Everything else in this guide treats “you cannot force an update” as a constraint to design around. Pinning is the one decision that turns that constraint into an outage.
When you rotate a pinned certificate or key, every installed copy of the app carrying the old pin stops working. Not degraded, stopped. And you cannot fix it from the server, because refusing the server is precisely what the app was built to do.
That leaves you needing a store release, review, and near-total adoption before the rotation. Mobile adoption has a long tail. A meaningful share of users sit on builds that are months old, some have automatic updates switched off entirely, and some are on devices that no longer receive your current build at all. Google’s own network security configuration documentation names this directly as the reason pin expiry exists: to prevent connectivity problems “in apps which do not get updates to their pin set, such as when the user disables app updates”.
A certificate renewal is routine infrastructure work. Pinning promotes it into a coordinated release event with a hard deadline and a user-visible failure mode if you miss it.
The other costs
- Emergency replacement becomes the worst case. If a key is compromised and must be replaced immediately, pinning is actively working against your incident response. You need the rotation to happen in hours, and your users need weeks.
- Backup pins can be exhausted. The standard mitigation is shipping a backup pin. Use it once and you are back to a single pin until the next release reaches everyone.
- Legitimate interception breaks. Corporate networks, some VPNs, and enterprise device management inspect TLS. Pinning breaks your app for exactly the enterprise customers most likely to have asked for pinning in the first place.
- Support load moves to your least diagnosable path. A pin failure looks to the user like “the app doesn’t work” with no useful error, on their device, on their network, and often not reproducible by your team.
- Third-party traffic is out of scope. Pinning your own domain does nothing for the analytics, payment, and crash-reporting software development kits (SDKs) shipping data from the same device.
When it is genuinely warranted
Pinning earns its cost in a narrow set of cases:
- The app handles high-value transactions where a successful interception is materially damaging, such as payments, banking, or health records.
- Your threat model explicitly includes a compromised device or a hostile network, and someone has written that model down.
- A regulator, scheme rule, or contract requires it, in which case the decision is made for you.
- You have the release cadence and adoption monitoring to run rotations safely, and the appetite to keep doing so indefinitely.
If you cannot say yes to the last point in particular, pinning will eventually cause you an incident that is worse than the risk it was bought to remove. Treat this as a judgement to make with your own risk and compliance people rather than a default to adopt.
If you do pin, how to make it survivable
None of the following is a substitute for the decision above. It reduces the blast radius rather than removing it.
- Pin the certificate authority, not the leaf. Pinning an intermediate or root that issues your certificates survives routine leaf renewal entirely, which removes the most common rotation trigger. Apple’s identity pinning guidance supports this through
NSPinnedCAIdentities, and it is the single highest-value choice on this list. - Always ship a backup pin, and treat spending it as an incident. When a backup is used, getting a replacement backup out becomes urgent work, not a nice-to-have for the next release.
- Set an expiry, and let it fail open. Both platforms support it: Android takes
expirationon thepin-setelement, after which pinning is disabled for that domain. This is a deliberate trade: users on stale builds lose pinning rather than losing the app. Google’s documentation is explicit that this weakens security for those users, and for most apps that is the right way round. - Deliver the pin set through remote configuration where you can. You already need remote config for the minimum version enforcement described above. Serving pins the same way decouples rotation from the store release cycle. This has its own caveat and it is not a small one: the configuration channel becomes security-critical, so the payload must be signed and validated by the app, and you need a trustworthy path to fetch it. Done carelessly this hands an attacker the ability to set your pins.
- Roll out gradually and watch the failure rate. Ship pinning to a small percentage first and monitor TLS failures by app version. A pinning fault shows up as a connection error rate, so if you are not measuring that, you will hear about it from users instead.
- Build the kill switch before you need it. A remote flag that disables pinning, tested in production, so a bad rotation is a configuration change rather than a store release.
- Tie it to your minimum supported version. Pinning and version enforcement solve the same underlying problem. If you are already prompting users off old builds, your pin rotation window is the same window.
Lighter options worth considering first
For many apps, these address a good share of the risk at a fraction of the operational cost:
- Certificate Transparency. iOS can require it per-domain through
NSRequiresCertificateTransparency. Be clear about scope: this defends against a certificate being mis-issued for your domain without your knowledge, and it does not stop interception on a device where the user has installed a root certificate. It addresses a related risk, not the same one. - Short-lived access tokens with rotating refresh tokens, which limit what an intercepted session is worth.
- Server-side anomaly detection, which catches the traffic patterns interception tends to produce.
Platform mechanics
On iOS, prefer the declarative App Transport Security approach using NSPinnedDomains (iOS 14 and later) over hand-written URLSession delegate validation. Pins are expressed as the Base64-encoded SHA-256 digest of the certificate’s Subject Public Key Info.
On Android, use the network_security_config.xml pin set rather than custom TrustManager code, which is easy to get subtly wrong.
Whichever you choose, test that it actually works. OWASP’s MASTG-TEST-0022 covers verifying pinning behaves as intended, including that it fails the way you expect.
Push notifications
Push notifications are a server-to-device channel that complements the request-response API.
Architecture
- Azure Notification Hubs for cross-platform push (APNs for iOS, FCM for Android) with tag-based targeting
- Backend trigger: the API or a background service publishes a notification when a relevant event occurs (order shipped, message received, appointment reminder)
- Deep linking: include a deep link URL in the notification payload so tapping the notification opens the correct screen in the app
Notification registration
The app registers its device token with the backend on launch and when the token refreshes. Associate the device token with the user account so notifications target the right user, not just the right device.
Handle the case where a user has multiple devices. Either send to all registered devices or let the user manage notification preferences per device.
For our implementation approach, see mobile push notifications.
Versioning for apps you cannot force-update
URL path versioning
Use major version numbers in the URL path: /v1/orders, /v2/orders. This is the clearest approach for mobile because the app hardcodes its API version and the server can route to the correct implementation.
Supporting multiple versions
Plan to support at least the current version and one prior version simultaneously. This gives users time to update. For each version:
- Maintain a separate set of request/response contracts (or a shared contract with version-specific transformations)
- Run integration tests against all supported versions
- Monitor traffic per version to know when adoption of a new version is high enough to deprecate the old one
Minimum version enforcement
Include a minimum supported API version in your app’s remote configuration (Firebase Remote Config, Azure App Configuration). When the app launches, check whether its version is still supported. If not, show an update prompt.
This lets you enforce updates for security patches or breaking changes without relying on app store review timelines.
Non-breaking changes
Add fields, endpoints, and optional parameters freely without bumping the major version. Never remove fields, change field types, or change the meaning of existing fields without a major version bump. Document your compatibility policy so mobile teams know what to expect.
For a deep dive on versioning approaches, see our guide on API versioning strategies.
Azure architecture for mobile backends
A production mobile backend on Azure typically includes:
- Azure API Management as the gateway: rate limiting, JWT validation, analytics, and version routing
- Azure App Service or Container Apps for the API (or BFF layer if you serve multiple client types)
- Azure SQL or Cosmos DB for data (SQL for relational, Cosmos for globally distributed or document-oriented workloads)
- Azure Redis Cache for session state, response caching, and rate limit counters
- Azure Entra ID or AD B2C for authentication
- Azure Notification Hubs for push notifications
- Azure Front Door for global distribution, WAF protection, and DDoS mitigation
- Application Insights for monitoring, distributed tracing, and alerting
For full details on our mobile backend architecture, see mobile app backends. For the CI/CD pipeline that deploys mobile apps alongside their backends, see mobile app CI/CD.
Frequently asked questions
Should I use REST or GraphQL for a mobile app backend?
How do I handle authentication for a mobile app?
How should I version a mobile API?
What is the best Azure architecture for a mobile backend?
How do I handle offline functionality in a mobile app?
Should we use certificate pinning in our mobile app?
How do I handle rate limiting for mobile clients?
Related guides
Backend for Frontend (BFF): API Patterns for Mobile and Web Clients
When to use the BFF pattern, how it compares to a single API or API gateway, and how to implement it on Azure. Practical architecture for teams serving mobile, web, and internal clients.
REST vs GraphQL vs gRPC: Choosing the Right API Style for Enterprise Systems
A practical comparison of REST, GraphQL, gRPC, and tRPC for enterprise teams: when each style fits, the trade-offs, and a decision matrix.
Extending a Ferry Reservation System: An API Layer Architecture
How to build custom apps, disruption tools, and analytics on top of a ferry reservation platform using an operator-owned API layer, without touching the core.