Google OAuth2
Google OAuth2 client for Node.js.
import axios from 'axios';
import qs from 'qs';
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from '@/config';
import { createGoogleAxiosResponseInterceptor } from '@/modules/verificationTracker/utils/googleAxiosResponseInterceptor';
import { UserRoleEnum } from '@/enums';
type GoogleTokensResult = {
access_token: string;
expires_in: number;
refresh_token: string;
scope: string;
id_token: string;
};
type GoogleUserResult = {
id: string;
email: string;
verified_email: boolean;
name: string;
given_name: string;
family_name: string;
picture: string;
locale: string;
};
type GoogleRefreshAccessTokenResult = {
access_token: string;
expires_in: number;
scope: string;
token_type: string;
id_token: string;
};
const googleClient = axios.create();
class Google {
static getAuthToken = async (
code: string,
redirectUri: string,
): Promise<GoogleTokensResult> => {
const url = 'https://oauth2.googleapis.com/token';
const values = {
code,
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
};
const res = await axios.post<GoogleTokensResult>(
url,
qs.stringify(values),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
);
return res.data;
};
static getUser = async ({
idToken,
accessToken,
}: {
idToken: string;
accessToken: string;
}): Promise<GoogleUserResult> => {
const url = `https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=${accessToken}`;
const res = await axios.get<GoogleUserResult>(url, {
headers: {
Authorization: `Bearer ${idToken}`,
},
});
return res.data;
};
static refreshAccessToken = async (
refreshToken: string,
): Promise<GoogleRefreshAccessTokenResult> => {
const url = 'https://oauth2.googleapis.com/token';
const values = {
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
refresh_token: refreshToken,
grant_type: 'refresh_token',
};
const res = await axios.post<GoogleRefreshAccessTokenResult>(
url,
qs.stringify(values),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
);
return res.data;
};
static revokeToken = async (
accessToken: string,
user: {id: string, role: UserRoleEnum},
): Promise<void> => {
const url = `https://oauth2.googleapis.com/revoke?token=${accessToken}`;
createGoogleAxiosResponseInterceptor(googleClient, user);
const res = await googleClient.post(url, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
return res.data;
};
}
export default Google;
When a request fails due to expired access token, you can use the intercept the response, refresh the token and retry the request.
import { AxiosInstance } from 'axios';
import { refreshGoogleAccountAccessToken } from '@/util';
import { UserRoleEnum } from '@/enums';
export const createGoogleAxiosResponseInterceptor = (
client: AxiosInstance,
user: { id: string; role: UserRoleEnum }
): void => {
const interceptor = client.interceptors.response.use(
(response) => response,
(error) => {
// Reject promise if usual error
if (!error.isAxiosError || !error.response) return Promise.reject(error);
if (error.response.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh fails with 401.
*/
client.interceptors.response.eject(interceptor);
return refreshGoogleAccountAccessToken(user)
.then(({ accessToken }) => {
error.response.config.headers.Authorization = `Bearer ${accessToken}`;
return client(error.response.config);
})
.catch((_error: Error) => Promise.reject(_error))
.finally(() => createGoogleAxiosResponseInterceptor(client, user));
}
);
};