Skip to content

Instantly share code, notes, and snippets.

@gillkyle
Last active September 18, 2021 20:05
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save gillkyle/7b6078705479ec60d4bc5081c0dbe755 to your computer and use it in GitHub Desktop.
Save gillkyle/7b6078705479ec60d4bc5081c0dbe755 to your computer and use it in GitHub Desktop.

Add Authentication with Auth0 to a Gatsby site

All the pieces of authentication are possible in Gatsby. Thanks to Auth0, setting it up doesn't have to be so cumbersome!

This setup comes straight from Auth0's official quick start for React.


The first step is installing the official Auth0 SDK for JavaScript from npm.


Auth0 provides some boilerplate code in their quick start that will provide some nice functions to access the user and silently reauthenticate them on your site.

The npm package's code lets you destructure values from a React hook that gives you the ability to:

  • access tokens - using functions like getIdTokenClaims and getTokenSilently
  • access user data - using the user object
  • check the authenticated state - using isAuthenticated
  • see if Auth0 is still initializing - using the loading boolean

The next recipe step will ask you to add it.


This is the boilerplate code from Auth0:


To encapsulate everything in one place, this recipe will generate a new local plugin to let the rest of the Gatsby app access the boilerplate code that wraps the app in the Auth0 provider.

The package.json and index.js files are needed for Gatsby plugins:


The wrap-root-element file is particularly noteworthy as it defines a provider to wrap your Gatsby site with. A provider comes from React's context API and allows access to data or functions in any component or file in your app below it in the React tree. This gives the rest of your app access to authentication information and functionality.


Calling wrapRootElement with the function made in the previous step will make sure your app is wrapped by the provider.


Gatsby doesn't need to authenticate users at build time when static assets are generated, so the webpack config can be modified to load null functions while the site builds.


Now, with the code added, you can install the local plugin in your Gatsby config. The options for domain and clientId are passed in.

You will need to replace the values with your own Auth0 app's client id and domain. The example values will not work until you change them.

<GatsbyPlugin name="gatsby-plugin-auth0" options={{ domain: yourdomain.auth0.com, clientId: s0m3r4nd0mCh4raCT3rZ, }} />


On the Auth0 side, all you need to ensure is that you have created an app and within that app you have added http://localhost:8000 to Allowed Callback URLs, Allowed Logout URLs, and Allowed Web Origins.


To test that it's working, this recipe will add an example page at http://localhost:8000/example-login-page where you can test by clicking on the login button, which will redirect you to Auth0's hosted login page.

You will see a user logged in the console when you login, and can use the useAuth0 hook by importing it from the local plugin on any page or in any componoent.

import React, { useState, useEffect, useContext } from "react"
import createAuth0Client from "@auth0/auth0-spa-js"
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname)
const defaultContext = {
isAuthenticated: false,
user: null,
loading: false,
popupOpen: false,
loginWithPopup: () => {},
handleRedirectCallback: () => {},
getIdTokenClaims: () => {},
loginWithRedirect: () => {},
getTokenSilently: () => {},
getTokenWithPopup: () => {},
logout: () => {},
}
export const Auth0Context = React.createContext(defaultContext)
export const useAuth0 = () => useContext(Auth0Context)
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState()
const [user, setUser] = useState()
const [auth0Client, setAuth0] = useState()
const [loading, setLoading] = useState(true)
const [popupOpen, setPopupOpen] = useState(false)
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions)
setAuth0(auth0FromHook)
if (
window.location.search.includes("code=") &&
window.location.search.includes("state=")
) {
const { appState } = await auth0FromHook.handleRedirectCallback()
onRedirectCallback(appState)
}
const isAuthenticated = await auth0FromHook.isAuthenticated()
setIsAuthenticated(isAuthenticated)
if (isAuthenticated) {
const user = await auth0FromHook.getUser()
setUser(user)
}
setLoading(false)
}
initAuth0()
// eslint-disable-next-line
}, [])
const loginWithPopup = async (params = {}) => {
setPopupOpen(true)
try {
await auth0Client.loginWithPopup(params)
} catch (error) {
console.error(error)
} finally {
setPopupOpen(false)
}
const user = await auth0Client.getUser()
setUser(user)
setIsAuthenticated(true)
}
const handleRedirectCallback = async () => {
setLoading(true)
await auth0Client.handleRedirectCallback()
const user = await auth0Client.getUser()
setLoading(false)
setIsAuthenticated(true)
setUser(user)
}
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p),
}}
>
{children}
</Auth0Context.Provider>
)
}
import React from "react"
import { useAuth0 } from "../../plugins/gatsby-plugin-auth0"
const ExamplePage = () => {
const { isAuthenticated, loginWithRedirect, logout, user } = useAuth0()
console.log({ isAuthenticated, user })
return (
<div>
{isAuthenticated ? (
<>
<h2>Hi, {user?.email}</h2>
<button onClick={() => logout()}>Log out</button>
</>
) : (
<>
<h2>Hi, try logging in:</h2>
<button onClick={() => loginWithRedirect({})}>Log in</button>
</>
)}
</div>
)
}
export default ExamplePage
export { wrapRootElement } from "./wrap-root-element"
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
// auth0 isn't required during build time, so it can be replaced with a null loader so builds don't break
actions.setWebpackConfig({
module: {
rules: [
{
test: /auth0-spa-js/,
use: loaders.null(),
},
],
},
})
}
}
export { wrapRootElement } from "./wrap-root-element"
import { useAuth0 } from "./auth"
export { useAuth0 }
import React from "react"
import { Auth0Provider } from "./auth"
import { navigate } from "gatsby"
const onRedirectCallback = appState => {
navigate(appState)
}
export const wrapRootElement = ({ element }, pluginOptions) => {
return (
<Auth0Provider
domain={pluginOptions.domain}
client_id={pluginOptions.clientId}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
{element}
</Auth0Provider>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment