SSO Single Sign-On setup for corporate mobile app SAML OIDC

NOVASOLUTIONS.TECHNOLOGY 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 1 servicesAll 1735 services
SSO Single Sign-On setup for corporate mobile app SAML OIDC
Complex
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

SSO Configuration for Corporate Mobile Application (SAML/OIDC)

SSO in corporate mobile app — not just "login with corporate account". Behind it stands IdP integration, right protocol choice, token handling per corporate security policies, and proper forced logout scenarios.

OIDC vs SAML: Protocol Choice

SAML 2.0 — XML-based protocol from 2005. Widely used in enterprise: ADFS, Okta, PingFederate. For mobile apps — inconvenient: SAML Assertions transmitted via HTTP POST (browser-based flow), requiring WebView or browser redirect. No native mobile SDK for SAML.

OpenID Connect (OIDC) — OAuth 2.0 overlay, uses JWT. Natively supported in mobile libraries. AppAuth — standard Authorization Code Flow with PKCE for iOS and Android.

If IdP supports both protocols (Okta, Azure AD, PingFederate do), for mobile choose OIDC. SAML needed only when IdP forces it: on-premise ADFS without modern update, or legacy corp system SAML-only.

OIDC Implementation via AppAuth

Authorization Code Flow with PKCE — mandatory standard for mobile (RFC 8252). No implicit flow — 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,
            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 similarly 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 AuthState — manages tokens and automatically refreshes when access token expires:

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

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

SAML via WebView

If IdP supports only SAML without OIDC wrapper — use custom WebView / SFSafariViewController for SAML assertion flow. Backend receives SAML Assertion, verifies, creates own JWT and returns via Deep Link.

Less secure approach (credentials pass through WebView), but sometimes only option. Flag this to customer as technical debt and recommend IdP upgrade.

Forced Logout and Session Revocation

Corporate requirement: on employee termination or account compromise — immediate access revocation. Mechanism: backend invalidates refresh token in IdP, next performActionWithFreshTokens fails, app redirects to login.

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

End-session endpoint — standard OIDC logout mechanism. Important to call on logout, otherwise user can re-login without password via SSO cookie in browser:

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

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

Common Issues

Clock skew. JWT validated by time — if device clock is 5+ minutes off, valid token rejected as expired. Handle with recommendation to sync time.

Redirect URI mismatch. Most common setup error. Redirect URI in code (com.company.app:/oauth2redirect) must match exactly what registered in IdP. Custom scheme vs Universal Link — different formats.

Multiple IdP in one organization. Large corps with M&A history often have multiple IdP. Need IdP discovery by email domain. Supported via OIDAuthorizationService.discoverConfiguration(forIssuer:).

SSO setup (OIDC + one IdP): 2–4 weeks. Multi-tenancy + multiple IdP + SAML fallback: 5–8 weeks. Cost estimated individually.