Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/grumpy-lamps-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@clerk/clerk-js': patch
'@clerk/types': patch
---

Fixes stale `SignIn` object on `authenticateWithRedirect` for `saml` and `enterprise_sso` custom flows

Previously, the same connection identifier would be used on every `authenticateWithRedirect` call leading to redirecting to the wrong identity provider
40 changes: 21 additions & 19 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,25 +231,27 @@ export class SignIn extends BaseResource implements SignInResource {
params: AuthenticateWithRedirectParams,
navigateCallback: (url: URL | string) => void,
): Promise<void> => {
const { strategy, redirectUrl, redirectUrlComplete, identifier, oidcPrompt } = params || {};

const { firstFactorVerification } =
(strategy === 'saml' || strategy === 'enterprise_sso') && this.id
? await this.prepareFirstFactor({
strategy,
redirectUrl: SignIn.clerk.buildUrlWithAuth(redirectUrl),
actionCompleteRedirectUrl: redirectUrlComplete,
oidcPrompt,
})
: await this.create({
strategy,
identifier,
redirectUrl: SignIn.clerk.buildUrlWithAuth(redirectUrl),
actionCompleteRedirectUrl: redirectUrlComplete,
oidcPrompt,
});

const { status, externalVerificationRedirectURL } = firstFactorVerification;
const { strategy, redirectUrl, redirectUrlComplete, identifier, oidcPrompt, continueSignIn } = params || {};

if (!this.id || !continueSignIn) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a PR comment a while ago (going to track it again to paste here) that the reasoning for not creating within here was to avoid issues if we created sign-in from AIOs.

My changes tackle exactly for this case, where SignIn provides continueSignIn as true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the issue only saml and enterprise_sso ?
I think adding a new property just makes things more complicated for the caller. My understanding is that now developers would need to pass both identifier and continueSignIn.

Can we get around this by checking if identifiers match ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enterprise_sso and saml since those rely on the identifier to query for the SSO connection.

They wouldn't have to pass continueSignIn to authenticateWithRedirect since it defaults to false, but it's being passed from SignInStart since it always creates a fresh sign-in:

const res = await safePasswordSignInForEnterpriseSSOInstance(signIn.create(buildSignInParams(fields)), fields);
switch (res.status) {
case 'needs_identifier':
// Check if we need to initiate an enterprise sso flow
if (res.supportedFirstFactors?.some(ff => ff.strategy === 'saml' || ff.strategy === 'enterprise_sso')) {
await authenticateWithEnterpriseSSO();
}
break;
case 'needs_first_factor':
if (res.supportedFirstFactors?.every(ff => ff.strategy === 'enterprise_sso')) {
await authenticateWithEnterpriseSSO();
break;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get around this by checking if identifiers match ?

I guess this will work 🤔 but we still have the problem where SignInStart calls authenticateWithRedirect, so we cannot place this logic there otherwise it'd create two sign-ins.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, is that because on needs_identifier we want to call prepare instead of create ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, is that because on needs_identifier we want to call prepare instead of create ?

Yeah, exactly, so SignInStart needs to handle the progressive flow. Custom flows also need to do so, so it's fair to also always create a fresh sign-in.

await this.create({
strategy,
identifier,
redirectUrl: SignIn.clerk.buildUrlWithAuth(redirectUrl),
actionCompleteRedirectUrl: redirectUrlComplete,
});
}

if (strategy === 'saml' || strategy === 'enterprise_sso') {
await this.prepareFirstFactor({
strategy,
redirectUrl: SignIn.clerk.buildUrlWithAuth(redirectUrl),
actionCompleteRedirectUrl: redirectUrlComplete,
oidcPrompt,
});
}

const { status, externalVerificationRedirectURL } = this.firstFactorVerification;

if (status === 'unverified' && externalVerificationRedirectURL) {
navigateCallback(externalVerificationRedirectURL);
Expand Down
1 change: 1 addition & 0 deletions packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ function SignInStartInternal(): JSX.Element {
redirectUrl,
redirectUrlComplete,
oidcPrompt: ctx.oidcPrompt,
continueSignIn: true,
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ describe('SignInStart', () => {
strategy: 'enterprise_sso',
redirectUrl: 'http://localhost/#/sso-callback',
redirectUrlComplete: '/',
continueSignIn: true,
});
});
});
Expand All @@ -314,6 +315,7 @@ describe('SignInStart', () => {
strategy: 'enterprise_sso',
redirectUrl: 'http://localhost/#/sso-callback',
redirectUrlComplete: '/',
continueSignIn: true,
});
});
});
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export type AuthenticateWithRedirectParams = {
*/
continueSignUp?: boolean;

/**
* Whether to continue existing SignIn (if present) or create a new SignIn.
*/
continueSignIn?: boolean;

/**
* One of the supported OAuth providers you can use to authenticate with, eg 'oauth_google'.
* Alternatively `saml` or `enterprise_sso`, to authenticate with Enterprise SSO.
Expand Down