In my quest never to set up a server ever again, I discovered Auth0 – a serverless authentication service. You give them keys to the kingdom, and they keep you and your users secure.
You don't even have to store anything. 👌
Since Gatsby is my favorite way to build new React apps with server-side rendering and super-fast performance, I decided to combine the two. Easy in theory, but turns out tricky in practice.
Took me 3 livecoding sessions to figure out. Luke Oliff's article on Gatsby v1 + Auth0 article helped a lot, as did Auth0's React SPA setup guide.
You can think of this article as a distillation of those guides and these 3 hours of coding 👇
Step 1: Get an Auth0 account
This one's easy. Go to Auth0 sign up for a free account. They even have a "Just playing around" option, and you're set.
Right now, a free account will hold you over up to 7,000 active users. I don't know about you, but none of my side projects ever hit 7000 DAU (daily active users).
See, it's just me on this one
Step 2: Create an app, get domain and client id
Click the big fat New Application button, fill out the form, and get your application's domain and client id. You'll need these to configure your Auth0 client.
The domain is where your login form will live. Your app is going to redirect people there. They'll see a login screen, do the things, and come back to your site with a redirect and a bunch of params.
Client ID tells the Auth0 machinery which site it's working with.
Strangely, you never have to set up a client secret. They generate one for you, but I haven't needed to use it yet. Maybe when you're doing server-to-server authentication?
Step 3: Whitelist your callback URL
Like I said, Auth0 redirects users back to your site after login. You give it a URL, and that's where they end up.
For your safety, callback URLs must be whitelisted. There's an Allowed Callback URLs
field in your app settings.
Add http://localhost:8000/auth0_callback
. I like to prefix callbacks with a service name because you never know how many you'll need.
Step 4: Start a new Gatsby project
This part is easy.
$ gatsby new gatsby-auth0-playground
Creates a new Gatsby site with the basic setup. You're welcome to use a starter. This guide should work with all of them.
Step 5: The auth service
All your Auth0 stuff lives in /utils/auth.js
. It's like a library on top of Auth0's library that makes your life a little easier.
You're best off copy-pasting this whole file verbatim into your project. Make sure to configure AUTH0_DOMAIN
and AUTH0_CLIENT_ID
. If I was a particularly good human, I'd put this on NPM and make it configurable through package.json, but we're not there yet :P
Today I learned that in programming when you're stuck, you should copy the instructions. No, not follow the instructions, copypasta their code word for word.
— Swizec Teller (@Swizec) January 21, 2019
Because copypasta will get you out of the weirdest shit imaginable. #200wordsTILhttps://t.co/V3wwtygBiy
// src/utils/auth.js
import auth0 from 'auth0-js'
import { navigate } from 'gatsby'
const AUTH0_DOMAIN = '<your domain="">.auth0.com'
const AUTH0_CLIENT_ID = '<your client="" id="">'
export default class Auth {
auth0 = new auth0.WebAuth({
domain: AUTH0_DOMAIN,
clientID: AUTH0_CLIENT_ID,
redirectUri: 'http://localhost:8000/auth0_callback',
audience: `https://${AUTH0_DOMAIN}/api/v2/`,
responseType: 'token id_token',
scope: 'openid profile email',
})
login = () => {
this.auth0.authorize()
}
logout = () => {
localStorage.removeItem('access_token')
localStorage.removeItem('id_token')
localStorage.removeItem('expires_at')
localStorage.removeItem('user')
}
handleAuthentication = () => {
if (typeof window !== 'undefined') {
// this must've been the trick
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult)
} else if (err) {
console.log(err)
}
// Return to the homepage after authentication.
navigate('/')
})
}
}
isAuthenticated = () => {
const expiresAt = JSON.parse(localStorage.getItem('expires_at'))
return new Date().getTime() < expiresAt
}
setSession = authResult => {
const expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
)
localStorage.setItem('access_token', authResult.accessToken)
localStorage.setItem('id_token', authResult.idToken)
localStorage.setItem('expires_at', expiresAt)
this.auth0.client.userInfo(authResult.accessToken, (err, user) => {
localStorage.setItem('user', JSON.stringify(user))
})
}
getUser = () => {
if (localStorage.getItem('user')) {
return JSON.parse(localStorage.getItem('user'))
}
}
getUserName = () => {
if (this.getUser()) {
return this.getUser().name
}
}
}
</your></your>
You don't have to understand how all this works, which is the beauty of using a service like Auth0, so here's a nutshell explanation:
- You set up an
auth0
client with config, put it inthis.auth0
- When you call
auth.login()
, the client takes over - Redirects to
AUTH0_DOMAIN
with some params - Users login through Auth0's beautiful UI
- Auth0 redirects back to
redirectUri
- Your callback page calls
auth.handleAuthentication
- Your
auth0
client parses URL params and decides all's good setSession
takes auth result and updates info in LocalStorage- Gatsby navigates back to your home page with
navigate('/')
We also have a couple convenience methods like isAuthenticated
, getUser
, and getUserName
. You might want to make these into proper ES6 getters. I haven't decided yet if that's better or not but it sure looks neater.
Step 6: Gatsby callback page
I mentioned a "callback page". You might think that's complicated, but with Gatsby, it's not. You just need a React component that loads on /auth0_callback
and calls handleAuthentication
on mount.
Like this 👇
// src/pages/auth0_callback.js
import React from "react";
import { ClipLoader } from "react-spinners";
import Auth from "../utils/auth";
import Layout from "../components/layout";
import useComponentDidMount from "../useComponentDidMount";
const Auth0CallbackPage = () => {
useComponentDidMount(() => {
const auth = new Auth();
auth.handleAuthentication();
});
return (
<layout>
<h1>
This is the auth callback page, you should be redirected immediately.
</h1>
<cliploader sizeunit="px" size={150}></cliploader>
</layout>
);
};
export default Auth0CallbackPage;
I'm using React hooks, but don't let that distract you.
Gatsby turns every file in the /src/pages
directory into a static page. So you now have a page that renders some text and a loading spinner from react-spinners
.
When the component mounts, that's only in browsers, we create a new Auth()
object and call auth.handleAuthentication()
. Our auth service takes care of the rest.
PS: the useComponentDidMount
hook looks like this:
export default function useComponentDidMount(onMounted) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
onMounted();
}, [mounted]);
}
It's a combination of useState
and useEffect
that ensures a function runs once and only once – on component mount. My understanding is that useEffect
runs any time React touches our component and I wanted to make certain the auth code runs once.
If you run handleAuthentication
multiple times, it leads to strange errors. State gets corrupted, tokens are out of date, everything breaks.
Step 8: A login button
Now all you need is a login button.
// src/components/Login.js
import React from "react";
import { Button } from "reakit";
import Auth from "../utils/Auth";
const auth = new Auth();
const Login = () => {
const { isAuthenticated } = auth;
if (isAuthenticated()) {
return <button onclick={auth.logout}>Logout {auth.getUserName()}</button>;
} else {
return <button onclick={auth.login}>Login</button>;
}
};
export default Login;
Since Auth0 handles all our user state, we don't need any sort of props or state management. Just a component that instantiates an Auth0 object and renders a button.
Use the isAuthenticated
helper to decide whether to say Login
or Logout
and call the appropriate methods on click.
That's it.
Once I got Gatsby and Auth0 to play together it was really quite delightful. Short writeup coming soon pic.twitter.com/eBNFGmxmoq
— Swizec Teller (@Swizec) January 23, 2019
Continue reading about How to set up Gatsby and Auth0 for easy authentication on your next project
Semantically similar articles hand-picked by GPT-4
- useAuth – the simplest way to add authentication to your React app
- useReducer + useContext for easy global state without libraries
- Connecting Gumroad to Auth0 for paywalled JAMStack apps
- Add granular role-based access to your JAMStack app
- How serverless beats servers
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️