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.

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