mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 10:13:31 +00:00
[PM-1632] Redirect on SSO required response from connect/token (#17637)
* feat: add Identity Sso Required Response type as possible response from token endpoint. * feat: consume sso organization identifier to redirect user * feat: add get requiresSso to AuthResult for more ergonomic code. * feat: sso-redirect on sso-required for CLI and Desktop * chore: fixing type errors * test: fix and add tests for new sso method * docs: fix misspelling * fix: get email from AuthResult instead of the FormGroup * fix:claude: when email is not available for SSO login show error toast. * fix:claude: add null safety check
This commit is contained in:
@@ -33,19 +33,27 @@ export class DefaultLoginComponentService implements LoginComponentService {
|
||||
*/
|
||||
async redirectToSsoLogin(email: string): Promise<void | null> {
|
||||
// Set the state that we'll need to verify the SSO login when we get the code back
|
||||
const [state, codeChallenge] = await this.setSsoPreLoginState();
|
||||
|
||||
// Set the email address in state. This is used in 2 places:
|
||||
// 1. On the web client, on the SSO component we need the email address to look up
|
||||
// the org SSO identifier. The email address is passed via query param for the other clients.
|
||||
// 2. On all clients, after authentication on the originating client the SSO component
|
||||
// will need to look up 2FA Remember token by email.
|
||||
await this.ssoLoginService.setSsoEmail(email);
|
||||
const [state, codeChallenge] = await this.setSsoPreLoginState(email);
|
||||
|
||||
// Finally, we redirect to the SSO login page. This will be handled by each client implementation of this service.
|
||||
await this.redirectToSso(email, state, codeChallenge);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the user to the SSO login page, either via route or in a new browser window.
|
||||
* @param email The email address of the user attempting to log in
|
||||
*/
|
||||
async redirectToSsoLoginWithOrganizationSsoIdentifier(
|
||||
email: string,
|
||||
orgSsoIdentifier: string,
|
||||
): Promise<void | null> {
|
||||
// Set the state that we'll need to verify the SSO login when we get the code back
|
||||
const [state, codeChallenge] = await this.setSsoPreLoginState(email);
|
||||
|
||||
// Finally, we redirect to the SSO login page. This will be handled by each client implementation of this service.
|
||||
await this.redirectToSso(email, state, codeChallenge, orgSsoIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op implementation of redirectToSso
|
||||
*/
|
||||
@@ -53,6 +61,7 @@ export class DefaultLoginComponentService implements LoginComponentService {
|
||||
email: string,
|
||||
state: string,
|
||||
codeChallenge: string,
|
||||
orgSsoIdentifier?: string,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
@@ -65,9 +74,9 @@ export class DefaultLoginComponentService implements LoginComponentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state required for verifying SSO login after completion
|
||||
* Set the state that we'll need to verify the SSO login when we get the authorization code back
|
||||
*/
|
||||
private async setSsoPreLoginState(): Promise<[string, string]> {
|
||||
private async setSsoPreLoginState(email: string): Promise<[string, string]> {
|
||||
// Generate SSO params
|
||||
const passwordOptions: any = {
|
||||
type: "password",
|
||||
@@ -93,6 +102,13 @@ export class DefaultLoginComponentService implements LoginComponentService {
|
||||
await this.ssoLoginService.setSsoState(state);
|
||||
await this.ssoLoginService.setCodeVerifier(codeVerifier);
|
||||
|
||||
// Set the email address in state. This is used in 2 places:
|
||||
// 1. On the web client, on the SSO component we need the email address to look up
|
||||
// the org SSO identifier. The email address is passed via query param for the other clients.
|
||||
// 2. On all clients, after authentication on the originating client the SSO component
|
||||
// will need to look up 2FA Remember token by email.
|
||||
await this.ssoLoginService.setSsoEmail(email);
|
||||
|
||||
return [state, codeChallenge];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,14 @@ export abstract class LoginComponentService {
|
||||
*/
|
||||
redirectToSsoLogin: (email: string) => Promise<void | null>;
|
||||
|
||||
/**
|
||||
* Redirects the user to the SSO login page with organization SSO identifier, either via route or in a new browser window.
|
||||
*/
|
||||
redirectToSsoLoginWithOrganizationSsoIdentifier: (
|
||||
email: string,
|
||||
orgSsoIdentifier: string | null | undefined,
|
||||
) => Promise<void | null>;
|
||||
|
||||
/**
|
||||
* Shows the back button.
|
||||
*/
|
||||
|
||||
@@ -381,6 +381,24 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
// redirect to SSO if ssoOrganizationIdentifier is present in token response
|
||||
if (authResult.requiresSso) {
|
||||
const email = this.formGroup?.value?.email;
|
||||
if (!email) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("emailRequiredForSsoLogin"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this.loginComponentService.redirectToSsoLoginWithOrganizationSsoIdentifier(
|
||||
email,
|
||||
authResult.ssoOrganizationIdentifier,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// User logged in successfully so execute side effects
|
||||
await this.loginSuccessHandlerService.run(authResult.userId, authResult.masterPassword);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user