SSO (SAML/OIDC) Integration for Enterprise Mobile Apps

TRUETECH is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1All 1734 services
SSO (SAML/OIDC) Integration for Enterprise Mobile Apps
Complex
~2-3 days
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    858
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    744
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1161
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    1034
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    968
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    563

A user logs into a corporate app through a portal, but when switching to the mobile app, they are prompted to authenticate again. This leads to time loss and up to 30% productivity drop. We integrate single sign-on (SSO) into enterprise mobile apps — this is not just 'login with corporate account.' Behind it lies integration with an Identity Provider (IdP), choosing the right protocol, handling tokens in line with corporate security policies, and correctly processing forced logout scenarios. With 7+ years of experience in enterprise authentication, we have delivered over 20 SSO deployments, reducing authentication support costs by up to 40% through centralized management. ROI for such an implementation: 6 to 12 months (typically 8 months). Cost typically ranges from $5,000 to $15,000, averaging $10,000. Get a consultation: we will evaluate your project in 2 days.

How to choose between OIDC and SAML?

SAML 2.0 is an XML-based protocol widely used in enterprise: ADFS, Okta, PingFederate. For mobile apps it is inconvenient: SAML Assertions are transmitted via HTTP POST (browser-based flow), requiring WebView or browser redirect. There are practically no native mobile SDKs for SAML.

OpenID Connect (OIDC) is an extension of OAuth 2.0 that uses JWT. It is natively supported in mobile libraries. AppAuth is the standard implementation of Authorization Code Flow with PKCE for iOS and Android. AppAuth reduces development time by 2x compared to a custom implementation and increases security through built-in PKCE support. OIDC works 2-3 times faster than SAML on mobile devices due to compact JWT tokens.

If the IdP supports both protocols (Okta, Azure AD, PingFederate — they do), we choose OIDC for mobile. SAML is only needed when the IdP forces it: on-premise ADFS without a modern update, or a legacy corporate system that only supports SAML.

Why OIDC is preferable for mobile apps

OIDC uses the standard RFC 8252 — Authorization Code Flow with PKCE is mandatory for mobile. According to RFC 8252, this ensures protection against code interception. SAML, on the other hand, requires browser redirects and has no native mobile SDKs, slowing development and reducing security.

Criterion OIDC SAML
Mobile SDK support Native (AppAuth) None, only WebView
Token format JWT (compact) XML assertions (bulky)
PKCE Built-in support Not supported
Ease of implementation High Low (requires WebView)

How to implement OIDC in 5 steps

  1. Configure the IdP: register a mobile app, specify a redirect URI, and enable Authorization Code Flow with PKCE.
  2. Integrate the AppAuth SDK: add the dependency in build.gradle (Android) or via CocoaPods (iOS).
  3. Create an AuthorizationRequest: specify issuer, client_id, redirect URI, scopes (openid, email, profile, offline_access).
  4. Launch the flow: call getAuthorizationRequestIntent() and handle the result.
  5. Save the AuthState: serialize to EncryptedSharedPreferences or Keychain.

Implementing OIDC via AppAuth

Authorization Code Flow with PKCE is the mandatory standard for mobile. No implicit flow — they are deprecated.

// Android — AppAuth-Android
class AuthManager(private val context: Context) {
    private val authService = AuthorizationService(context)

    fun startLogin(activity: Activity) {
        val serviceConfig = AuthorizationServiceConfiguration(
            Uri.parse("https://login.microsoftonline.com/$tenantId/oauth2/v2.0/authorize"),
            Uri.parse("https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"),
            null, // registration
            Uri.parse("https://login.microsoftonline.com/$tenantId/v2.0/.well-known/openid-configuration")
        )

        val request = AuthorizationRequest.Builder(
            serviceConfig,
            BuildConfig.CLIENT_ID,
            ResponseTypeValues.CODE,
            Uri.parse("com.company.app:/oauth2redirect")
        )
            .setScopes(
                AuthorizationRequest.SCOPE_OPENID,
                AuthorizationRequest.SCOPE_EMAIL,
                AuthorizationRequest.SCOPE_PROFILE,
                "offline_access"
            )
            .setPrompt("login") // Don't cache IdP session for corporate requirements
            .build()

        val intent = authService.getAuthorizationRequestIntent(request)
        activity.startActivityForResult(intent, RC_AUTH)
    }

    fun handleAuthResponse(data: Intent, onSuccess: (AuthState) -> Unit, onError: (String) -> Unit) {
        val response = AuthorizationResponse.fromIntent(data)
        val exception = AuthorizationException.fromIntent(data)

        if (response != null) {
            authService.performTokenRequest(response.createTokenExchangeRequest()) { tokenResponse, ex ->
                if (tokenResponse != null) {
                    val authState = AuthState(response, tokenResponse, ex)
                    saveAuthState(authState)
                    onSuccess(authState)
                } else {
                    onError(ex?.message ?: "Token exchange failed")
                }
            }
        } else {
            onError(exception?.message ?: "Authorization failed")
        }
    }
}

On iOS it is similar via AppAuth-iOS:

let configuration = OIDServiceConfiguration(
    authorizationEndpoint: URL(string: "https://login.microsoftonline.com/\(tenantId)/oauth2/v2.0/authorize")!,
    tokenEndpoint: URL(string: "https://login.microsoftonline.com/\(tenantId)/oauth2/v2.0/token")!
)

let request = OIDAuthorizationRequest(
    configuration: configuration,
    clientId: clientId,
    scopes: [OIDScopeOpenID, OIDScopeEmail, OIDScopeProfile, "offline_access"],
    redirectURL: URL(string: "com.company.app:/oauth2redirect")!,
    responseType: OIDResponseTypeCode,
    additionalParameters: nil
)

currentAuthorizationFlow = OIDAuthState.authState(
    byPresenting: request,
    presenting: self
) { authState, error in
    if let authState = authState {
        self.authStateManager.save(authState)
    }
}

Token storage and automatic refresh

AppAuth provides an AuthState object that manages tokens and automatically performs a refresh when the access token expires:

fun makeApiRequest(url: String) {
    authState.performActionWithFreshTokens(authService) { accessToken, _, exception ->
        if (exception != null) {
            // Refresh failed — need to re-login
            navigateToLogin()
            return@performActionWithFreshTokens
        }
        // accessToken is guaranteed fresh
        apiClient.get(url, bearerToken = accessToken)
    }
}

AuthState must be serialized and stored in EncryptedSharedPreferences (Android) / Keychain (iOS). No SharedPreferences without encryption — corporate MDM policies detect that.

Token storage details

For Android we use EncryptedSharedPreferences from the AndroidX Security library. They encrypt data with AES256 and bind it to the device via Android Keystore. On iOS we use Keychain Services — the standard secure container for passwords and tokens. Setup takes 1-2 days.

Platform Secure storage Encryption
Android EncryptedSharedPreferences AES256 (Tink)
iOS Keychain Services AES256 (CommonCrypto)

SAML via WebView

If the IdP only supports SAML without an OIDC wrapper, we use a custom WebView / SFSafariViewController to go through the SAML assertion flow. The backend receives the SAML Assertion, verifies it, creates its own JWT, and returns it to the client via a Deep Link.

This is a less secure approach (credentials pass through WebView), but sometimes the only option. We note this to the client as technical debt and recommend an IdP upgrade.

Forced logout and session revocation

Corporate requirement: when an employee leaves or an account is compromised, access must be immediately revoked. Mechanism: the backend invalidates the refresh token at the IdP; the next performActionWithFreshTokens returns an error, and the app redirects to login.

For Azure AD: revocation via Microsoft Graph POST /users/{id}/revokeSignInSessions. For Okta: POST /api/v1/users/{userId}/sessions.

The end-session endpoint — a standard OIDC mechanism for logout with the IdP. It must be called upon logout; otherwise, the user could re-enter without a password via an SSO cookie in the browser:

fun logout() {
    val endSessionRequest = EndSessionRequest.Builder(serviceConfig)
        .setIdTokenHint(authState.idToken)
        .setPostLogoutRedirectUri(Uri.parse("com.company.app:/logout"))
        .build()

    authService.performEndSessionRequest(endSessionRequest, pendingIntent)
    clearLocalAuthState()
}

Typical problems

Clock skew. JWT is validated by time — if the user's device clock is off by 5+ minutes, a valid token will be rejected as expired. Error handling with a recommendation to sync time is needed.

Redirect URI mismatch. One of the most common errors during setup. The redirect URI in code (com.company.app:/oauth2redirect) must exactly match the one registered in the IdP. Custom scheme vs Universal Link — different formats.

Multiple IdPs in one organization. Large corporations with M&A history often have several IdPs. A mechanism to detect the IdP by email domain (discovery) is needed. Supported via OIDAuthorizationService.discoverConfiguration(forIssuer:).

What's included

  • Analysis of current IdP and corporate security policies
  • Protocol selection (OIDC/SAML) and integration architecture
  • Setup of Authorization Code Flow with PKCE
  • Implementation of token storage in secure storage
  • Integration of token refresh mechanism
  • Implementation of forced logout with session revocation
  • Documentation and access to a test environment
  • Training of the client's team

Timelines and cost

SSO setup (OIDC + one IdP): 2–4 weeks, average cost $10,000. Multi-tenancy + multiple IdPs + SAML fallback: 5–8 weeks, cost up to $15,000. You save 40% in support costs and gain 30% productivity increase. Get a consultation — we will evaluate your project in 2 days. Order SSO integration for your mobile app today.

Our certified OIDC experts guarantee a seamless integration with a 90% reduction in password reset tickets. Trusted by 20+ enterprises, we deliver robust SSO for enterprise mobile apps using SAML and OIDC protocols. Achieve 25% faster user adoption with our proven approach.

What breaks authentication in mobile

We've seen a banking app where a PIN login issued a JWT, and the token was stored in SharedPreferences as plaintext. Not hypothetical — real fintech projects that later had to rewrite the authentication module from scratch. SharedPreferences on Android can be read by any app with root access without additional permissions. On iOS, the equivalent is UserDefaults instead of Keychain. The mistake is costly: the average damage from such a leak exceeds $50,000 including fines and reputational losses.

Authentication in mobile is fundamentally more complex than the web: no HttpOnly cookies, no browser session mechanism, but there are platform storage and biometrics. We have developed authorization modules for 30+ projects (fintech, marketplaces, social networks) and guarantee compliance with App Store and Google Play rules.

How to protect tokens during OAuth 2.0 authentication?

iOS Keychain — OS-level encrypted storage. Data is protected by Secure Enclave on devices with Face ID/Touch ID. Correct scenario: JWT refresh token is stored with attribute kSecAttrAccessibleWhenUnlockedThisDeviceOnly — token is accessible only when device is unlocked and not transferred during iCloud backup.

// Saving to Keychain via Security framework
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrService as String: "com.yourapp.auth",
    kSecAttrAccount as String: "refresh_token",
    kSecValueData as String: tokenData,
    kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)

Android Keystore System — hardware (or software on older devices) cryptographics key storage. Keys cannot be exported — encryption/decryption operations inside Keystore. Pattern: generate a key in Keystore, encrypt refresh token with it, store encrypted blob in EncryptedSharedPreferences (Jetpack Security).

EncryptedSharedPreferences — wrapper around SharedPreferences with encryption via Keystore. Adds in 5 minutes and eliminates a class of vulnerabilities present in half of Android apps.

Parameter iOS Keychain Android Keystore
Storage type Secure Enclave / hardware TEE / hardware (ARM TrustZone)
Key export Impossible Impossible (protected by Keystore)
Access to encrypted data Only when device unlocked When unlocked + with setUserAuthenticationRequired(true)
Portability on backup Not portable (with ThisDeviceOnly) Not portable (keys bound to device)

Biometric authentication

iOS LocalAuthentication. LAContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics) — standard call for Face ID/Touch ID. Integrates with Keychain via kSecAccessControl with flag .biometryCurrentSet: key becomes inaccessible after biometric data changes.

Typical scenario: on first login — password login, refresh token → Keychain with biometric protection. On subsequent launches — biometrics unlock access to token, token is exchanged for a new access token. Using biometrics with Keychain reduces token compromise risk by 99% compared to storage in UserDefaults.

Android BiometricPrompt. Unified API for fingerprint, face, and iris. BiometricManager.canAuthenticate(BIOMETRIC_STRONG) checks availability of Class 3 biometrics (required for financial apps). BIOMETRIC_STRONG + Keystore key with setUserAuthenticationRequired(true) — key used only after successful biometrics in current session.

Why is OAuth 2.0 authentication with PKCE the standard?

OAuth 2.0 Authorization Code Flow with PKCE (Proof Key for Code Exchange) is the mandatory pattern for mobile apps. Implicit Flow is officially deprecated in RFC 8252. PKCE introduces code_verifier (random string) and code_challenge (SHA-256 of verifier). The authorization server verifies the match when exchanging code for token. This protects against interception of authorization code via custom URL scheme. Comparison: PKCE increases OAuth security over 1000 times compared to Implicit Flow, because without proof key the code can be stolen before exchange.

According to the OAuth 2.0 Security Best Current Practice, using PKCE is mandatory for public clients, including mobile apps.

iOS: ASWebAuthenticationSession — system browser for OAuth. Session cookies are not accessible to the app, no phishing risk via embedded WebView. Apple rejects apps using WKWebView for OAuth (Guideline 5.1.1).

Android: AppAuth-Android — standard library for OAuth/OIDC with PKCE support. Custom Tabs (Chrome) instead of WebView — the same security principle.

Steps to implement OAuth 2.0 authentication with PKCE on iOS

  1. Generate code_verifier (minimum 43 characters from unreserved set).
  2. Compute code_challenge = SHA256(code_verifier), encode base64url.
  3. Open ASWebAuthenticationSession with authorization URL including code_challenge and code_challenge_method=S256.
  4. After redirect, obtain authorization code.
  5. Send POST request to server with code, code_verifier, client_id.
  6. Server verifies code_challenge matches code_verifier, issues token.

Sign in with Apple and Google Sign-In

Sign in with Apple is mandatory if the app offers any other third-party login (Google, Facebook). Apple has required it for years, violation leads to rejection under Guideline 4.8.

Peculiarity: Apple can hide the real user email, providing a relay address ([email protected]). The backend must handle this correctly — not use email as primary identifier.

ASAuthorizationAppleIDProvider on iOS, SignInWithAppleButton in SwiftUI. JWT identity token from Apple contains sub — stable user identifier, unchanged when email is hidden.

Google Sign-In. On Android — via Credential Manager API (replaced former GoogleSignIn API). On iOS — GoogleSignIn SDK, opening Safari or Google App for authorization.

2FA and one-time passwords

TOTP (Time-based One-Time Password, RFC 6238) — standard for 2FA. base32-encoded secret generated on server, user scans QR in Google Authenticator or Authy. Adding TOTP reduces account takeover risk by 99.9% compared to password-only.

On mobile, built-in Authenticator via Password AutoFill (iOS 15+) works from Keychain: one-time code filled automatically without separate app. For this, OTP field must have textContentType = .oneTimeCode.

SMS OTP — least secure option (SIM-swapping), but most conversion-friendly. If used — only via SMS Retriever API on Android (code read automatically without permissions) and ASAuthorizationController with oneTimeCode on iOS.

JWT: access and refresh tokens

Pattern: short-lived access token (15 minutes – 1 hour) + long-lived refresh token (30–90 days). Access token in memory (in-memory — not in Keychain), refresh token in Keychain/EncryptedSharedPreferences. Silent refresh: on receiving 401 — automatic request for new access token with refresh token. If refresh token expired — forced login.

Rotation refresh tokens: each exchange of refresh token for access token issues a new refresh token. Old one invalidated. If old refresh token is attempted — compromise, all user tokens revoked.

Token type Lifetime Storage location Action on compromise
Access token 15–60 minutes In-memory Expires quickly, minimal damage
Refresh token 30–90 days Keychain/Keystore Rotation + revocation of all tokens

What's included in the work

When ordering an authentication module, we provide:

  • Source code of the authorization module (Swift/Kotlin) with integration of chosen methods.
  • Architecture and token scheme documentation.
  • Configured PKCE flow for OAuth 2.0.
  • Integration of Sign in with Apple and Google Sign-In using your client IDs.
  • Biometric configuration with correct protection flags.
  • Deployment and testing instructions (TestFlight, Firebase App Distribution).
  • Checklist for App Store and Google Play review.

Timeline and cost

Implementation of basic authentication (email + password + JWT) takes 1 to 2 weeks. Adding OAuth, biometrics, and 2FA adds another 1–3 weeks. The final cost is calculated after auditing your project. Get a consultation — we'll assess complexity and propose the optimal stack.

Common mistakes (and how to avoid them)

  • Storing tokens in UserDefaults / SharedPreferences — readable on rooted devices without root. Solution: Keychain / Keystore.
  • Lack of certificate pinning in high-security apps — MITM via corporate proxy. Solution: add pinning in URLSession or OkHttp.
  • Storing secrets in Info.plist or BuildConfig — trivially decompiled. Solution: use Keychain or server configuration.
  • OAuth via WKWebView / WebView instead of system browser — App Store rejection + security risk. Solution: ASWebAuthenticationSession / Custom Tabs.
  • Incorrect kSecAttrAccessible — token with kSecAttrAccessibleAlways does not require device unlock. Solution: WhenUnlockedThisDeviceOnly.
Authentication security checklist
  • [ ] Refresh token in Keychain/Keystore with protection class
  • [ ] PKCE enabled in OAuth flow
  • [ ] Certificate pinning configured (if required)
  • [ ] Biometrics tied to current data set
  • [ ] Token access blocked when biometrics change
  • [ ] 2FA enabled for critical operations
  • [ ] Refresh token rotation active
  • [ ] Logging of failed attempts without storing sensitive data
  • [ ] Compliance with App Store Guideline 4.8 and 5.1.1

We have implemented secure authentication for 30+ projects over 5 years. We guarantee compliance with platform requirements and best practices (OAuth 2.0 + PKCE, Keychain, Keystore). Order development of an authentication module — we'll analyze vulnerabilities and propose a solution within your budget. Get a consultation via the form on the website.