Skip to content

Commit 15e63f2

Browse files
jeromegamezMarijus Kilmanas
andcommitted
Enable getting a user by their federated IdP UID
Co-authored-by: Marijus Kilmanas <marijus.kilmanas@visiongroup.io>
1 parent a439dde commit 15e63f2

6 files changed

Lines changed: 170 additions & 1 deletion

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
12+
* You can now get a user by their federated identity provider (e.g. Google, Facebook, etc.) UID with
13+
`Kreait\Firebase\Auth::getUserByProviderUid()`. ([#1000](https://github.com/kreait/firebase-php/pull/1000))
14+
Since this method couldn't be added to the `Kreait\Firebase\Contract\Auth` interface without causing a breaking
15+
change, a new transitional interface/contract named `Kreait\Firebase\Contract\Transitional\FederatedUserFetcher`
16+
was added. This interface will be removed in the next major version of the SDK.
17+
There are several ways to check if you can use the `getUserByProviderUid()` method:
18+
```php
19+
use Kreait\Firebase\Contract\Transitional\FederatedUserFetcher;
20+
use Kreait\Firebase\Factory;
21+
22+
$auth = (new Factory())->createAuth();
23+
// The return type is Kreait\Firebase\Contract\Auth, which doesn't have the method
24+
25+
if (method_exists($auth, 'getUserByProviderUid')) {
26+
$user = $auth->getUserByProviderUid('google.com', 'google-uid');
27+
}
28+
29+
if ($auth instanceof \Kreait\Firebase\Auth) { // This is the implementation, not the interface
30+
$user = $auth->getUserByProviderUid('google.com', 'google-uid');
31+
}
32+
33+
if ($auth instanceof FederatedUserFetcher) {
34+
$user = $auth->getUserByProviderUid('google.com', 'google-uid');
35+
}
36+
```
37+
1038
## [7.19.0] - 2025-06-14
1139

1240
### Added

docs/user-management.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,53 @@ Get information about a specific user
144144
$user = $auth->getUser('some-uid');
145145
$user = $auth->getUserByEmail('user@example.com');
146146
$user = $auth->getUserByPhoneNumber('+49-123-456789');
147+
// For `getUserByProviderUid()` please see the section below.
148+
$user = $auth->getUserByProviderUid('google.com', 'google-uid');
147149
} catch (\Kreait\Firebase\Exception\Auth\UserNotFound $e) {
148150
echo $e->getMessage();
149151
}
150152
153+
***************************************************
154+
Get information about a user by federated provider
155+
***************************************************
156+
157+
You can retrieve a user by their federated identity provider UID (e.g. Google, Facebook, etc.):
158+
159+
.. code-block:: php
160+
161+
try {
162+
$googleUser = $auth->getUserByProviderUid('google.com', 'google-uid');
163+
$facebookUser = $auth->getUserByProviderUid('facebook.com', 'facebook-uid');
164+
} catch (\Kreait\Firebase\Exception\Auth\UserNotFound $e) {
165+
echo $e->getMessage();
166+
}
167+
168+
.. note::
169+
Since this method couldn't be added to the ``Kreait\Firebase\Contract\Auth`` interface without causing a breaking
170+
change, a new transitional interface/contract named ``Kreait\Firebase\Contract\Transitional\FederatedUserFetcher``
171+
was added. This interface will be removed in the next major version of the SDK.
172+
173+
There are several ways to check if you can use the ``getUserByProviderUid()`` method:
174+
175+
.. code-block:: php
176+
177+
use Kreait\Firebase\Contract\Transitional\FederatedUserFetcher;
178+
use Kreait\Firebase\Factory;
179+
180+
$auth = (new Factory())->createAuth();
181+
182+
if (method_exists($auth, 'getUserByProviderUid')) {
183+
$user = $auth->getUserByProviderUid('google.com', 'google-uid');
184+
}
185+
186+
if ($auth instanceof \Kreait\Firebase\Auth) { // This is the implementation, not the interface
187+
$user = $auth->getUserByProviderUid('google.com', 'google-uid');
188+
}
189+
190+
if ($auth instanceof FederatedUserFetcher) {
191+
$user = $auth->getUserByProviderUid('google.com', 'google-uid');
192+
}
193+
151194
************************************
152195
Get information about multiple users
153196
************************************

src/Firebase/Auth.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Kreait\Firebase\Auth\SignInWithRefreshToken;
2525
use Kreait\Firebase\Auth\UserQuery;
2626
use Kreait\Firebase\Auth\UserRecord;
27+
use Kreait\Firebase\Contract\Transitional\FederatedUserFetcher;
2728
use Kreait\Firebase\Exception\Auth\AuthError;
2829
use Kreait\Firebase\Exception\Auth\FailedToVerifySessionCookie;
2930
use Kreait\Firebase\Exception\Auth\FailedToVerifyToken;
@@ -59,7 +60,7 @@
5960
/**
6061
* @internal
6162
*/
62-
final class Auth implements Contract\Auth
63+
final class Auth implements Contract\Auth, FederatedUserFetcher
6364
{
6465
private readonly Parser $jwtParser;
6566

@@ -210,6 +211,22 @@ public function getUserByPhoneNumber(Stringable|string $phoneNumber): UserRecord
210211
return UserRecord::fromResponseData($data['users'][0]);
211212
}
212213

214+
public function getUserByProviderUid(Stringable|string $providerId, Stringable|string $providerUid): UserRecord
215+
{
216+
$providerId = (string) $providerId;
217+
$providerUid = (string) $providerUid;
218+
219+
$response = $this->client->getUserByProviderUid($providerId, $providerUid);
220+
221+
$data = Json::decode((string) $response->getBody(), true);
222+
223+
if (empty($data['users'][0])) {
224+
throw new UserNotFound("No user with federated account ID '{$providerId}:{$providerUid}' found.");
225+
}
226+
227+
return UserRecord::fromResponseData($data['users'][0]);
228+
}
229+
213230
public function createAnonymousUser(): UserRecord
214231
{
215232
return $this->createUser(CreateUser::new());

src/Firebase/Auth/ApiClient.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ public function getUserByPhoneNumber(string $phoneNumber): ResponseInterface
118118
return $this->requestApi($url, ['phoneNumber' => [$phoneNumber]]);
119119
}
120120

121+
/**
122+
* @throws AuthException
123+
*/
124+
public function getUserByProviderUid(string $providerId, string $uid): ResponseInterface
125+
{
126+
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:lookup');
127+
128+
return $this->requestApi($url, ['federatedUserId' => [['providerId' => $providerId, 'rawId' => $uid]]]);
129+
}
130+
121131
/**
122132
* @throws AuthException
123133
*/
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kreait\Firebase\Contract\Transitional;
6+
7+
use Kreait\Firebase\Auth\UserRecord;
8+
use Kreait\Firebase\Exception;
9+
use Kreait\Firebase\Exception\Auth\UserNotFound;
10+
use Stringable;
11+
12+
/**
13+
* @TODO: This interface is intended to be integrated into the Auth interface on the next major release.
14+
*/
15+
interface FederatedUserFetcher
16+
{
17+
/**
18+
* @param Stringable|non-empty-string $providerId
19+
* @param Stringable|non-empty-string $providerUid
20+
*
21+
* @throws UserNotFound
22+
* @throws Exception\AuthException
23+
* @throws Exception\FirebaseException
24+
*/
25+
public function getUserByProviderUid(Stringable|string $providerId, Stringable|string $providerUid): UserRecord;
26+
}

tests/Integration/AuthTestCase.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Kreait\Firebase\Auth\SignIn\FailedToSignIn;
1515
use Kreait\Firebase\Auth\UserRecord;
1616
use Kreait\Firebase\Contract\Auth;
17+
use Kreait\Firebase\Contract\Transitional\FederatedUserFetcher;
1718
use Kreait\Firebase\Exception\Auth\InvalidOobCode;
1819
use Kreait\Firebase\Exception\Auth\RevokedIdToken;
1920
use Kreait\Firebase\Exception\Auth\RevokedSessionCookie;
@@ -520,6 +521,50 @@ public function getUserByNonExistingPhoneNumber(): void
520521
$this->auth->getUserByPhoneNumber($phoneNumber);
521522
}
522523

524+
#[Test]
525+
public function getUserByProviderUid(): void
526+
{
527+
if (Util::authEmulatorHost() === null) {
528+
$this->markTestSkipped('Getting user by provider UID can only be tested with the Firebase emulator.');
529+
}
530+
531+
$auth = $this->auth;
532+
if (!($auth instanceof FederatedUserFetcher)) {
533+
$this->markTestSkipped('This test requires a FederatedUserFetcher implementation.');
534+
}
535+
536+
$phoneNumber = '+1234567'.random_int(1000, 9999);
537+
538+
$user = $this->auth->createUser([
539+
'phoneNumber' => $phoneNumber,
540+
]);
541+
542+
$check = $auth->getUserByProviderUid('phone', "$phoneNumber");
543+
544+
try {
545+
$this->assertSame($user->uid, $check->uid);
546+
} finally {
547+
$auth->deleteUser($user->uid);
548+
}
549+
550+
}
551+
552+
#[Test]
553+
public function getUserByNonExistingProviderUid(): void
554+
{
555+
if (Util::authEmulatorHost() === null) {
556+
$this->markTestSkipped('Getting user by provider UID can only be tested with the Firebase emulator.');
557+
}
558+
559+
$auth = $this->auth;
560+
if (!($auth instanceof FederatedUserFetcher)) {
561+
$this->markTestSkipped('This test requires a FederatedUserFetcher implementation.');
562+
}
563+
564+
$this->expectException(UserNotFound::class);
565+
$auth->getUserByProviderUid('phone', '+192837465');
566+
}
567+
523568
#[Test]
524569
public function createUser(): void
525570
{

0 commit comments

Comments
 (0)