Skip to content

pixel-point/gatsby-theme-auth0-minimal

Repository files navigation

gatsby-theme-auth0-minimal 🔐

Just-add-water Auth0 authorization in your GatsbyJS app

Live Example | Source Code

What is that?

This is Auth0 authorization plugin built upon such concept as Gatsby Themes. It will allow you to set up a full-fledged user authorization flow in your Gatsby app in a matter of minutes.

  • 🔲 Minimal setup without dependencies overhead
  • 🚝 Uses modern approach with functional components and hooks
  • 💎 No crutches and cumbersome custom functionality, every line of code is easily explainable and written with Gatsby/Auth0 recommendations and best practices in mind
  • 👌 /auth/callback page comes out of the box. Easily tweakable via callbackPath
  • ⚙️ Fully customisable thanks to Shadowing

Installation

$ npm install --save gatsby-theme-auth0-minimal

Usage

Setting up environment variables

Before we begin you have to create .env file at the root of your project. There are actually a few other approaches to provide variables into your app, but we will stick with simplest possible.

Here is example of how .env should look:

# required
GATSBY_AUTH0_DOMAIN=your-auth-app.auth0.com
GATSBY_AUTH0_CLIENTID=ADG34DG236YQ2DGQXB345
GATSBY_AUTH0_CALLBACK=https://localhost:9000/auth/callback
# optional
GATSBY_AUTH0_AUDIENCE=https://your-auth-app.auth0.com/api/v2/
GATSBY_AUTH0_SCOPE='profile email'
GATSBY_AUTH0_CALLBACK_PATH=/callback

The same structure you can spot in our example. Feel free to copy that and use in your project.

Here is detailed explanation:

Key Default Required Description
GATSBY_AUTH0_DOMAIN true Configure Auth0 Domain.
GATSBY_AUTH0_CLIENTID true Configure Auth0 Client ID
GATSBY_AUTH0_CALLBACK true Configure Auth0 Callback URL
GATSBY_AUTH0_AUDIENCE false false Configure Auth0 Audience
GATSBY_AUTH0_SCOPE "openid email profile" false Configure Auth0 Scope
GATSBY_AUTH0_CALLBACK_PATH "/auth/callback" false Change callback URL path

Note ⚠️ Originally Auth0 also allows to set up the responseType field with exact three options in our case of Implicit Flow , those are token, id_token and token id_token. To offer you maximum flexibility and prevent potential bugs we removed ability to tweak this parameter and set it to token id_token by default.

Wrapping root component

This theme uses React Context mechanism as the single source of truth in terms of auth state for the whole app, so the first step would be wrapping our root component with our AuthProvider.

This is our custom wrapper:

// with-auth-provider.js
import React from 'react';
import { AuthProvider } from 'gatsby-theme-auth0-minimal';

export default ({ element }) => {
  return <AuthProvider>{element}</AuthProvider>;
};

Here is it's example source code

Next step we are filling our gatsby-browser.js and gatsby-ssr.js as described in Gatsby's doc:

// gatsby-ssr.js
import withAuthProvider from './path/to/with-auth-provider';
export const wrapRootElement = withAuthProvider;

gatsby-ssr.js example source code

// gatsby-browser.js
import withAuthProvider from './path/to/with-auth-provider';
export const wrapRootElement = withAuthProvider;

gatsby-browser.js example source code

And yes, this duplication is intentional.

Using Auth Hook

The last part, we are getting access to authState via exposed useAuth hook:

import React from 'react';
import { useAuth } from 'gatsby-theme-auth0-minimal';

export default () => {
  const {
    isAuthenticated,
    login,
    logout,
    checkSession,
    authState: {
      // handful
      token, // access token for API requests
      user, // auth0 user data
      // just in case
      idToken, // contains user payload
      expiresAt, // how long token are going to live
      unschedule, // auxillary field to manually unschedule token renewing
      isLoading, // flag that reflects auth request state, helpes handle the UI
    },
    handleAuthentication, // would be useful implementing you own callback component
  } = useAuth();
  // set this up at highest level of your protected client routes
  // to automatically handle the authentication (this is called silent auth)
  useEffect(() => {
    if (localStorage.getItem('isLoggedIn') === 'true') {
      checkSession();
    }
  }, []);
  if (isLoading) {
    return <p>Loading...</p>;
  }
  return (
    <div>
      {user && <p>Hello {user.name}</p>}
      {isAuthenticated ? (
        <button onClick={logout}>Logout</button>
      ) : (
        <button onClick={login}>Login</button>
      )}
    </div>
  );
};

Source code for this piece

useAuth() return value under the microscope:

Methods

name args description
login - Wrapper for auth0 native login function. Logs the user in with username and password using the implicit flow. Sets postLoginUrl value into localStorage where user is being redirected after he successful logs in.
logout - Wrapper for auth0 native logout function. Redirects to the auth0 logout endpoint. Refreshes authState with default values. Removes any isLoggedIn flag from localStorage.
checkSession - Wrapper for auth0 native checkSession function. Renews an existing session on Auth0's servers using response_mode=web_message. Changes isLoading during execution (true -> false). Exploits postLoginUrl value.
handleAuthentication - Wrapper for auth0 native parseHash function. Parse the url hash and extract the Auth response from a Auth flow. Changes isLoading during execution (true -> false). Exploits postLoginUrl value.

Properties

name type description
isLoading Boolean Flag that reflects current state of possible auth0 requests
isAuthenticated Boolean Flag that reflects current authentication state, binded to authState.expiresAt property.
authState Object Object representing auth data. Contains:
- accessToken String | null String with auth0 access token
- idToken String | null String with auth0 id token
- userProfile Object | null Object with user data
- expiresAt Date | 0 Lifespan amount of a token
- unschedule Function | null Function that serves as a flag that reflects state of token auto renewal. Clears schedule on execution.

Check out full example

Important concepts in use

Shadowing

Gatsby Themes has a concept called Shadowing, which allows users to override a file in a gatsby theme. This allows the theme to be fully customizable.

To start shadowing, create a folder with the theme name gatsby-theme-auth0 in your project's src directory.

Now you're able to override any file in the theme. For example, if you want to override the callback component, create a file:

src/gatsby-theme-auth0-minimal/components/common/callback.js

Auth0 Implicit Flow

How everything works under the hood in 1 minute

There is a number of authentication flow exist, but for Gatsby its the Implicit Flow since its basically SPA when it comes to authorization business. Keep in mind that tokens are short-lived and refresh tokens are not available in this flow.

Implicit Flow Authentication Sequence

  1. The user clicks Login within the SPA.
  2. Auth0's SDK redirects the user to the Auth0 Authorization Server (/authorize endpoint) passing along a response_type parameter that indicates the type of requested credential.
  3. Your Auth0 Authorization Server redirects the user to the login and authorization prompt.
  4. The user authenticates using one of the configured login options and may see a consent page listing the permissions Auth0 will give to the SPA.
  5. Your Auth0 Authorization Server redirects the user back to the SPA with an ID token and an Access Token.
  6. Your SPA can use the Access Token to call an API.
  7. The API responds with requested data.

Access Token Lifespan

Custom APIs

By default, an Access Token for a custom API is valid for 86400 seconds (24 hours) .

To learn how to change the Access Token expiration time, see Update Access Token Lifetime.

/userinfo endpoint

Access Tokens issued strictly for the purpose of accessing the OpenID Connect (OIDC) /userinfo endpoint have a default lifetime and can't be changed. The length of lifetime in implicit flow is equal to 7200 seconds (2 hours).

Scheduled renewal

To leave an active user out of pointless interaction when his token is expired but he IS actually authorized, silent authentication concept is being used here. Here is how it works:

  • Schedule renewal initiation is happening during setSession method by calling scheduleRenewal function. This will occur after every authentication flow, either when the user explicitly logs in, or when the silent authentication happens.

  • scheduleRenewal sets up a time at which authentication should be silently renewed, which is equal to 30 seconds before the actual token expires. Also updates AuthProvider state with a reference to unschedule function which allows to remove last scheduled renewal on call.

  • If the renewal is successful, use the existing setSession method to set the new tokens in local storage AND schedule a new token renewal.

Example in Auth0 docs

Contribution

Contributions of any kind are welcome! If you know, how to make it better, add a new exciting functionality or simply fix a typ0 in this README file - go for it! Your previous coding experience doesn't matter as long as you understand what are you trying to do 😄

In order to start contributing, run

git clone git@github.com:pixel-point/gatsby-theme-auth0-minimal.git && yarn install

that will get you a local copy of the project with all deps installed, but make sure you read next chapters before you put your hands on the code.

Understanding Yarn workspaces

This project is using Yarn's workspaces concept, which allows to skip the mess with npm link , mantain both example and actual theme's code in the same repo and painless development. More info on that you can find in this awesome post by Brent Jackson.

Auth0 Application Settings

To use this example, certain application settings must be set correctly, otherwise unexpected behavior will occur.

Set the following fields to the values shown below, where {portNumber} is whatever port number you are using (e.g., port 8000 in development mode):

  • Allowed Callback URLs http://localhost:{portNumber}/auth/callback

  • Allowed Web Origins http://localhost:{portNumber}

  • Allowed Logout URLs http://localhost:{portNumber}

Make sure that there is no trailing '/' after the port number in the Allowed Web Origins and Allowed Logout URLs fields.

Set up env variables

To be able to work on that locally you should get an acc on Auth0 (its' free), follow their initiation procedure and grab values from your dashboard.

Go to example directory, copy the env.example -> .env.development. Fill in the required environment variables before starting up the client dev server.

Available Scripts

$ yarn dev

This will run the demo app in development mode using .env.development.

Navigate to http://localhost:8000 to view it in the browser.

$ yarn build

This will build the demo app for production using .env.production.

Outputs to the example/public folder.

$ yarn serve

This will serve previously built demo app for production using .env.production.

Outputs to the example/public folder.

$ yarn clean

This will remove contents of example/public and its cache by running gatsby clean in example dir

$ yarn format

This will format all files with js, js, json or md extensions in the repo, excluding those in .prettierignore

$ yarn lint

Same as above, but with linting.

Issues

If you experiencing any issue or believe there is a bug, feel free leave an issue with thorough description and how to reproduce step-by-step instruction. However, don't hesitate to make a PR, as was previously mentioned, contributions of any kind are welcome.

Credits

Licence

MIT (C) 2020

About

Simplest possible Auth0 + Gatsby integration. No deps overhead.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published