DEV Community

Cover image for Serve Static Internal Documentation Behind OAuth Authentication
Daniel Starner
Daniel Starner

Posted on • Updated on

Serve Static Internal Documentation Behind OAuth Authentication

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, and why its important to present internal documentation in an easy-to-read, easy-to-search manner.

In that post, I talked about how my team ended up writing our documentation using Docusaurus, 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 features.

We hosted this documentation site internally, with no authentication required since viewing it needed a user to be on the company VPN 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 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 3rd party provider before viewing our documentation.

Tools & Concepts

  • Our documentation will be generated using the Docusaurus static-site generator
  • The site will be protected using GitHub sign-in by fronting it with Oauth2 Proxy
  • The site will be hosted on Heroku for ease, but you can change this for your specific project.

What is Oauth?

Oauth (Open Authorization) 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 3rd party providers, so you can easily log in without needing credentials.

Oauth Example Login

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 if you want to just get something deployed.

GitHub logo dstarner / oauth-gated-docusaurus

Running Docusaurus gated behind an OAuth provider session

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 to configure these if needed.

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

npx create-docusaurus@latest oauth-gated-docusaurus classic
Enter fullscreen mode Exit fullscreen mode

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.

rm -rf blog src/pages src/components/*
Enter fullscreen mode Exit fullscreen mode

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

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'),
        },
      }),
    ],
  ],
Enter fullscreen mode Exit fullscreen mode

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.

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

# Welcome to Our Documentation

Hello world!

EOT
Enter fullscreen mode Exit fullscreen mode

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

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.

Deploy Basic Version to Heroku

Before adding in the authentication and authorization components, we will get the basic site up on Heroku, so we have a URL to redirect back to when configuring our OAuth application later.

  1. Create the Heroku app. Create a Heroku account and install the Heroku CLI as needed.

    heroku create
    
  2. Add the nodejs buildpack which will run npm build automatically before deployment

    heroku buildpacks:add heroku/nodejs
    
  3. Add a Procfile to run Docusaurus in a web process

    cat <<EOT >> Procfile
    web: npm run serve -- -p \$PORT
    EOT
    
  4. Commit the changes and push them to GitHub & Heroku

    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.

heroku open
Enter fullscreen mode Exit fullscreen mode

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 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

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.

heroku config:set OAUTH2_PROXY_CLIENT_ID=<value from GitHub>
Enter fullscreen mode Exit fullscreen mode

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.

heroku config:set OAUTH2_PROXY_CLIENT_SECRET=<value from GitHub>
Enter fullscreen mode Exit fullscreen mode

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 to your application.

heroku buildpacks:add cfra/oauth2-proxy
Enter fullscreen mode Exit fullscreen mode

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:

heroku config:set OAUTH2_PROXY_PROVIDER=github
Enter fullscreen mode Exit fullscreen mode

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.

heroku config:set OAUTH2_PROXY_COOKIE_SECRET=$(python -c \
    'from secrets import token_urlsafe; print(token_urlsafe(32)[:32])' \
)
Enter fullscreen mode Exit fullscreen mode

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.

web: /app/bin/start_with_oauth2_proxy.sh npm run serve -- -p 8080
Enter fullscreen mode Exit fullscreen mode

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

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 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, 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.

heroku config:set OAUTH2_PROXY_ARGS="-github-org='my-cool-org'"
Enter fullscreen mode Exit fullscreen mode

And update the Procfile to read the OAUTH2_PROXY_ARGS configuration variable.

web: /app/bin/start_with_oauth2_proxy.sh $OAUTH2_PROXY_ARGS npm run serve -- -p 8080
Enter fullscreen mode Exit fullscreen mode

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.

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.

Top comments (0)