TL;DR: MDX is used to load, parse and render JSX in Markdown documents. In this tutorial, you'll learn how to use Markdown with React to build a project documentation app. Check out this GitHub repository if you'd like to dive straight into the code.

What is MDX?

MDX combines the readability of Markdown files with the expressiveness of JSX. It is a format that lets you seamlessly use JSX in your Markdown documents. MDX is fast, has no runtime compilation, provides customizable layouts, easily pluggable to existing projects and can import components, like interactive charts, and export metadata.

MDX (https://t.co/dcLf4AdE6o) for those who haven't seen it is a markdown parser for ambitious projects. It's extremely useful for using design system components to render markdown and weaving interactive components in with existing markdown.

What We'll Build

There are several frameworks and projects for setting up documentation websites for open or closed-source software. Instead of setting up any of these projects for our documentation, we'll use MDX to build a documentation app for a fictional JavaScript framework called JollofJS. Once the app is feature-complete, we'll secure it with Auth0.

Getting Started

First, create a React app. To make this easy, use the create-react-app open-source software by Facebook.

# Install it globally
npm install -g create-react-app

Once you are done installing create-react-app on your machine, create a React app like so:

create-react-app jollofjs-doc

Another option is to create a new React app via npx:

npx create-react-app jollofjs-doc

Note: Make sure you have node version >= v8.5 installed on your system and npx comes with npm 5.2+ and higher.

Go ahead to change and the new working directory, jollofjs-doc:

cd jollofjs-doc

Now, install react-app-rewired:

npm install react-app-rewired --save-dev

react-app-rewired allows you tweak the create-react-app webpack config(s) without using eject and without creating a fork of the react-scripts.

Create a config-overrides.js file in the root directory and add the code below to it:

addComponent: The addComponent function dynamically imports .mdx files from the markdown directory and stores the content in the app's state.

capitalize: The capitalize function simply capitalizes the name of the route to match the name of the file in the markdown directory.

componentDidMount: This is a lifecycle method that's invoked once the Page component loads. This method calls the addComponent method when it is invoked.

getDerivedStateFromProps: This is a static method which is invoked after a component is instantiated as well as when it receives new props. It receives two params nextProps and prevState. We compared the prop stored in the state with the next prop. If it is different, it returns the new prop as a new state else it returns null.

componentDidUpdate: This is a new lifecycle method that's fired when state changes and we called the addComponent method to load the content of the new markdown file.

This is the backbone of our application. This component is able to load the content of the mdx files because of the incredible @mdx-js/loader and @mdx-js/mdx tools.

Note: Replace the <YOUR_AUTH0_CLIENT_ID> and <YOUR_AUTH0_DOMAIN> with the values from your Auth0 application settings.

YOUR_AUTH0_CLIENT_ID: Client ID from the Auth0 application setting we created earlier.

YOUR_AUTH0_DOMAIN: Domain from the Auth0 application setting we created earlier.

Let's analyze what's going on in the authentication code above:

constructor: An instance of auth0.WebAuth is created and initialized with your Auth0 values and define some other important configurations. For example, you are defining that Auth0 will redirect users (redirectUri) to the http://localhost:3000/callback URL (the same one you inserted in the Allowed Callback URLs field previously).

getIdToken: This method returns the idToken generated by Auth0 for the current user.

handleAuthentication: This is the method that your app will call right after the user is redirected from Auth0. This method simply reads the hash segment of the URL to fetch the user details and the id token.

isAuthenticated: This method returns whether there is an authenticated user or not.

redirectUri: The URL where Auth0 will call back to with the result of a successful or failed authentication. It must be whitelisted in the Allowed Callback URLs in your Auth0 Application's settings.

audience: The default audience, used if requesting access to an API.

responseType: Response type for all authentication requests. It can be any space separated list of the values code, token, id_token. If you don't provide a global responseType, you will have to provide a responseType for each method that you use.

scope: The default scope used for all authorization requests.

Let's update the Header component to hide/show the login and logout buttons based on the user's authentication status.

Add A Callback Component

We will create a new component in the src directory and call it Callback.js. This component will be activated when the localhost:3000/callback route is called and it will process the redirect from Auth0 and ensure we received the right data back after a successful authentication.

Once a user is authenticated, Auth0 will redirect back to our application and call the /callback route. Auth0 will also append the id_token to this request, and our Callback component will make sure to properly process and store the token in the in-app memory. Then, the app will be redirected back to the / page and will be in a logged-in state.

Keeping Users Signed In

Right now, once you reload the application, the users automatically get logged out because the user's credentials are stored in the app's memory. Let's keep the users logged in!

Note: The routes are not guarded yet, so you can still navigate the app. We'll add a guard to secure the page route later on in this article.

We'll use the Silent Authentication provided by Auth0. Whenever your application is loaded, it will send a silent request to Auth0 to check if the current user (actually the browser) has a valid session. If they do, Auth0 will send back to you an idToken and an idTokenPayload, just like it does on the authentication callback.

Head over to the Applications section of your Auth0 dashboard. Click on the MDX app from the list provided, then head over to the Settings tab of the app and update the following:

Allowed Web Origins: Add localhost:3000 to the field. Without this value there, Auth0 would deny any AJAX request coming from your app.

Social Connections: Auth0 auto-configure all new accounts to use development keys registered at Google for the social login. We expect developers to replace these keys with theirs once they start using Auth0 in more capacity. Furthermore, every time an app tries to perform a silent authentication, and that app is still using the development keys, Auth0 returns that there is no session active (even though this is not true). Head over to the Social connections on your dashboard, and update the Client ID and Client Secret fields with the new keys gotten from Connect your app to Google documentation provided by Auth0..

Note: Quickly head over to the Applications section of your Auth0 dashboard, click on the MDX app from the list provided, then head over to the Settings tab of the app and update the Allowed Logout URLs section with http://localhost:3000.

Let's analyze the change above. The authFlag constant is a flag that we store in local storage to indicate whether a user is logged in or not. It is also an indicator for renewing tokens with the Auth0 server after a full-page refresh.

Once the user is authenticated and redirected back to the app from the Auth0 server, the authFlag is set to true and stored in local storage so that if the user returns to the app later, we can check whether to ask the authorization server for a fresh token. The silentAuth method checks if the user is indeed authorized via the Auth0 checkSession method if the authFlag is true. If the user is authorized, new authentication data is returned and the setSession method is called. If the user is not authorized, the authFlag is deleted from local storage and logged out.

If the requested route is /callback, the app does nothing. This is the correct behavior because, when users are requesting for the /callback route, they do so because they are getting redirected by Auth0 after the authentication process. In this case, you can leave the Callback component to handle the process.

If the requested route is anything else, the app wants to try a silentAuth. Then, if no error occurs, the app calls forceUpdate so the user can see its name and that they are signed in.

If there is an error on the silentAuth, the app checks if the error is login_required. If this is the case, the app does nothing because it means the user is not signed in (or that you are using development keys, which you shouldn't). If there is an error that is not login_required, the error is simply logged to the console. Actually, in this case, it would be better to notify someone about the error so they could check what is happening.

Try to authenticate your app. Then, refresh your browser and you'll discover that you won't be logged out on a page refresh.

One more thing. Let's make sure the page routes can't be accessed if the user is not authenticated!

Secure Page Route

Create a GuardedRoute.js file in the src/GuardedRoute directory. Now add code to the GuardedRoute.js file:

Once you try to access any /page route when you are not logged in, you'll be redirected to the login page.

Conclusion

There's a lot to learn about MDX. I just scratched the surface of what's possible in this tutorial. As a developer, MDX gives you enough flexibility and power to easily build awesome documentation software for your closed and open source projects while Auth0 takes care of securing the app.