{"record":{"id":"41a83a0a1eb07d80","repo":"passbolt/passbolt_api","slug":"single-sign-on-failed-you-must-authenticate-with-azure-again","errorCode":null,"errorMessage":"Single sign-on failed. You must authenticate with Azure again.","messagePattern":"Single sign-on failed\\. You must authenticate with Azure again\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Service/Sso/Azure/SsoAzureService.php","lineNumber":159,"sourceCode":"    // HELPERS\n\n    /**\n     * @param int|null $authTime `auth_time` received from Azure. The value can be `null` when the claim is not added in\n     *                           the Azure AD admin console since it's an optional claim.\n     * @param int $ssoStateCreatedAt SSO state created timestamp.\n     * @return void\n     */\n    private function assertAuthTime(?int $authTime, int $ssoStateCreatedAt): void\n    {\n        $ssoSettingsData = $this->getSettings()->getData()->toArray();\n\n        if ($ssoSettingsData['prompt'] === SsoSettingsAzureDataForm::PROMPT_NONE) {\n            return;\n        }\n\n        if ($authTime !== null && $authTime < $ssoStateCreatedAt) {\n            $msg = __('Single sign-on failed.') . ' ' . __('You must authenticate with Azure again.');\n            throw new BadRequestException($msg);\n        }\n    }\n}\n","sourceCodeStart":141,"sourceCodeEnd":163,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Service/Sso/Azure/SsoAzureService.php#L141-L163","documentation":"For Azure, when the settings' prompt option is not 'none', Passbolt checks the auth_time claim from the id_token: if the user's last Azure authentication happened before the SSO state was created, the sign-in did not occur during this flow, so a BadRequestException asking to re-authenticate with Azure is thrown.","triggerScenarios":"assertAuthTime (called from assertResourceOwnerAgainstSsoState) finds authTime < ssoStateCreatedAt while prompt is set to login/consent/select_account — i.e. Azure returns an id_token from a cached session instead of a fresh authentication.","commonSituations":"Azure reuses an existing browser SSO session so no fresh authentication happens despite prompt != none; clock skew between passbolt server and Azure; state created long before the callback (user delayed completing the flow).","solutions":["Complete the Azure sign-in when prompted instead of relying on an existing session, then retry.","Set the prompt option to 'none' in Azure SSO settings if fresh authentication is not required for your policy.","Retry the flow immediately after starting it so auth_time is after state creation.","Check server clock synchronization (NTP) to rule out skew between passbolt and Azure timestamps.","Ensure the Azure app issues id_tokens including an accurate auth_time claim."],"exampleFix":"// before\nprompt: 'login' but user silently reuses cached Azure session (auth_time older than state)\n// after\nprompt: 'none'  // or force fresh sign-in at Azure so auth_time > state creation","handlingStrategy":"retry","validationCode":"$claims = json_decode(base64_decode(explode('.', $idToken)[1]), true);\nif (isset($claims['auth_time']) && $claims['auth_time'] < $ssoState->created->timestamp\n    && $prompt !== 'none') {\n    // force a fresh Azure authentication before continuing\n}","typeGuard":null,"tryCatchPattern":"try {\n    $uac = $service->assertStateCodeAndGetUac(...);\n} catch (BadRequestException $e) {\n    if (str_contains($e->getMessage(), 'authenticate with Azure again')) {\n        // restart flow; user must sign in freshly at Azure\n    }\n    throw $e;\n}","preventionTips":["Keep server clocks NTP-synchronized to avoid false auth_time comparisons.","Set prompt to 'none' if fresh authentication is not required by your policy.","Start and complete the SSO flow promptly; don't leave the state open for long.","Sign out of stale Azure sessions if a cached session is silently reused."],"tags":["sso","azure","oidc","auth-time"],"backgroundTag":"jwt-token-expired","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}