Skip to content

Fix/sso account linking security#532

Open
aivuk wants to merge 5 commits into
mainfrom
fix/sso-account-linking-security
Open

Fix/sso account linking security#532
aivuk wants to merge 5 commits into
mainfrom
fix/sso-account-linking-security

Conversation

@aivuk

@aivuk aivuk commented Jun 3, 2026

Copy link
Copy Markdown
Member

Context

Fix #495, #496, #497

Checklist

  • Tested manually
  • GitHub issue linked
  • Changelist updated
  • Backward and forward compatible with aula-frontend/releases
  • Independent of the other BE version (v1 <-> v2)
  • Must be deployed ASAP (HOTFIX)
  • Needs update of docs.aula.de (repo)

@aivuk aivuk requested a review from nikola-maric-aula June 3, 2026 10:59

class SsoController extends Controller
{
private const LINK_INTENT_TTL_MINUTES = 10;

$fresh = LegacyUser::find($authUser->id);

if ($fresh->sso_sub !== null && $fresh->sso_sub !== $intent['sso_sub']) {
}

DB::transaction(function () use ($fresh, $intent) {
$fresh->sso_sub = $intent['sso_sub'];

DB::transaction(function () use ($fresh, $intent) {
$fresh->sso_sub = $intent['sso_sub'];
$fresh->sso_provider = $intent['sso_provider'] ?? null;
DB::transaction(function () use ($fresh, $intent) {
$fresh->sso_sub = $intent['sso_sub'];
$fresh->sso_provider = $intent['sso_provider'] ?? null;
$fresh->sso_id_token = $intent['sso_id_token'] ?? null;
* Returns null when the token is missing, malformed, or the payload is not valid JSON.
*/
protected function buildIdpLogoutUrl(?string $idpIdToken, string $redirectUri): ?string
protected function decodeIdTokenPayload(?string $idToken): ?array
protected function decodeIdTokenPayload(?string $idToken): ?array
{
if (! $idpIdToken) {
if (! $idToken) {
/**
* OIDC: the email claim is only trustworthy when email_verified is strictly true.
*/
protected function isEmailVerified(?array $idTokenPayload): bool
* The intent carries everything the link endpoint needs to stamp the row
* once the user has proven legacy-account possession via password.
*/
protected function storeLinkIntent(LegacyUser $emailMatch, $socialiteUser, Tenant $tenant): string
return $token;
}

protected function linkIntentCacheKey(string $token): string
@@ -18,8 +18,20 @@
$jwt = new JWT($instance->jwt_key, $db, $crypt, $syslog);

if ($_SERVER['REQUEST_METHOD'] === 'GET') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

praise: very nice handling and thanks for the useful inline comments

'password' => self::PASSWORD,
], ['aula-instance-code' => self::INSTANCE_CODE]);

$response->assertOk()->assertJson(['success' => true]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: how is this test case different from the one above? (we're not checking anything different in the assertion and the tenant is the same like above, the only diff is the email+username of the user, but sso_sub is absent in both tests)

if ($tenant && $tenant->sso_required) {
return response()->json([
'success' => false,
'error_code' => 3,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: fmpov (but let's discuss this), adding error_code and error is redundant.. i think we should decide to use only one of them to make it less confusing..

my understanding is that by using error_code in the legacy login controller you want to be interoperable with FE that works with v1 API.. if that assumption is correct, i'd argue we should force the FE to use v2 API for login and then we don't have to use error_code.. in fact, even if we extend the v1 login flow with error_code: 3, we'd still need to update FE to know how to interpret the value 3 correctly, so we might as well make it work with error slugs..

pls let me know if i got it right there..


$response->assertOk();
$response->assertJson(['success' => false, 'error_code' => 3, 'error' => 'tenant_requires_sso']);
$this->assertArrayNotHasKey('JWT', $response->json());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion(nitpick): not sure if this would work, but i noticed this function:

Suggested change
$this->assertArrayNotHasKey('JWT', $response->json());
$response->assertJsonMissingPath('$.JWT');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Define account-linking policy for SSO <-> legacy users

3 participants