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
- Configure a SAML enterprise connection (e.g. Okta) for a domain.
attemptSignIn(strategy: …, identifier: <email>), then ssoSignIn(context, Strategy.enterpriseSSO) (the pattern ClerkSignInPanel uses).
- 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:295 — if (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:272 → isSSO => 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.
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 in0.0.14-beta,0.0.15-beta, and0.0.16-beta...all affected. The full SAML browser round-trip works (IdP login → ACS → callback withrotating_token_nonce) but the SDK just never converts the result into a session.Environment
clerk_flutter/clerk_auth: 0.0.16-betasamlc_…)ClerkAuthStatedirectly (also reproduces via the prebuiltClerkAuthenticationpanel, which uses the samessoSignInpath)Steps to reproduce
attemptSignIn(strategy: …, identifier: <email>), thenssoSignIn(context, Strategy.enterpriseSSO)(the patternClerkSignInPaneluses).422 identifier is required(Bug 1). If you patch Bug 1 locally, the IdP flow completes and the callback arrives withrotating_token_nonce, but sign-in still doesn't complete (Bug 2) -signIn.verification.statusstaysunverified.Bug 1:
oauthSignInnever forwardsidentifier, so enterprise_sso is rejectedssoSignIn(context, Strategy.enterpriseSSO)→oauthSignIncallscreateSignInwithout an identifier:packages/clerk_auth/lib/src/clerk_auth/auth.dart:359(oauthSignInsignature has no identifier)packages/clerk_auth/lib/src/clerk_auth/auth.dart:365-createSignIn(strategy: strategy, redirectUrl: redirectUrl)(no identifier)But
enterprise_ssorequires the email to match the connection by domain, so the server responds:createSignInalready acceptsidentifier(packages/clerk_auth/lib/src/clerk_api/api.dart:333);oauthSignIn/ssoSignInjust 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:
parseDeepLinkdoesn't treat thesamlverification strategy as SSO, so completion is silently skippedAfter the IdP round-trip, the callback
com.clerk.flutter://callback?rotating_token_nonce=…is received andparseDeepLinkruns, but takes no action:packages/clerk_flutter/lib/src/clerk_auth_state.dart:295—if (verification.strategy.isSSO) { … completeOAuthSignIn(token) … }strategy: saml(even though sign-in was initiatedwith
enterprise_sso).Strategy.saml.isSSO == false:packages/clerk_auth/lib/src/models/client/strategy.dart:272→isSSO => name == 'oauth' || isEnterpriseSSO(excludessaml;samlis defined at :113).So the
ifis false,completeOAuthSignInis never called despite the nonce being present, and theSignInstaysunverified, no session is created.Proposed fix
Bug 1: forward the identifier (it's already a
createSignInparam):Bug 2: recognize
samlas a completion-eligible SSO strategy. Either localize it inparseDeepLink:or, arguably more correct, include
samlinStrategy.isSSO: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:enterprise_sso needs the email to match the connection by domain, so the server returns:
Bug 2: parseDeepLink ignores the
samlverification 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:
For a SAML connection the server returns the verification with strategy
saml(even when initiated with enterprise_sso), and Strategy.saml.isSSO == false:
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=samlCode sample
Code sample
Flutter Doctor output
Doctor output