Serve Static Internal Documentation Behind OAuth Authentication
Tags: heroku, tutorial, docusaurus, oauth
Published: 2022-07-02 04:31:33

## Background

Engineering teams frequently have centralized documentation websites that tie together all the different teams, projects, and workflows supported internally by the company. [I have spoken about the importance of quality documentation before](https://dev.to/dan_starner/keeping-documentation-debt-at-bay-clients-happy-53o), and why its important to present internal documentation in an easy-to-read, easy-to-search manner.

{% embed https://dev.to/dan_starner/keeping-documentation-debt-at-bay-clients-happy-53o %}

In that post, I talked about how my team ended up writing our documentation using [Docusaurus](https://docusaurus.io/), a React- & Markdown-driven static site generator. It suited our needs well, looked great, and was _very_ easy to write customized documentation and components using the [MDX](https://mdxjs.com/) features.

We hosted this documentation site internally, with no authentication required since viewing it needed a user to be on [the company VPN](https://www.paloaltonetworks.com/cyberpedia/what-is-a-business-vpn-understand-its-uses-and-limitations) already.

I wanted to bring this same documentation style over to other companies, but they did not enforce the same VPN restrictions as Bloomberg. With this constraint, we needed a way to force authentication to our Docusaurus site before showing content to users. Authentication & private content was [already discussed](https://github.com/facebook/docusaurus/issues/2769) a few times on the project repository, but was ultimately rejected since that is outside the scope of a static-site generator.

So what can we do?

## Solution

Let's walk through how to deploy Docusaurus behind an OAuth proxy which will force users to log in with a 3<sup>rd</sup> party provider before viewing our documentation.

### Tools & Concepts

- Our documentation will be generated using the [Docusaurus](https://docusaurus.io/) static-site generator
- The site will be protected using GitHub sign-in by fronting it with [Oauth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/)
- The site will be hosted on [Heroku](https://www.heroku.com) for ease, but you can change this for your specific project.

### What is Oauth?

[Oauth](https://oauth.net/) (**O**pen **Auth**orization) is an authorization protocol that allows a user to authenticate and access one service by allowing another service to provide your basic account details. OAuth allows for password-less logins - which are inherently safer - and requires users to maintain fewer accounts & credentials across services.

An example of Oauth is when a website allows you to log in with your Google, Twitter, or GitHub credentials. The site has preconfigured routes, configurations, and protocols to interact with those 3<sup>rd</sup> party providers, so you can easily log in without needing credentials.

![Oauth Example Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/epr2zxlgb286iq75t8n8.png)

Our example will allow users to log in with their GitHub account to view the documentation.

## The Process

> This was performed on Docusaurus `2.0.0-beta.21`

With some background on what we are doing, let's dive into setting up the project. This will walk through the individual steps, but you can view [the complete starter project](https://github.com/dstarner/oauth-gated-docusaurus) if you want to just get something deployed.

{% embed https://github.com/dstarner/oauth-gated-docusaurus no-readme %}

### Creating the Docusaurus Project

To start, we will create a basic Docusaurus project. This guide assumes you already have Node 16.x and `npm` installed on your machine, but you can use [the `nvm` tool](https://github.com/nvm-sh/nvm) to configure these if needed.

Create a new Docusaurus site with the `classic` (default) template. 

```bash
npx create-docusaurus@latest oauth-gated-docusaurus classic
```

**Assume that the rest of the steps are run inside this newly created `oauth-gated-docusaurus` directory.**

### Docs-Only Mode

Since this will be internal documentation only, I usually delete the `blog` & homepage and clean up the `docusaurus.config.js` to match.

```bash
rm -rf blog src/pages src/components/*
```

Next, replace the `presets` key in the `docusaurus.config.js` file with the following:

```js
presets: [
    [
      'classic',
      /** @type {import('@docusaurus/preset-classic').Options} */
      ({
        docs: {
          sidebarPath: require.resolve('./sidebars.js'),
          routeBasePath: '/', // Serve the docs at the site's root
          // Please change this to your repo.
          // Remove this to remove the "edit this page" links.
          editUrl:
            'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
        },
        blog: false,
        theme: {
          customCss: require.resolve('./src/css/custom.css'),
        },
      }),
    ],
  ],
```

Make sure to update the GitHub URL(s) to point to your repository instead.

Finally, create a landing page for your blog site. The `slug: /` denotes this file as the root URL. It is the only file that should have the `slug` key defined.

```bash
cat <<EOT >> docs/index.md
---
sidebar_position: 1
sidebar_label: Homepage
slug: /
---

# Welcome to Our Documentation

Hello world!

EOT
```

At this point, you should be able to `npm start` the server and open up the URL Docusaurus shows in the immediate output.


![Docusaurus Running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q8nnpdliw8mzdwr6mk1j.png)

If you try to run `npm run build`, you may encounter a few build errors due to invalid URLs. This is left to an exercise for the user, but the example repository already has the fixes. It comes down to removing the `to: blog` and replacing `/docs/intro` with `/intro` to get the project to build nicely.

If you do not want to fix these issues, you can also set `onBrokenLinks: 'warn'` if you like. See the [`onBrokenLinks` documentation here](https://docusaurus.io/docs/api/docusaurus-config#onBrokenLinks).

### Deploy Basic Version to Heroku
Before adding in the authentication and authorization components, we will get the basic site up on [Heroku](https://heroku.com), so we have a URL to redirect back to when configuring our OAuth application later.

1. Create the Heroku app. Create a [Heroku account](https://heroku.com) and [install the Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) as needed.
    ```bash
    heroku create
    ```
2. Add the `nodejs` buildpack which will run `npm build` automatically before deployment
    ```bash
    heroku buildpacks:add heroku/nodejs
    ```
3. Add a `Procfile` to run Docusaurus in a web process
    ```bash
    cat <<EOT >> Procfile
    web: npm run serve -- -p \$PORT
    EOT
    ```
4. Commit the changes and push them to GitHub & Heroku
    ```bash
    git add . && git commit -m "prepare for heroku deployment"
    git push heroku main && git push origin main
    ```

With the following command, you should be able to view your documentation in the browser, but there's one issue...it's still public! So let's fix that with some OAuth and a proxy.

```bash
heroku open
```

### Creating the GitHub OAuth Credentials

With Docusaurus configured and running locally, we can begin creating an OAuth application. We will be using GitHub as an application provider, meaning users will log in via it, redirecting them back to our documentation. I chose GitHub because I assume _most_ people reading this article have a GitHub account, and any GitHub user can create OAuth applications without paying.

1. Visit [the page to register a new app](https://github.com/settings/applications/new) and provide the required details.
	1. **Application Name**: `Docusaurus OAuth Example`
	2. **Homepage URL**: Whatever `heroku open` opened in your browser
	3. **Authorization callback URL**: `<Homepage URL >/oauth2/callback`


![Register the GitHub OAuth App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gf0lc3qu93g9xab3ytm3.png)

Once the OAuth application is registered, note the **Client ID**, which should be a random-looking string of 16-24 characters. Save this to Heroku to be used later with the following command.

```bash
heroku config:set OAUTH2_PROXY_CLIENT_ID=<value from GitHub>
```

#### Generate a Client Secret
Finally, you will need to generate a client secret for the OAuth application. Under the **Client secrets** section, click the **Generate a new client secret** button, which will reload the page with a newly minted client secret key. **Make sure to copy it manually or by clicking the Copy icon because you cannot retrieve this key again!**

Save this to the Heroku app to be used later.

```bash
heroku config:set OAUTH2_PROXY_CLIENT_SECRET=<value from GitHub>
```

### Adding OAuth Proxy to Docusaurus
Finally, all of the pieces are in place to run our documentation site behind an OAuth2 Proxy.

> A Proxy is a middleman between users trying to access the website and the web process hosting the content. Its job is to either show the website content to authorized users or redirect those not authorized to GitHub.

Begin by adding [the `heroku-buildpack-oauth2-proxy` community buildpack](https://elements.heroku.com/buildpacks/cfra/heroku-buildpack-oauth2-proxy) to your application.

```bash
heroku buildpacks:add cfra/oauth2-proxy
```

We added the GitHub OAuth client and secret keys in the last step, but we need to tell our proxy to use GitHub for authentication. We can do this with the following:

```bash
heroku config:set OAUTH2_PROXY_PROVIDER=github
```

The last step is to generate a secret key to sign all authentication cookies. This string can be random, so we are using Python for this step. You may need to switch `python` for `python3` in the command if you get a Python error.

```bash
heroku config:set OAUTH2_PROXY_COOKIE_SECRET=$(python -c \
    'from secrets import token_urlsafe; print(token_urlsafe(32)[:32])' \
)
```

 To see the change, you will need to place the oauth2-proxy script _in front_ of our `npm serve ...` command from earlier. Then, update the Procfile to the following. 

```bash
web: /app/bin/start_with_oauth2_proxy.sh npm run serve -- -p 8080
```

Notice how the server is now running on `8080` instead of looking at the `$PORT` variable. This change is because `oauth2_proxy` runs listening on `$PORT,` and it expects our gated webserver to listen on `8080`.

### Testing Login
View your documentation in the browser again, and you should be greeted with the OAuth2 Proxy landing page. Try to sign in with GitHub now!


![OAuth is required to view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3fl08ttzqwb7osqdx8ns.png)

If all worked, you should be able to view your documentation again! We have successfully gated our documentation behind an OAuth Provider.

Suppose you would like to change the OAuth provider being used. In that case, you will need to update the `OAUTH2_PROXY_PROVIDER`, `OAUTH2_PROXY_CLIENT_ID`, and the `OAUTH2_PROXY_CLIENT_SECRET` variables on the app _after_ reading [the `oauth2-proxy` documentation](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider) for the associated provider type.

## Bonus: Restricting to a GitHub Organization
Cool, our documentation now has OAuth in front of it, meaning people have to log into their GitHub accounts before viewing it. That's great, but we said this was for internal company documentation; we don't want just _any_ GitHub user seeing our documentation.

Reading through [the `oauth2-proxy` GitHub Provider documentation](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider#github-auth-provider), we can see that the provider can implement more strict access controls at the organization, team, repository, token, or user level. Let's restrict this to a single team by adding a configuration argument.

```bash
heroku config:set OAUTH2_PROXY_ARGS="-github-org='my-cool-org'"
```

And update the `Procfile` to read the `OAUTH2_PROXY_ARGS` configuration variable.

```bash
web: /app/bin/start_with_oauth2_proxy.sh $OAUTH2_PROXY_ARGS npm run serve -- -p 8080
```

This means that changing who can access the documentation at different abstraction levels boils down to just updating the `OAUTH2_PROXY_ARGS` configuration variable, which can be done via the `heroku` CLI or dashboard under the app settings. Tinkering with this config var is left as a final exercise to the user.

---

## Conclusion

With this setup, engineering (and non-engineering teams too!) can write easy-to-manage, easier-to-view documentation sites powered by Docusaurus while ensuring that only appropriate individuals can view them. This detail is usually critical for internal company documentation. Much more configuration and OAuth settings can be included by reading through [the `oauth2-proxy` documentation](https://oauth2-proxy.github.io/oauth2-proxy/docs/).

Thank you for reading this post! I hope it helps you and your teams as much as figuring out how to make this work helped mine.