Skip to content

Commit 57e8120

Browse files
committed
fix: Document and validate multiple client IDs for Apple and Google auth
Closes #8493
1 parent 7e9d53a commit 57e8120

3 files changed

Lines changed: 49 additions & 4 deletions

File tree

spec/AuthenticationAdapters.spec.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,27 @@ describe('google auth adapter', () => {
575575
expect(result).toEqual(fakeClaim);
576576
});
577577

578+
it('(using client id as array with multiple items) should verify id_token (google.com)', async () => {
579+
const fakeClaim = {
580+
iss: 'https://accounts.google.com',
581+
aud: 'secret',
582+
exp: Date.now(),
583+
sub: 'the_user_id',
584+
};
585+
const fakeDecodedToken = { kid: '123', alg: 'RS256' };
586+
const fakeSigningKey = { kid: '123', rsaPublicKey: 'the_rsa_public_key' };
587+
spyOn(authUtils, 'getHeaderFromToken').and.callFake(() => fakeDecodedToken);
588+
spyOn(authUtils, 'getSigningKey').and.resolveTo(fakeSigningKey);
589+
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
590+
591+
await expectAsync(
592+
google.validateAuthData(
593+
{ id: 'the_user_id', id_token: 'the_token' },
594+
{ clientId: ['secret', 'other-client-id'] }
595+
)
596+
).toBeResolvedTo(fakeClaim);
597+
});
598+
578599
it('(using client id as string) should throw error with with invalid jwt issuer (google.com)', async () => {
579600
const fakeClaim = {
580601
iss: 'https://not.google.com',
@@ -655,6 +676,14 @@ describe('google auth adapter', () => {
655676
expect(e.message).toBe('Google auth is not configured.');
656677
}
657678
});
679+
680+
it('should throw error when clientId is an empty array', async () => {
681+
await expectAsync(
682+
google.validateAuthData({ id: 'the_user_id', id_token: 'the_token' }, { clientId: [] })
683+
).toBeRejectedWith(
684+
new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Google auth is not configured.')
685+
);
686+
});
658687
});
659688

660689
describe('keycloak auth adapter', () => {
@@ -1470,6 +1499,14 @@ describe('apple signin auth adapter', () => {
14701499
expect(e.message).toBe('Apple auth is not configured.');
14711500
}
14721501
});
1502+
1503+
it('should throw error when clientId is an empty array', async () => {
1504+
await expectAsync(
1505+
apple.validateAuthData({ id: 'the_user_id', token: 'the_token' }, { clientId: [] })
1506+
).toBeRejectedWith(
1507+
new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple auth is not configured.')
1508+
);
1509+
});
14731510
});
14741511

14751512
describe('phant auth adapter', () => {

src/Adapters/Auth/apple.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* @class AppleAdapter
55
* @param {Object} options - Configuration options for the adapter.
6-
* @param {string} options.clientId - Your Apple App ID.
6+
* @param {string|string[]} options.clientId - Your Apple App ID, or an array of App IDs to accept tokens for multiple bundle IDs (e.g. separate iPhone and iPad apps sharing one Parse app).
77
*
88
* @param {Object} authData - The authentication data provided by the client.
99
* @param {string} authData.id - The user ID obtained from Apple.
@@ -21,6 +21,10 @@
2121
* }
2222
* }
2323
* ```
24+
* `clientId` also accepts an array of App IDs to accept tokens issued for any of several bundle IDs:
25+
* ```json
26+
* { "auth": { "apple": { "clientId": ["12345", "67890"] } } }
27+
* ```
2428
*
2529
* ## Expected `authData` from the Client
2630
* The adapter expects the client to provide the following `authData` payload:
@@ -73,7 +77,7 @@ const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
7377
};
7478

7579
const verifyIdToken = async ({ token, id }, { clientId, cacheMaxEntries, cacheMaxAge }) => {
76-
if (!clientId) {
80+
if (!clientId || (Array.isArray(clientId) && !clientId.length)) {
7781
throw new Parse.Error(
7882
Parse.Error.OBJECT_NOT_FOUND,
7983
'Apple auth is not configured.'

src/Adapters/Auth/google.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* @class GoogleAdapter
55
* @param {Object} options - The adapter configuration options.
6-
* @param {string} options.clientId - Your Google application Client ID.
6+
* @param {string|string[]} options.clientId - Your Google application Client ID, or an array of Client IDs to accept tokens issued for any of them.
77
* @param {number} [options.cacheMaxEntries] - Maximum number of JWKS cache entries. Default: 5.
88
* @param {number} [options.cacheMaxAge] - Maximum age of JWKS cache entries in ms. Default: 3600000 (1 hour).
99
*
@@ -19,6 +19,10 @@
1919
* }
2020
* }
2121
* ```
22+
* `clientId` also accepts an array of Client IDs to accept tokens issued for any of them:
23+
* ```json
24+
* { "auth": { "google": { "clientId": ["id-1", "id-2"] } } }
25+
* ```
2226
*
2327
* The adapter requires the following `authData` fields:
2428
* - **id**: The Google user ID.
@@ -74,7 +78,7 @@ const getGoogleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
7478
};
7579

7680
async function verifyIdToken({ id_token: token, id }, { clientId, cacheMaxEntries, cacheMaxAge }) {
77-
if (!clientId) {
81+
if (!clientId || (Array.isArray(clientId) && !clientId.length)) {
7882
throw new Parse.Error(
7983
Parse.Error.OBJECT_NOT_FOUND,
8084
'Google auth is not configured.'

0 commit comments

Comments
 (0)