Skip to content

Enterprise SAML SSO sign-in never completes: oauthSignIn drops "identifier", and parseDeepLink ignores the "saml" verification strategy #421

Description

@sarkisdev

Summary

Enterprise SSO sign-in (Strategy.enterpriseSSO) backed by a SAML connection cannot complete. Two independent bugs in the sign-in path each block it. Confirmed in 0.0.14-beta, 0.0.15-beta, and 0.0.16-beta ...all affected. The full SAML browser round-trip works (IdP login → ACS → callback with rotating_token_nonce) but the SDK just never converts the result into a session.

Environment

  • clerk_flutter / clerk_auth: 0.0.16-beta
  • Platform: iOS (in-app WebView SSO flow)
  • IdP: Okta, SAML 2.0 enterprise connection (samlc_…)
  • Custom sign-in UI driving ClerkAuthState directly (also reproduces via the prebuilt ClerkAuthentication panel, which uses the same ssoSignIn path)

Steps to reproduce

  1. Configure a SAML enterprise connection (e.g. Okta) for a domain.
  2. attemptSignIn(strategy: …, identifier: <email>), then ssoSignIn(context, Strategy.enterpriseSSO) (the pattern ClerkSignInPanel uses).
  3. Observe the 422 identifier is required (Bug 1). If you patch Bug 1 locally, the IdP flow completes and the callback arrives with rotating_token_nonce, but sign-in still doesn't complete (Bug 2) -signIn.verification.status stays unverified.

Bug 1: oauthSignIn never forwards identifier, so enterprise_sso is rejected

ssoSignIn(context, Strategy.enterpriseSSO)oauthSignIn calls createSignIn without an identifier:

  • packages/clerk_auth/lib/src/clerk_auth/auth.dart:359 (oauthSignIn signature has no identifier)
  • packages/clerk_auth/lib/src/clerk_auth/auth.dart:365 - createSignIn(strategy: strategy, redirectUrl: redirectUrl) (no identifier)

But enterprise_sso requires the email to match the connection by domain, so the server responds:

422 | "identifier" is required when "strategy" is "enterprise_sso".

createSignIn already accepts identifier (packages/clerk_auth/lib/src/clerk_api/api.dart:333);
oauthSignIn/ssoSignIn just don't thread it through. (Note: PR #259 added this forwarding but appears to have regressed before any release, it's not in 0.0.14/0.0.15/0.0.16.)

Bug 2: parseDeepLink doesn't treat the saml verification strategy as SSO, so completion is silently skipped

After the IdP round-trip, the callback com.clerk.flutter://callback?rotating_token_nonce=… is received and
parseDeepLink runs, but takes no action:

  • packages/clerk_flutter/lib/src/clerk_auth_state.dart:295if (verification.strategy.isSSO) { … completeOAuthSignIn(token) … }
  • For a SAML connection, the server returns the verification with strategy: saml (even though sign-in was initiated
    with enterprise_sso).
  • Strategy.saml.isSSO == false: packages/clerk_auth/lib/src/models/client/strategy.dart:272isSSO => name == 'oauth' || isEnterpriseSSO (excludes saml; saml is defined at :113).

So the if is false, completeOAuthSignIn is never called despite the nonce being present, and the SignIn stays unverified, no session is created.

Proposed fix

Bug 1: forward the identifier (it's already a createSignIn param):

// auth.dart oauthSignIn(...)
  required Strategy strategy,
  required Uri? redirect,
  String? identifier,                       // add
}) async {
  ...
  await _api
      .createSignIn(strategy: strategy, identifier: identifier, redirectUrl: redirectUrl)  // forward
      .then(_housekeeping);
  ...
}
// clerk_auth_state.dart ssoSignIn(...) — add `String? identifier` and pass it to oauthSignIn(...)

Bug 2: recognize saml as a completion-eligible SSO strategy. Either localize it in parseDeepLink:

if (verification.strategy.isSSO || verification.strategy == Strategy.saml) {

or, arguably more correct, include saml in Strategy.isSSO:

bool get isSSO => name == _oauth || isEnterpriseSSO || this == saml;

Expected results

After completing an enterprise SAML SSO (e.g. Okta) sign-in via ssoSignIn(context, Strategy.enterpriseSSO), the SDK should create a session and auth.isSignedIn should become true.

Actual results

Enterprise SSO backed by a SAML connection never completes. Two separate bugs,
both present in 0.0.14-beta, 0.0.15-beta, and 0.0.16-beta (latest):

Bug 1: oauthSignIn never forwards identifier, so the request is rejected. ssoSignIn → oauthSignIn calls createSignIn WITHOUT an identifier:

• packages/clerk_auth/lib/src/clerk_auth/auth.dart:359  (oauthSignIn has no identifier param)
• packages/clerk_auth/lib/src/clerk_auth/auth.dart:365  createSignIn(strategy: strategy, redirectUrl: redirectUrl)

enterprise_sso needs the email to match the connection by domain, so the server returns:

422 | `identifier` is required when `strategy` is `enterprise_sso`. (createSignIn already accepts `identifier` at packages/clerk_auth/lib/src/clerk_api/api.dart:333 - oauthSignIn/ssoSignIn just don't pass it. PR #259 added this but regressed before release.)

Bug 2: parseDeepLink ignores the saml verification strategy.

After locally patching Bug 1, the full SAML round-trip succeeds (IdP login → ACS → callback com.clerk.flutter://callback?rotating_token_nonce=…), but sign-in still does not complete. parseDeepLink takes no action:

• packages/clerk_flutter/lib/src/clerk_auth_state.dart:295  if (verification.strategy.isSSO) { … completeOAuthSignIn … }

For a SAML connection the server returns the verification with strategy saml
(even when initiated with enterprise_sso), and Strategy.saml.isSSO == false:

• packages/clerk_auth/lib/src/models/client/strategy.dart:272  isSSO => name == 'oauth' || isEnterpriseSSO   (excludes `saml`)

So completeOAuthSignIn is never called despite hasNonce==true, and the SignIn stays unverified - no session is created. Observed: isSignedIn=false verification.status=unverified verification.strategy=saml

Code sample

Code sample
  import 'package:clerk_auth/clerk_auth.dart';
  import 'package:clerk_flutter/clerk_flutter.dart';

  // Prerequisite: a Clerk instance with a SAML (e.g. Okta) enterprise connection
  // configured for "email" domain, and a user assigned in the IdP.
  //
  // This is the exact public-API path ClerkSignInPanel uses for enterprise SSO.
  Future<void> reproEnterpriseSamlSso(BuildContext context, String email) async {
    final auth = await ClerkAuthState.create(
      config: ClerkAuthConfig(publishableKey: '<your pk_test_… key>'),
    );

    await auth.resetClient();

    // Seed the SignIn with the email so Clerk can match the SAML connection.
    await auth.attemptSignIn(strategy: Strategy.password, identifier: email);

    final signIn = auth.client.signIn;
    final hasEnterpriseSso =
        signIn?.factors.any((f) => f.strategy.isEnterpriseSSO) ?? false;
    assert(hasEnterpriseSso, 'enterprise_sso factor not offered for $email');

    // BUG 1: this throws/errors with
    //   422 — `identifier` is required when `strategy` is `enterprise_sso`
    // because oauthSignIn calls createSignIn without the identifier.
    await auth.ssoSignIn(context, Strategy.enterpriseSSO);

    // After patching Bug 1, the IdP round-trip succeeds and the callback arrives
    // with rotating_token_nonce, but this prints:
    //   signedIn=false  status=unverified  strategy=saml
    // because parseDeepLink only completes when verification.strategy.isSSO, and
    // Strategy.saml.isSSO == false (BUG 2).
    final v = auth.client.signIn?.verification;
    debugPrint('signedIn=${auth.isSignedIn} '
        'status=${v?.status} strategy=${v?.strategy}');
  }

Flutter Doctor output

Doctor output
[✓] Flutter (Channel main, 3.45.0-1.0.pre-472, on macOS 27.0 26A5353q darwin-arm64, locale en-US) [2.8s]
    • Flutter version 3.45.0-1.0.pre-472 on channel main at /Users/.../development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 453f94bd2a (7 days ago), 2026-06-12 09:46:33 -0400
    • Engine revision 453f94bd2a
    • Dart version 3.13.0 (build 3.13.0-201.0.dev)
    • DevTools version 2.59.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android,
      enable-ios, cli-animations, enable-native-assets, no-enable-swift-package-manager, omit-legacy-version-file,
      enable-lldb-debugging, enable-uiscene-migration, enable-riscv64

[!] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [4.0s]
    • Android SDK at /Users/.../Library/Android/sdk
    • Emulator version 34.2.15.0 (build_id 11906825) (CL:N/A)
    • Platform android-36, build-tools 36.0.0
    • ANDROID_HOME = /Users/.../Library/Android/sdk
    • Java binary at: /usr/bin/java
      This JDK was found in the system PATH.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment Corretto-19.0.2.7.1 (build 19.0.2+7-FR)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

[✓] Xcode - develop for iOS and macOS (Xcode 26.5) [3.1s]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 17F42
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [5ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (3 available) [12.3s]
    • iPhone (mobile) • 0000XXXX-00XXXXXC1XX2401C • ios            • iOS 27.0 24A5355q
    • macOS (desktop)          • macos                     • darwin-arm64   • macOS 27.0 26A5353q darwin-arm64
    • Chrome (web)             • chrome                    • web-javascript • Google Chrome 149.0.7827.115

[✓] Network resources [5.2s]
    • All expected network resources are available.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workingclerk_authpackage: clerk_authclerk_flutterpackage: clerk_flutterenterprise ssoAn issue affecting Enterprise SSOoktaAn issue related to Okta SSO

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions