A Recipe to Power Up Gatsby with Auth

A Recipe to Power Up Gatsby with Auth

Gatsby rose to fame as a static site generator, but it’s also an amazing platform for building dynamic sites! Wouldn’t it be awesome if we could add a user profile with access to protected data in our Gatsby app? Like Mario’s fire flower or Link’s hook shot, adding auth will power up your dynamic Gatsby site. Just one question: is it any different than adding auth to a regular React app?

In this talk, you’ll learn about Gatsby’s build process and runtime and how they impact setting up auth. You’ll also learn how to use Hooks and Context to build your auth sidekick. Finally, you’ll learn how Gatsby recipes can speed up this implementation and make it easy to replicate in all of your projects. By the end, you’ll be ready for the next level of your Gatsby quest!

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

July 01, 2020
Tweet

Transcript

  1. None
  2. A Receipe to Power Up Gatsby

  3. A Receipe to Power Up Gatsby with Auth!

  4. @samjulien

  5. Static Site Generator @samjulien

  6. Dynamic Site Framework @samjulien

  7. Blazing Fast Web & App Framework @samjulien

  8. @samjulien

  9. @samjulien +

  10. @samjulien +

  11. @samjulien

  12. @samjulien

  13. Gatsby Recipes # @samjulien

  14. Adding Auth to Gatsby @samjulien

  15. Adding Auth to Gatsby @samjulien Build Time vs. Runtime

  16. Adding Auth to Gatsby @samjulien Build Time vs. Runtime Using

    Hooks for Auth
  17. Adding Auth to Gatsby @samjulien Build Time vs. Runtime Using

    Hooks for Auth An Auth Recipe for Gatsby %
  18. @samjulien Sam Julien samjulien.com

  19. @samjulien Sam Julien samjulien.com Sr. Developer Advocate Engineer at Auth0

  20. @samjulien Sam Julien samjulien.com Sr. Developer Advocate Engineer at Auth0

    Google Developer Expert
  21. @samjulien Sam Julien samjulien.com Sr. Developer Advocate Engineer at Auth0

    Google Developer Expert Instructor for Thinkster & egghead
  22. Adding Auth to Gatsby @samjulien Build Time vs. Runtime Using

    Hooks for Auth An Auth Recipe for Gatsby %
  23. @samjulien

  24. Build Time Runtime @samjulien

  25. Build Time Runtime @samjulien

  26. Build Time Runtime @samjulien

  27. Build Time Runtime @samjulien

  28. No browser APIs available! @samjulien

  29. Build Time Runtime @samjulien

  30. Build Time Runtime @samjulien

  31. Build Time Runtime @samjulien

  32. Build Time Runtime @samjulien

  33. Build Time Runtime @samjulien

  34. Build Time Runtime @samjulien

  35. Build Time Runtime @samjulien

  36. @samjulien +

  37. @samjulien +

  38. @samjulien

  39. Start login Handle redirect Log in to provider @samjulien

  40. Handle redirect Success Error @samjulien

  41. Success User Token @samjulien

  42. Success User Token Redirect @samjulien

  43. Adding Auth to Gatsby @samjulien Build Time vs. Runtime Using

    Hooks for Auth An Auth Recipe for Gatsby %
  44. @samjulien

  45. Auth Utility @samjulien

  46. @samjulien Auth Utility

  47. @samjulien Store token and user in memory Auth Utility

  48. @samjulien Store token and user in memory Interact with auth

    SDK Auth Utility
  49. @samjulien Store token and user in memory Interact with auth

    SDK Handle all redirect logic Auth Utility
  50. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  51. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  52. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  53. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  54. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  55. @samjulien export const AuthProvider = ({ children, ...initOptions }) =>

    {...};
  56. @samjulien export const AuthProvider = ({ children, ...initOptions }) =>

    { };
  57. @samjulien export const AuthProvider = ({ children, ...initOptions }) =>

    { ...};
  58. @samjulien ...

  59. @samjulien const [authClient, setAuthClient] = useState(); const [isAuthenticated, setIsAuthenticated] =

    useState(); const [token, setToken] = useState(); const [user, setUser] = useState(); const [loading, setLoading] = useState(true);
  60. @samjulien const [authClient, setAuthClient] = useState(); const [isAuthenticated, setIsAuthenticated] =

    useState(); const [token, setToken] = useState(); const [user, setUser] = useState(); const [loading, setLoading] = useState(true);
  61. @samjulien const [authClient, setAuthClient] = useState(); const [isAuthenticated, setIsAuthenticated] =

    useState(); const [token, setToken] = useState(); const [user, setUser] = useState(); const [loading, setLoading] = useState(true);
  62. @samjulien const [authClient, setAuthClient] = useState(); const [isAuthenticated, setIsAuthenticated] =

    useState(); const [token, setToken] = useState(); const [user, setUser] = useState(); const [loading, setLoading] = useState(true);
  63. @samjulien const [authClient, setAuthClient] = useState(); const [isAuthenticated, setIsAuthenticated] =

    useState(); const [token, setToken] = useState(); const [user, setUser] = useState(); const [loading, setLoading] = useState(true);
  64. @samjulien useEffect(() => { const initAuth = async () =>

    { // initialize the auth client const authFromHook = await createAuthClient(initOptions); setAuth(authFromHook); // check if we're in the middle of a redirect if ( window.location.search.includes("code=") && window.location.search.includes("state=") ) { const { appState } = await authFromHook.handleRedirectCallback(); onRedirectCallback(appState); }
  65. @samjulien useEffect(() => { const initAuth = async () =>

    { // initialize the auth client const authFromHook = await createAuthClient(initOptions); setAuth(authFromHook); // check if we're in the middle of a redirect if ( window.location.search.includes("code=") && window.location.search.includes("state=") ) { const { appState } = await authFromHook.handleRedirectCallback(); onRedirectCallback(appState); }
  66. @samjulien useEffect(() => { const initAuth = async () =>

    { // initialize the auth client const authFromHook = await createAuthClient(initOptions); setAuth(authFromHook); // check if we're in the middle of a redirect if ( window.location.search.includes("code=") && window.location.search.includes("state=") ) { const { appState } = await authFromHook.handleRedirectCallback(); onRedirectCallback(appState); }
  67. @samjulien useEffect(() => { const initAuth = async () =>

    { // initialize the auth client const authFromHook = await createAuthClient(initOptions); setAuth(authFromHook); // check if we're in the middle of a redirect if ( window.location.search.includes("code=") && window.location.search.includes("state=") ) { const { appState } = await authFromHook.handleRedirectCallback(); onRedirectCallback(appState); }
  68. @samjulien // set initial state using hooks const isAuthenticated =

    await authFromHook.isAuthenticated(); setIsAuthenticated(isAuthenticated); if (isAuthenticated) { const user = await authFromHook.getUser(); setUser(user); } setLoading(false); }; initAuth(); }, []);
  69. @samjulien // set initial state using hooks const isAuthenticated =

    await authFromHook.isAuthenticated(); setIsAuthenticated(isAuthenticated); if (isAuthenticated) { const user = await authFromHook.getUser(); setUser(user); } setLoading(false); }; initAuth(); }, []);
  70. @samjulien // set initial state using hooks const isAuthenticated =

    await authFromHook.isAuthenticated(); setIsAuthenticated(isAuthenticated); if (isAuthenticated) { const user = await authFromHook.getUser(); setUser(user); } setLoading(false); }; initAuth(); }, []);
  71. @samjulien // set initial state using hooks const isAuthenticated =

    await authFromHook.isAuthenticated(); setIsAuthenticated(isAuthenticated); if (isAuthenticated) { const user = await authFromHook.getUser(); setUser(user); } setLoading(false); }; initAuth(); }, []);
  72. @samjulien // set initial state using hooks const isAuthenticated =

    await authFromHook.isAuthenticated(); setIsAuthenticated(isAuthenticated); if (isAuthenticated) { const user = await authFromHook.getUser(); setUser(user); } setLoading(false); }; initAuth(); }, []);
  73. @samjulien // set initial state using hooks const isAuthenticated =

    await authFromHook.isAuthenticated(); setIsAuthenticated(isAuthenticated); if (isAuthenticated) { const user = await authFromHook.getUser(); setUser(user); } setLoading(false); }; initAuth(); }, []);
  74. @samjulien const handleRedirectCallback = async () => { // handle

    callback // use hooks to set state };
  75. @samjulien return ( <AuthContext.Provider value={{ isAuthenticated, user, token, loading, handleRedirectCallback,

    getToken: (...p) => authClient.getTokenSilently(...p), logout: (...p) => authClient.logout(...p), }} > {children} </AuthContext.Provider> );
  76. @samjulien return ( <AuthContext.Provider value={{ isAuthenticated, user, token, loading, handleRedirectCallback,

    getToken: (...p) => authClient.getTokenSilently(...p), logout: (...p) => authClient.logout(...p), }} > {children} </AuthContext.Provider> );
  77. @samjulien return ( <AuthContext.Provider value={{ isAuthenticated, user, token, loading, handleRedirectCallback,

    getToken: (...p) => authClient.getTokenSilently(...p), logout: (...p) => authClient.logout(...p), }} > {children} </AuthContext.Provider> );
  78. @samjulien return ( <AuthContext.Provider value={{ isAuthenticated, user, token, loading, handleRedirectCallback,

    getToken: (...p) => authClient.getTokenSilently(...p), logout: (...p) => authClient.logout(...p), }} > {children} </AuthContext.Provider> );
  79. @samjulien return ( <AuthContext.Provider value={{ isAuthenticated, user, token, loading, handleRedirectCallback,

    getToken: (...p) => authClient.getTokenSilently(...p), logout: (...p) => authClient.logout(...p), }} > {children} </AuthContext.Provider> );
  80. @samjulien return ( <AuthContext.Provider value={{ isAuthenticated, user, token, loading, handleRedirectCallback,

    getToken: (...p) => authClient.getTokenSilently(...p), logout: (...p) => authClient.logout(...p), }} > {children} </AuthContext.Provider> );
  81. Auth Utility @samjulien

  82. @samjulien Auth Utility

  83. @samjulien Store token and user in memory Interact with auth

    SDK Handle all redirect logic Auth Utility
  84. Does our Gatsby app work? @samjulien

  85. Maybe (but probably not). @samjulien

  86. gatsby develop vs gatsby build @samjulien

  87. None
  88. None
  89. Build Time Runtime @samjulien

  90. Build Time Runtime @samjulien

  91. Build Time Runtime @samjulien

  92. Build Time Runtime @samjulien

  93. Build Time Runtime @samjulien

  94. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  95. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  96. @samjulien const defaultContext = { isAuthenticated: false, user: null, token:

    null, loading: false, login: () => {}, };
  97. Why do we do this? @samjulien

  98. Example: NavBar Component @samjulien

  99. @samjulien const NavBar = () => { const { isAuthenticated,

    login, logout } = useAuth(); return ( <div> <Link to="/">Home</Link>{" "} {!isAuthenticated && <button onClick={() => login()}>Log in</button>} {isAuthenticated && ( <> <Link to="/profile">Profile</Link> <button onClick={() => logout()}>Log out</button> </> )} </div> ); };
  100. @samjulien const NavBar = () => { const { isAuthenticated,

    login, logout } = useAuth(); return ( <div> <Link to="/">Home</Link>{" "} {!isAuthenticated && <button onClick={() => login()}>Log in</button>} {isAuthenticated && ( <> <Link to="/profile">Profile</Link> <button onClick={() => logout()}>Log out</button> </> )} </div> ); };
  101. @samjulien const NavBar = () => { const { isAuthenticated,

    login, logout } = useAuth(); return ( <div> <Link to="/">Home</Link>{" "} {!isAuthenticated && <button onClick={() => login()}>Log in</button>} {isAuthenticated && ( <> <Link to="/profile">Profile</Link> <button onClick={() => logout()}>Log out</button> </> )} </div> ); };
  102. @samjulien const NavBar = () => { const { isAuthenticated,

    login, logout } = useAuth(); return ( <div> <Link to="/">Home</Link>{" "} {!isAuthenticated && <button onClick={() => login()}>Log in</button>} {isAuthenticated && ( <> <Link to="/profile">Profile</Link> <button onClick={() => logout()}>Log out</button> </> )} </div> ); };
  103. @samjulien const NavBar = () => { const { isAuthenticated,

    login, logout } = useAuth(); return ( <div> <Link to="/">Home</Link>{" "} {!isAuthenticated && <button onClick={() => login()}>Log in</button>} {isAuthenticated && ( <> <Link to="/profile">Profile</Link> <button onClick={() => logout()}>Log out</button> </> )} </div> ); };
  104. What if we use this in a Gatsby page? @samjulien

  105. @samjulien export default () => { const { loading }

    = useAuth(); if (loading) { return <div>Loading...</div>; } return ( <div> <NavBar /> <Home /> </div> ); };
  106. @samjulien export default () => { const { loading }

    = useAuth(); if (loading) { return <div>Loading...</div>; } return ( <div> <NavBar /> <Home /> </div> ); };
  107. @samjulien export default () => { const { loading }

    = useAuth(); if (loading) { return <div>Loading...</div>; } return ( <div> <NavBar /> <Home /> </div> ); };
  108. @samjulien

  109. @samjulien

  110. @samjulien

  111. @samjulien

  112. @samjulien const defaultContext = { isAuthenticated: false, user: null, token:

    null, loading: false, login: () => {}, };
  113. @samjulien

  114. @samjulien

  115. @samjulien

  116. @samjulien

  117. @samjulien

  118. Where do we use this AuthProvider? @samjulien

  119. Adding Auth to Gatsby @samjulien Build Time vs. Runtime Using

    Hooks for Auth An Auth Recipe for Gatsby %
  120. Gatsby Recipes # @samjulien

  121. Gatsby Recipes # @samjulien

  122. Automate common tasks Gatsby Recipes # @samjulien

  123. Automate common tasks Gatsby Recipes # Written in MDX @samjulien

  124. Automate common tasks Gatsby Recipes # Install packages and plugins

    Written in MDX @samjulien
  125. Automate common tasks Gatsby Recipes # Install packages and plugins

    Generate pages and code Written in MDX @samjulien
  126. Automate common tasks Run from the CLI Gatsby Recipes #

    Install packages and plugins Generate pages and code Written in MDX @samjulien
  127. Written in MDX @samjulien

  128. Step 0: Create a local MDX file or a Gist.

    @samjulien
  129. Install packages and plugins @samjulien

  130. Step 1: Install Auth SDK @samjulien

  131. Step 1: Install Auth SDK <NPMPackage name="auth-sdk" /> @samjulien

  132. Generate pages and code @samjulien

  133. Step 2: Add Auth Provider @samjulien

  134. Step 2: Add Auth Provider <File path="./src/utils/auth.js" content="./auth-recipe/auth.js" /> @samjulien

  135. Step 2: Add Auth Provider <File path="./src/utils/auth.js" content="./auth-recipe/auth.js" /> @samjulien

  136. Step 2: Add Auth Provider <File path="./src/utils/auth.js" content="./auth-recipe/auth.js" /> @samjulien

  137. Where do we use this AuthProvider? @samjulien

  138. Step 3: Add gatsby-browser.js @samjulien

  139. gatsby-browser.js @samjulien

  140. export const wrapRootElement = ({ element }) => { return

    ( <AuthProvider // pass in any configuration here > {element} </AuthProvider> ); }; @samjulien
  141. export const wrapRootElement = ({ element }) => { return

    ( <AuthProvider // pass in any configuration here > {element} </AuthProvider> ); }; @samjulien
  142. export const wrapRootElement = ({ element }) => { return

    ( <AuthProvider // pass in any configuration here > {element} </AuthProvider> ); }; @samjulien
  143. Step 3: Add gatsby-browser.js @samjulien

  144. <File path="./gatsby-browser.js" content="./auth-recipe/gatsby- browser.js" />; Step 3: Add gatsby-browser.js @samjulien

  145. Build Time Runtime @samjulien

  146. No browser APIs available! @samjulien

  147. Watch out for dependencies using browser APIs like window or

    document. @samjulien
  148. Watch out for dependencies using browser APIs like window or

    document. @samjulien (Spoilers: auth libraries usually do this a lot because of redirects and pop-ups.)
  149. Step 4 (maybe): Add gatsby-node.js @samjulien

  150. gatsby-node.js @samjulien

  151. @samjulien exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {

    if (["build-html", “develop-html"].includes(stage)) { actions.setWebpackConfig({ module: { rules: [ { test: /auth-sdk/, use: loaders.null(), }, ], }, }); } };
  152. @samjulien exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {

    if (["build-html", “develop-html"].includes(stage)) { actions.setWebpackConfig({ module: { rules: [ { test: /auth-sdk/, use: loaders.null(), }, ], }, }); } };
  153. @samjulien exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {

    if (["build-html", "develop-html"].includes(stage)) { actions.setWebpackConfig({ module: { rules: [ { test: /auth-sdk/, use: loaders.null(), }, ], }, }); } };
  154. @samjulien exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {

    if (["build-html", "develop-html"].includes(stage)) { actions.setWebpackConfig({ module: { rules: [ { test: /auth-sdk/, use: loaders.null(), }, ], }, }); } };
  155. @samjulien exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {

    if (["build-html", "develop-html"].includes(stage)) { actions.setWebpackConfig({ module: { rules: [ { test: /auth-sdk/, use: loaders.null(), }, ], }, }); } };
  156. @samjulien exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {

    if (["build-html", "develop-html"].includes(stage)) { actions.setWebpackConfig({ module: { rules: [ { test: /auth-sdk/, use: loaders.null(), }, ], }, }); } };
  157. Step 4 (maybe): Add gatsby-node.js @samjulien

  158. <File path="./gatsby-node.js" content="./auth-recipe/gatsby- node.js" />; Step 4 (maybe): Add gatsby-node.js

    @samjulien
  159. Run from the CLI @samjulien

  160. Step 5: Run your recipe! @samjulien

  161. gatsby recipes ./auth-recipe/recipe.mdx Step 5: Run your recipe! @samjulien

  162. Level up: Update this to create a local plugin @samjulien

  163. @samjulien <GatsbyPlugin name="gatsby-plugin-auth-recipe" options={{ domain: `domain.auth-provider.com`, clientId: `theclientid`, }} />;

  164. Let’s Review @samjulien

  165. @samjulien

  166. Static Site Generator @samjulien

  167. Dynamic Site Framework @samjulien

  168. @samjulien +

  169. @samjulien +

  170. @samjulien

  171. Build Time Runtime @samjulien

  172. Build Time Runtime @samjulien

  173. Start login Handle redirect Log in to provider @samjulien

  174. Handle redirect Success Error @samjulien

  175. Success User Token @samjulien

  176. Success User Token Redirect @samjulien

  177. @samjulien

  178. Auth Utility @samjulien

  179. @samjulien Auth Utility

  180. @samjulien Store token and user in memory Auth Utility

  181. @samjulien Store token and user in memory Interact with auth

    SDK Auth Utility
  182. @samjulien Store token and user in memory Interact with auth

    SDK Handle all redirect logic Auth Utility
  183. @samjulien export const AuthContext = React.createContext(defaultContext); export const useAuth =

    () => useContext(AuthContext);
  184. @samjulien return ( <AuthContext.Provider value={{ isAuthenticated, user, token, loading, handleRedirectCallback,

    getToken: (...p) => authClient.getTokenSilently(...p), logout: (...p) => authClient.logout(...p), }} > {children} </AuthContext.Provider> );
  185. @samjulien const defaultContext = { isAuthenticated: false, user: null, token:

    null, loading: false, login: () => {}, };
  186. @samjulien

  187. @samjulien

  188. @samjulien

  189. @samjulien

  190. Gatsby Recipes # @samjulien

  191. Gatsby Recipes # @samjulien

  192. Automate common tasks Gatsby Recipes # @samjulien

  193. Automate common tasks Gatsby Recipes # Written in MDX @samjulien

  194. Automate common tasks Gatsby Recipes # Install packages and plugins

    Written in MDX @samjulien
  195. Automate common tasks Gatsby Recipes # Install packages and plugins

    Generate pages and code Written in MDX @samjulien
  196. Automate common tasks Run from the CLI Gatsby Recipes #

    Install packages and plugins Generate pages and code Written in MDX @samjulien
  197. Step 0: Create a local MDX file or a Gist.

    @samjulien
  198. Step 1: Install Auth SDK <NPMPackage name="auth-sdk" /> @samjulien

  199. Step 2: Add Auth Provider <File path="./src/utils/auth.js" content="./auth-recipe/auth.js" /> @samjulien

  200. <File path="./gatsby-browser.js" content="./auth-recipe/gatsby- browser.js" />; Step 3: Add gatsby-browser.js @samjulien

  201. export const wrapRootElement = ({ element }) => { return

    ( <AuthProvider // pass in any configuration here > {element} </AuthProvider> ); }; @samjulien
  202. <File path="./gatsby-node.js" content="./auth-recipe/gatsby- node.js" />; Step 4 (maybe): Add gatsby-node.js

    @samjulien
  203. Build Time Runtime @samjulien

  204. No browser APIs available! @samjulien

  205. Watch out for dependencies using browser APIs like window or

    document. @samjulien
  206. Watch out for dependencies using browser APIs like window or

    document. @samjulien (Spoilers: auth libraries usually do this a lot because of redirects and pop-ups.)
  207. @samjulien exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {

    if (stage === "build-html") { actions.setWebpackConfig({ module: { rules: [ { test: /auth-sdk/, use: loaders.null(), }, ], }, }); } };
  208. gatsby recipes ./auth-recipe/recipe.mdx Step 5: Run your recipe! @samjulien

  209. Level up: Update this to create a local plugin @samjulien

  210. @samjulien

  211. samj.im/gatsby-days @samjulien

  212. samj.im/gatsby-days Thank you! @samjulien