Swizec Teller - a geek with a hatswizec.com

    How to set up Gatsby and Auth0 for easy authentication on your next project

    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

    Auth0 dashboard screenshot

    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.

    Callback config

    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

    Swizec Teller published ServerlessHandbook.dev avatarSwizec Teller published ServerlessHandbook.dev@Swizec
    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.

    Because copypasta will get you out of the weirdest shit imaginable. #200wordsTIL

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

    1. You set up an auth0 client with config, put it in this.auth0
    2. When you call auth.login(), the client takes over
    3. Redirects to AUTH0_DOMAIN with some params
    4. Users login through Auth0's beautiful UI
    5. Auth0 redirects back to redirectUri
    6. Your callback page calls auth.handleAuthentication
    7. Your auth0 client parses URL params and decides all's good
    8. setSession takes auth result and updates info in LocalStorage
    9. 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.

    Swizec Teller published ServerlessHandbook.dev avatarSwizec Teller published ServerlessHandbook.dev@Swizec
    Once I got Gatsby and Auth0 to play together it was really quite delightful. Short writeup coming soon
    Tweet media

    Did you enjoy this article?

    Published on January 23rd, 2019 in Front End, Technical

    Learned something new?
    Want to become an expert?

    Here's how it works πŸ‘‡

    Leave your email and I'll send you thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

    Join Swizec's Newsletter

    And get thoughtful letters πŸ’Œ 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. πŸ‘Œ"

    ~ Ashish Kumar

    Join over 14,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

    Have a burning question that you think I can answer?Β I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

    Ready to Stop copy pasting D3 examples and create data visualizations of your own? Β Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

    Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

    Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first πŸ’° on the side with ServerlessReact.Dev

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

    Created bySwizecwith ❀️