1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-30 15:13:32 +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:
Ike
2025-12-10 10:31:28 -05:00
committed by GitHub
parent 852248d5fa
commit 0e277a411d
19 changed files with 308 additions and 48 deletions

View File

@@ -13,6 +13,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request";
import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/webauthn-login-token.request";
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
import { IdentitySsoRequiredResponse } from "@bitwarden/common/auth/models/response/identity-sso-required.response";
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
@@ -49,7 +50,8 @@ import { CacheData } from "../services/login-strategies/login-strategy.state";
type IdentityResponse =
| IdentityTokenResponse
| IdentityTwoFactorResponse
| IdentityDeviceVerificationResponse;
| IdentityDeviceVerificationResponse
| IdentitySsoRequiredResponse;
export abstract class LoginStrategyData {
tokenRequest:
@@ -128,6 +130,8 @@ export abstract class LoginStrategy {
return [await this.processTokenResponse(response), response];
} else if (response instanceof IdentityDeviceVerificationResponse) {
return [await this.processDeviceVerificationResponse(response), response];
} else if (response instanceof IdentitySsoRequiredResponse) {
return [await this.processSsoRequiredResponse(response), response];
}
throw new Error("Invalid response object.");
@@ -398,4 +402,19 @@ export abstract class LoginStrategy {
result.requiresDeviceVerification = true;
return result;
}
/**
* Handles the response from the server when a SSO Authentication is required.
* It hydrates the AuthResult with the SSO organization identifier.
*
* @param {IdentitySsoRequiredResponse} response - The response from the server indicating that SSO is required.
* @returns {Promise<AuthResult>} - A promise that resolves to an AuthResult object
*/
protected async processSsoRequiredResponse(
response: IdentitySsoRequiredResponse,
): Promise<AuthResult> {
const result = new AuthResult();
result.ssoOrganizationIdentifier = response.ssoOrganizationIdentifier;
return result;
}
}

View File

@@ -10,6 +10,7 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for
import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request";
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
import { IdentitySsoRequiredResponse } from "@bitwarden/common/auth/models/response/identity-sso-required.response";
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
import { HashPurpose } from "@bitwarden/common/platform/enums";
@@ -165,14 +166,20 @@ export class PasswordLoginStrategy extends LoginStrategy {
identityResponse:
| IdentityTokenResponse
| IdentityTwoFactorResponse
| IdentityDeviceVerificationResponse,
| IdentityDeviceVerificationResponse
| IdentitySsoRequiredResponse,
credentials: PasswordLoginCredentials,
authResult: AuthResult,
): Promise<void> {
// TODO: PM-21084 - investigate if we should be sending down masterPasswordPolicy on the
// IdentityDeviceVerificationResponse like we do for the IdentityTwoFactorResponse
// If the response is a device verification response, we don't need to evaluate the password
if (identityResponse instanceof IdentityDeviceVerificationResponse) {
// If SSO is required, we also do not evaluate the password here, since the user needs to first
// authenticate with their SSO IdP Provider
if (
identityResponse instanceof IdentityDeviceVerificationResponse ||
identityResponse instanceof IdentitySsoRequiredResponse
) {
return;
}

View File

@@ -8,9 +8,9 @@ export class SsoUrlService {
* @param webAppUrl The URL of the web app
* @param clientType The client type that is initiating SSO, which will drive how the response is handled
* @param redirectUri The redirect URI or callback that will receive the SSO code after authentication
* @param state A state value that will be peristed through the SSO flow
* @param state A state value that will be persisted through the SSO flow
* @param codeChallenge A challenge value that will be used to verify the SSO code after authentication
* @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier
* @param email The optional email address of the user initiating SSO, which will be used to look up the org SSO identifier
* @param orgSsoIdentifier The optional SSO identifier of the org that is initiating SSO
* @returns The URL for redirecting users to the web app SSO component
*/