Skip to content

Commit 3ea647d

Browse files
committed
feat(google-one-tap): Set signInEmail query parameter instead of token
The users `token` contains sensitive information, most of which we don't need to log the user in. The only piece of information we need is the email which we can extract from the token.
1 parent b8026a1 commit 3ea647d

File tree

2 files changed

+63
-15
lines changed

2 files changed

+63
-15
lines changed

dotcom-rendering/src/components/GoogleOneTap.importable.tsx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,31 @@ const getStage = (hostname: string): StageType => {
4444
return 'PROD';
4545
};
4646

47+
/**
48+
* Extract the readers email address from a JWT token.
49+
*
50+
* As we're not using the token for authentication we don't need to verify the signature,
51+
* if the user wants to spoof a token it doesn't matter as they can only sign in as themselves.
52+
*
53+
* @param token A JWT Token
54+
* @returns extracted email address
55+
*/
56+
export const extractEmailFromToken = (token: string): string | undefined => {
57+
const payload = token.split('.')[1];
58+
if (!payload) return;
59+
const decoded = atob(payload);
60+
const parsed = JSON.parse(decoded) as Record<string, unknown>;
61+
if (typeof parsed.email !== 'string') return;
62+
return parsed.email;
63+
};
64+
4765
export const getRedirectUrl = ({
4866
stage,
49-
token,
67+
signInEmail,
5068
currentLocation,
5169
}: {
5270
stage: StageType;
53-
token: string;
71+
signInEmail: string;
5472
currentLocation: string;
5573
}): string => {
5674
const profileDomain = {
@@ -59,7 +77,7 @@ export const getRedirectUrl = ({
5977
DEV: 'https://profile.thegulocal.com',
6078
}[stage];
6179
const queryParams = new URLSearchParams({
62-
token,
80+
signInEmail,
6381
returnUrl: currentLocation,
6482
});
6583

@@ -184,6 +202,17 @@ export const initializeFedCM = async ({
184202
credentials,
185203
});
186204

205+
const signInEmail = extractEmailFromToken(credentials.token);
206+
if (signInEmail) {
207+
log('identity', `FedCM ID token for ${signInEmail} received`);
208+
} else {
209+
log(
210+
'identity',
211+
'FedCM token received but email could not be extracted from token',
212+
);
213+
return;
214+
}
215+
187216
await submitComponentEvent(
188217
{
189218
action: 'SIGN_IN',
@@ -200,7 +229,7 @@ export const initializeFedCM = async ({
200229
window.location.replace(
201230
getRedirectUrl({
202231
stage,
203-
token: credentials.token,
232+
signInEmail,
204233
currentLocation: window.location.href,
205234
}),
206235
);

dotcom-rendering/src/components/GoogleOneTap.test.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { submitComponentEvent as submitComponentEventMock } from '../client/ophan/ophan';
2-
import { getRedirectUrl, initializeFedCM } from './GoogleOneTap.importable';
2+
import {
3+
extractEmailFromToken,
4+
getRedirectUrl,
5+
initializeFedCM,
6+
} from './GoogleOneTap.importable';
37

48
jest.mock('../client/ophan/ophan', () => ({
59
submitComponentEvent: jest.fn(),
@@ -36,38 +40,52 @@ describe('GoogleOneTap', () => {
3640
expect(
3741
getRedirectUrl({
3842
stage: 'PROD',
39-
token: 'test-token',
43+
signInEmail: '[email protected]',
4044
currentLocation: 'https://www.theguardian.com/uk',
4145
}),
4246
).toEqual(
43-
'https://profile.theguardian.com/signin/google?token=test-token&returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fuk',
47+
'https://profile.theguardian.com/signin/google?signInEmail=valid%40email.com&returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fuk',
4448
);
4549

4650
expect(
4751
getRedirectUrl({
4852
stage: 'CODE',
49-
token: 'test-token',
53+
signInEmail: '[email protected]',
5054
currentLocation: 'https://m.code.dev-theguardian.com/uk',
5155
}),
5256
).toEqual(
53-
'https://profile.code.dev-theguardian.com/signin/google?token=test-token&returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2Fuk',
57+
'https://profile.code.dev-theguardian.com/signin/google?signInEmail=valid%40email.com&returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2Fuk',
5458
);
5559

5660
expect(
5761
getRedirectUrl({
5862
stage: 'DEV',
59-
token: 'test-token',
63+
signInEmail: '[email protected]',
6064
currentLocation:
6165
'http://localhost/Front/https://m.code.dev-theguardian.com/uk',
6266
}),
6367
).toEqual(
64-
'https://profile.thegulocal.com/signin/google?token=test-token&returnUrl=http%3A%2F%2Flocalhost%2FFront%2Fhttps%3A%2F%2Fm.code.dev-theguardian.com%2Fuk',
68+
'https://profile.thegulocal.com/signin/google?signInEmail=valid%40email.com&returnUrl=http%3A%2F%2Flocalhost%2FFront%2Fhttps%3A%2F%2Fm.code.dev-theguardian.com%2Fuk',
6569
);
6670
});
6771

72+
it('should return email address from a valid JWT token', () => {
73+
expect(
74+
extractEmailFromToken(
75+
'NULL.eyJlbWFpbCI6InZhbGlkQGVtYWlsLmNvbSJ9.NULL',
76+
),
77+
).toEqual('[email protected]');
78+
});
79+
80+
it('should return undefined from a malformed JWT token', () => {
81+
expect(extractEmailFromToken('NULL')).toEqual(undefined);
82+
});
83+
6884
it('should initializeFedCM and redirect to Gateway with token on success', async () => {
6985
const navigatorGet = jest.fn(() =>
70-
Promise.resolve({ token: 'test-token' }),
86+
Promise.resolve({
87+
token: 'NULL.eyJlbWFpbCI6InZhbGlkQGVtYWlsLmNvbSJ9.NULL',
88+
}),
7189
);
7290
const locationReplace = jest.fn();
7391

@@ -83,7 +101,8 @@ describe('GoogleOneTap', () => {
83101
context: 'continue',
84102
providers: [
85103
{
86-
clientId: '774465807556.apps.googleusercontent.com',
104+
clientId:
105+
'774465807556-4d50ur6svcjj90l7fe6i0bnp4t4qhkga.apps.googleusercontent.com',
87106
configURL: 'https://accounts.google.com/gsi/fedcm.json',
88107
},
89108
],
@@ -92,7 +111,7 @@ describe('GoogleOneTap', () => {
92111
});
93112

94113
expect(locationReplace).toHaveBeenCalledWith(
95-
'https://profile.theguardian.com/signin/google?token=test-token&returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fuk',
114+
'https://profile.theguardian.com/signin/google?signInEmail=valid%40email.com&returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fuk',
96115
);
97116

98117
expect(submitComponentEventMock).toHaveBeenNthCalledWith(
@@ -273,7 +292,7 @@ describe('GoogleOneTap', () => {
273292
});
274293

275294
await initializeFedCM({ isSignedIn: true });
276-
295+
277296
expect(submitComponentEventMock).toHaveBeenCalledTimes(1);
278297
expect(submitComponentEventMock).toHaveBeenCalledWith(
279298
{

0 commit comments

Comments
 (0)