Serve Static Internal Documentation Behind OAuth Authentication
Daniel Starner
Posted on July 2, 2022
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.
Maintaining Quality Documentation
Daniel Starner ・ Sep 9 '21
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.
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.
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
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/*
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'),
},
}),
],
],
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
At this point, you should be able to npm start
the server and open up the URL Docusaurus shows in the immediate output.
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.
-
Create the Heroku app. Create a Heroku account and install the Heroku CLI as needed.
heroku create
-
Add the
nodejs
buildpack which will runnpm build
automatically before deployment
heroku buildpacks:add heroku/nodejs
-
Add a
Procfile
to run Docusaurus in a web process
cat <<EOT >> Procfile web: npm run serve -- -p \$PORT EOT
-
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
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.
- Visit the page to register a new app and provide the required details.
-
Application Name:
Docusaurus OAuth Example
-
Homepage URL: Whatever
heroku open
opened in your browser -
Authorization callback URL:
<Homepage URL >/oauth2/callback
-
Application Name:
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>
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>
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
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
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])' \
)
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
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!
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'"
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
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.
Posted on July 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.