Skip to content

Commit 7dd11e6

Browse files
Enable configuring multi factor authentication for a user. (#993)
Co-authored-by: ThijsLacquet <[email protected]>
1 parent 753a6ce commit 7dd11e6

File tree

5 files changed

+125
-0
lines changed

5 files changed

+125
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Please read about the future of the Firebase Admin PHP SDK on the
77

88
## [Unreleased]
99

10+
### Added
11+
12+
* It is now possible to configure multi factor authentication for a user.
13+
1014
## [7.17.0] - 2025-02-22
1115

1216
### Added

docs/user-management.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ Property Type Description
269269
``deletePhotoUrl`` boolean Whether or not to delete the user's photo.
270270
``deleteDisplayName`` boolean Whether or not to delete the user's display name.
271271
``deletePhoneNumber`` boolean Whether or not to delete the user's phone number.
272+
``resetMultiFactor`` boolean Whether or not to reset all of the user's enrolled factors. Including phone and TOTP factors.
273+
``multiFactors`` array An array of multi-factor factors.
272274
``deleteProvider`` string|array One or more identity providers to delete.
273275
``customAttributes`` array A list of custom attributes which will be available in a User's ID token.
274276
====================== ============ ===========
@@ -396,6 +398,25 @@ This method always returns an instance of ``Kreait\Firebase\Auth\DeleteUsersResu
396398
Cloud Functions for Firebase. This is because batch deletes do not trigger a user deletion event on each user.
397399
Delete users one at a time if you want user deletion events to fire for each deleted user.
398400

401+
*********************************
402+
Set multi factor authentication
403+
*********************************
404+
405+
The Firebase Admin SDK allows setting multi-factor authentication for a user, consisting of phone factors. Setting the
406+
multi-factor authentication overwrites all existing factors. Setting the `mfaEnrollmentId` and `enrolledAt` properties is
407+
optional. For example:
408+
409+
.. code-block:: php
410+
411+
$uid = 'some-uid';
412+
413+
$updatedUser = $auth->updateUser($uid, ['multifactors' => [[
414+
'mfaEnrollmentId' => '85dc3f7b-7bef-45b9-b9e6-0a1c2c656fed',
415+
'phoneInfo' => '+31123456789',
416+
'displayName' => 'foo',
417+
'enrolledAt' => '2025-02-28T15:30:00Z',
418+
]]);
419+
399420
**************************************
400421
Duplicate/Unregistered email addresses
401422
**************************************

src/Firebase/Request/EditUserTrait.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ trait EditUserTrait
3838

3939
protected ?string $clearTextPassword = null;
4040

41+
/** @var array<string, mixed>|null */
42+
protected ?array $multiFactor = null;
43+
4144
/**
4245
* @param Stringable|mixed $uid
4346
*/
@@ -168,6 +171,7 @@ public function prepareJsonSerialize(): array
168171
'phoneNumber' => $this->phoneNumber,
169172
'photoUrl' => $this->photoUrl,
170173
'password' => $this->clearTextPassword,
174+
'mfa' => $this->multiFactor,
171175
], static fn($value): bool => $value !== null);
172176
}
173177

src/Firebase/Request/UpdateUser.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ public static function withProperties(array $properties): self
152152
$request,
153153
);
154154

155+
break;
156+
157+
case 'resetmultifactor':
158+
if ($value === true) {
159+
$request = $request->resetMultiFactor();
160+
}
161+
162+
break;
163+
164+
case 'multifactors':
165+
$request = $request->withMultiFactors($value);
166+
155167
break;
156168
}
157169
}
@@ -205,6 +217,32 @@ public function withRemovedEmail(): self
205217
return $request;
206218
}
207219

220+
/**
221+
* @param array<array-key, array{
222+
* 'mfaEnrollmentId'?: string,
223+
* 'displayName': string,
224+
* 'phoneInfo': string,
225+
* 'enrolledAt'?: string,
226+
* }> $enrollments
227+
*/
228+
public function withMultiFactors(array $enrollments): self
229+
{
230+
$request = clone $this;
231+
$request->multiFactor ??= [];
232+
$request->multiFactor['enrollments'] = $enrollments;
233+
234+
return $request;
235+
}
236+
237+
public function resetMultiFactor(): self
238+
{
239+
$request = clone $this;
240+
$request->multiFactor ??= [];
241+
$request->multiFactor['enrollments'] = [];
242+
243+
return $request;
244+
}
245+
208246
/**
209247
* @param array<string, mixed> $customAttributes
210248
*/

tests/Integration/Request/UpdateUserTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
namespace Kreait\Firebase\Tests\Integration\Request;
66

77
use DateTimeImmutable;
8+
use Kreait\Firebase\Auth\MfaInfo;
89
use Kreait\Firebase\Contract\Auth;
910
use Kreait\Firebase\Request\CreateUser;
1011
use Kreait\Firebase\Request\UpdateUser;
1112
use Kreait\Firebase\Tests\IntegrationTestCase;
13+
use Kreait\Firebase\Util\DT;
1214
use PHPUnit\Framework\Attributes\Test;
1315

1416
use function bin2hex;
@@ -203,4 +205,60 @@ public function timeOfLastPasswordUpdateIsIncluded(): void
203205
$this->auth->deleteUser($user->uid);
204206
}
205207
}
208+
209+
#[Test]
210+
public function setMultiFactor(): void
211+
{
212+
$user = $this->auth->createUser(
213+
CreateUser::new()->withVerifiedEmail(self::randomEmail(__FUNCTION__)),
214+
);
215+
216+
$factor = [
217+
'mfaEnrollmentId' => '85dc3f7b-7bef-45b9-b9e6-0a1c2c656fed',
218+
'phoneInfo' => '+31123456789',
219+
'displayName' => '',
220+
'enrolledAt' => '2025-02-28T15:30:00Z',
221+
];
222+
223+
$enrolledAt = DT::toUTCDateTimeImmutable($factor['enrolledAt']);
224+
225+
try {
226+
$check = $this->auth->updateUser($user->uid, ['multifactors' => [$factor]]);
227+
228+
$this->assertInstanceOf(MfaInfo::class, $check->mfaInfo);
229+
$this->assertSame($factor['mfaEnrollmentId'], $check->mfaInfo->mfaEnrollmentId);
230+
$this->assertSame($factor['phoneInfo'], $check->mfaInfo->phoneInfo);
231+
$this->assertSame($factor['displayName'], $check->mfaInfo->displayName);
232+
$this->assertEquals($enrolledAt, $check->mfaInfo->enrolledAt);
233+
} finally {
234+
$this->auth->deleteUser($user->uid);
235+
}
236+
}
237+
238+
#[Test]
239+
public function resetMultiFactor(): void
240+
{
241+
$user = $this->auth->createUser(
242+
CreateUser::new()->withVerifiedEmail(self::randomEmail(__FUNCTION__)),
243+
);
244+
245+
$factor = [
246+
'mfaEnrollmentId' => '85dc3f7b-7bef-45b9-b9e6-0a1c2c656fed',
247+
'phoneInfo' => '+31123456789',
248+
'displayName' => '',
249+
'enrolledAt' => '2025-02-28T15:30:00Z',
250+
];
251+
252+
try {
253+
$updatedUser = $this->auth->updateUser($user->uid, ['multifactors' => [$factor]]);
254+
255+
$this->assertInstanceOf(MfaInfo::class, $updatedUser->mfaInfo);
256+
257+
$check = $this->auth->updateUser($user->uid, ['resetmultifactor' => true]);
258+
259+
$this->assertNotInstanceOf(MfaInfo::class, $check->mfaInfo);
260+
} finally {
261+
$this->auth->deleteUser($user->uid);
262+
}
263+
}
206264
}

0 commit comments

Comments
 (0)