Skip to main content

Docusaurus with Google Authentication

· 5 min read

With a little bit of programming, your site made by Docusaurus can be modified to be accessible only to users signed in to Google.

The source material came from this article by Thomasdevshare. His article describes a similar authentication scheme for Docusaurus with Firebase as the identity provider whereas this article describes the same approach using Google API directly.

The main concepts described in this article will be largely the same as that written by Thomas. The main differences:

(The example in this document is written in TypeScript but it should be easily applicable to JavaScript.)

Pre-requisites

These are the things that you need to set up:

1. Google credential

This step can be done at https://console.cloud.google.com/apis/credentials

Follow the Google instructions to configure your project and create a client ID for Web application.

For local development, add http://localhost and http://localhost:<port_number> to the Authorized JavaScript origins for @react-oauth to work. When deploying to production, specify the actual URL of your site.

Note the Client ID field for the OAuth 2.0 Client - this needs to be placed into the .env file (see below).

Google OAuth 2.0 Client

2. Add the @react-oauth/google dependency

npm i @react-oauth/google

This dependency makes it easy to incorporate the Google SSO implicit-authorization flow.

3. Specify Google Client ID and allowed users

Create a file .env in the root of the directory (same location as docusaurus.config.js) and add the following contents, replacing the text in arrow brackets with actual values:

GOOGLE_CLIENTID=<google client ID>


ALLOWED_USERS=<email addresses separated by commas>

The Google credentials obtained in Step 1 should replace <google client ID>.

<email addresses separated by commas> should be replaced with actual email addresses e.g. email1@example.com, email2@example.com

(The space after the comma will be trimmed by the plugin so it doesn't matter if the commas are followed by spaces.)

4. Configure docusaurus.config.js to read the environment variables

Docusaurus is only able to read the environment variable during pre-processing. This means that the values in the .env file have to be "transferred" to the configuration file.

This is done by first adding the following line to docusaurus.config.js:

require('dotenv').config();

Then, somewhere in the file below, assign the process environment variables to properties under customFields:

const config = {
title: 'Me3 Technical Documentation',
customFields: {
allowedUsers: process.env.ALLOWED_USERS,
googleClientId: process.env.GOOGLE_CLIENTID,
},
// ...
};
tip

I had used the plugin docusaurus2-dotenv before but it didn't work with deployment to Cloudflare Pages.

Log-in Component

Next create the log-in component. This component displays the Google sign-in button for the user to authenticate himself/herself.

Create a file src/components/login-google/index.tsx

src/components/login-google/index.tsx
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import {
CredentialResponse,
GoogleLogin,
GoogleOAuthProvider,
} from '@react-oauth/google';
import React from 'react';

export function LoginGoogle(props: {
login: (string) => void,
denied?: boolean,
}) {
const {
siteConfig: { customFields },
} = useDocusaurusContext();

const clientId = customFields.googleClientId as string;

function handleError() {
console.error('Failed to sign in with Google.');
}

function handleSuccess(creds: CredentialResponse) {
const plaintext = decode(creds.credential);

if (!plaintext) {
return;
}
const payload = JSON.parse(plaintext);
if (isAllowed(payload.email)) {
props.login(payload.email);
} else {
props.login(null);
}
}

// Checks if the supplied email address is allowed to see the page.
function isAllowed(email: string): boolean {
let allowedUsers = [];
if (typeof customFields.allowedUsers === "string") {
allowedUsers = customFields.allowedUsers.split(",").map((e) => e.trim());
}
if (allowedUsers.includes(email)) {
return true;
}
return false;
}

return (
<GoogleOAuthProvider clientId={clientId}>
<div
style={{
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'center',
margin: '5rem auto',
textAlign: 'center',
}}
>
<h2>Please sign in to your Google account to get access.</h2>
{props.denied ? (
<em>Not authorised</em>
) : (
<GoogleLogin
onSuccess={handleSuccess}
onError={handleError}
></GoogleLogin>
)}
</div>
</GoogleOAuthProvider>
);
}

// Function to decode the JWT from Google to get the user's email address.
function decode(jwt: string): string {
const parts = jwt.split('.');
if (parts.length !== 3) {
return '';
}
return window.atob(parts[1]);
}

Swizzle the Root Component

Swizzling is a term describing the overridding of an existing component with a custom implementation of it.

The Root component is the component to swizzle as it covers all generated pages.

src/theme/Root.tsx
import React, { useState } from 'react';

import { LoginGoogle } from '@site/src/components/login-google';

export default function Root({ children }) {
// `email` is used to determine if user is allowed.
const [email, setEmail] = (useState < string) | (null > '');

if (!email) {
return <LoginGoogle login={setEmail} denied={email === null}></LoginGoogle>;
}

return <>{children}</>;
}

Conclusion

One thing to note: the authentication information is only persisted in memory. What this means is that the authentiction is temporary (the correct term is ephemeral). The implication is that as soon as the user refreshes the page, the authenticated information (i.e. email address) is lost and the user gets "logged out".