Securing Gatsby with Auth0

Thanks. Actually, changing the ‘navigate’ was our first solution. But it doesn’t work. The application gets hung on the callback page and/or the entire application just goes back to whatever you put in navigate. So if you put in navigate(‘/’), after someone logs in, they can only access the homepage and no other page. So this doesn’t provide a solution. If that’s the solution for the new SDK also, then I guess the SDK will have the same problem.

Honestly, there is nothing different in our app. We copied your code exactly, and it works, except for the fact that you can only access the /account/ page after or whatever page is in the navigate. And we are using the Gatsby Starter blog. We are not the only one with this problem as per the comments above, and your guide specifically mentions this issue as well, as a problem. So it’s got nothing to do with our app. You yourselves recognize the problem, but don’t provide a solution. I’m sure many people just gave up on using Auth0 with Gatsby, after experiencing the same issue.

However, I’m sure, our auth.js now is completely wrong, because we have no idea what the changes should be based on the suggestions provided, so yeah, our app is messed up now. Why can’t you just publish an updated auth.js guide with the code that allows people to navigate to any part of the site after logging in? Of what benefit is it to offer a solution with a built-in bug? It will just make most people completely give up on your service, because the code provided doesn’t work and the solution to the problem is not being provided in a format that allows it to be implemented.

It would be in the financial interest of your company, to just publish the code in the auth.js or the new SDK, as it should be written, not as some sort of puzzle to figure out. If it works in the Gatsby store, why can’t you just publish what their auth.js is? It’s not some proprietary code if they are just using what you used in your guide with a couple of line chages. What is the point of making this difficult to use?

Here is the code for our current auth.js based on the changes suggested above. Please let us know what it is wrong. Per the suggestion above we also changed navigate to navigate(“/”) instead of ‘/account/’. So now when a user logs in they get redirected to the homepage now for every single page, instead of the /account/. So it’s the same problem, just a different page. When a user logs in, they can only access one page on the entire website. What is the code so that they can navigate the entire website and not get redirected to one page?

import auth0 from "auth0-js"

import { navigate } from "gatsby"

const isBrowser = typeof window !== "undefined"

const auth = isBrowser

  ? new auth0.WebAuth({

      domain: process.env.AUTH0_DOMAIN,

      clientID: process.env.AUTH0_CLIENTID,

      redirectUri: process.env.AUTH0_CALLBACK,

      responseType: "token id_token",

      scope: "openid profile email",

    })

  : {}

const tokens = {

  accessToken: false,

  idToken: false,

  expiresAt: false,

}

let user = {}

export const isAuthenticated = () => {

  if (!isBrowser) {

    return

  }

  return localStorage.getItem("isLoggedIn") === "true"

}

export const login = () => {

  if (!isBrowser) {

    return

  }

  auth.authorize({

    appState: {

      redirectUri: `${window.location.pathname}${window.location.search}`,

    },

})

}

const setSession = (cb = () => {}) => (err, authResult) => {

  const redirect = authResult.state || '/';

  

  if (err) {

    navigate(redirect)

    cb()

    return

  }

  if (authResult && authResult.accessToken && authResult.idToken) {

    let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()

    tokens.accessToken = authResult.accessToken

    tokens.idToken = authResult.idToken

    tokens.expiresAt = expiresAt

    user = authResult.idTokenPayload

    localStorage.setItem("isLoggedIn", true)

    navigate("/")

    cb()

  }

}

export const silentAuth = callback => {

  if (!isAuthenticated()) return callback()

  auth.checkSession({state: window.location.pathname+window.location.search}, setSession(callback))

}

export const handleAuthentication = () => {

  if (!isBrowser) {

    return

  }

  auth.parseHash(setSession())

}

export const getProfile = () => {

  return user

}

export const logout = () => {

  localStorage.setItem("isLoggedIn", false)

  auth.logout()

}

@ddsgadget that sounds really frustrating, sorry you’re dealing with that.

Thanks for posting your code; it helped me understand your problem much better.

I went ahead and created a new branch on the official repo to show the working redirect. Specifically, here’s the code that’s changed from the original in auth.js.

And here is auth.js in its entirety:

import auth0 from "auth0-js"
import { navigate } from "gatsby"

const isBrowser = typeof window !== "undefined"

const auth = isBrowser
  ? new auth0.WebAuth({
      domain: process.env.AUTH0_DOMAIN,
      clientID: process.env.AUTH0_CLIENTID,
      redirectUri: process.env.AUTH0_CALLBACK,
      responseType: "token id_token",
      scope: "openid profile email",
    })
  : {}

const tokens = {
  accessToken: false,
  idToken: false,
  expiresAt: false,
}

let user = {}

export const isAuthenticated = () => {
  if (!isBrowser) {
    return
  }

  return localStorage.getItem("isLoggedIn") === "true"
}

export const login = () => {
  if (!isBrowser) {
    return
  }

  auth.authorize({
    appState: `${window.location.pathname}${window.location.search}`,
  })
}

const setSession = (cb = () => {}) => (err, authResult) => {
  if (err) {
    navigate("/")
    cb()
    return
  }

  if (authResult && authResult.accessToken && authResult.idToken) {
    let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
    tokens.accessToken = authResult.accessToken
    tokens.idToken = authResult.idToken
    tokens.expiresAt = expiresAt
    user = authResult.idTokenPayload
    localStorage.setItem("isLoggedIn", true)
    const redirect = authResult.appState || "/"
    navigate(redirect)
    cb()
  }
}

export const silentAuth = callback => {
  if (!isAuthenticated()) return callback()

  auth.checkSession(
    {
      state: window.location.pathname + window.location.search,
    },
    setSession(callback)
  )
}

export const handleAuthentication = () => {
  if (!isBrowser) {
    return
  }

  auth.parseHash(setSession())
}

export const getProfile = () => {
  return user
}

export const logout = () => {
  localStorage.setItem("isLoggedIn", false)
  auth.logout()
}

I’ll get with the Technical Content team to see the best approach to updating the article.

Hope that solves the problem!

Awesome! Works now. Thanks so much. Really appreciate your help. You now have a customer for life and I can get some sleep. Cheers.

@ddsgadget So excited to hear this! :partying_face: Sleep well!

Not sure if anyone else has ran into this issue before but Im having a weird issues surrounding how the callback page returns on mobil devices.

When I run my site locally in development/ production mode and deploy to a CDN the login works perfect but when I go to check it out on a mobil device’s chrome/ safari I keep getting stuck in the /callback page.

Ive gone back through this thread and added some of the state and app state configurations to my projects auth.js and redeployed but I am seeing the same issue on mobil devices. Here is a link to my repo: GitHub - L-Town-FC/we-roast: Dont talk to me before i've had my coffee @sam.julien any thoughts?

I missed this one @myhendry – you should be able to just pass redirectTo param into logout:

auth0.logout({
    returnTo: 'http://localhost:3000/'
  });

To make this dynamic, add a LOGOUT_URL key to your .env file and change the logout function to:

auth0.logout({
    returnTo: process.env.LOGOUT_URL
  });

Just be sure all URLs are in the settings of the application in Auth0.

Hope that helps!

If I’m understanding you correctly, each SPA would need to be its own application with its own Client ID.

I feel like I have heard of this but can’t for the life of me remember what the answer was. @konrad.sopala do you have any idea why this may be? I think you’ve done more mobile dev than me.

Unfortunately not on the react side of things but rather native…

It’s a really weird bug, everything works on desktop. Even on mobil it directs you to the auth0 page then bounces you back to the call back page but the callback page never sends you to the profile page. If it helps I’m opening the site on both safari and chrome on my iPhone.

I will check with the SDK team on this. @atmollohan are you using auth0.js or the SPA SDK, I can’t remember?

I am using auth0.js. Here is a link to the repo for reference: GitHub - L-Town-FC/we-roast: Dont talk to me before i've had my coffee I’ll try to debug it on my own on mobile. Is there a specific place I could open a ticket or issue?

Go ahead and open an issue on the GitHub issues page. That way I can link both this thread and that GitHub issue in Slack to them.

Awesome, I opened the issue Issue with callback in Gatsby · Issue #1102 · auth0/auth0.js · GitHub

Thanks again. There might be something In the newer version of Gatsby that i causing this bug.

1 Like

So, we successfully deployed Auth0 and Gatsby on a production site. However, we ran into a frustrating bug. Basically, after as user logs in with Auth0, if the user then refreshes the page for whatever reason, a 404 Error appears, that the Auth0 authentication can’t be accessed. If you navigate the site without refreshing pages you are fine, but the minute you try to refresh the page, which happens quite often with users, the page is gone and there is a 404 error. Now I know this has to do with React Router somehow and how it processes the app, but the application demo with Auth0 and Gatsby uses React Router (@reach/router), so I’m wondering how we are supposed to fix this problem on a production site?

@ddsgadget are you using a connection/social provider like Google to login by chance? If you are you’ll need to register and create production keys with that provider. The Auth0 dev keys for those connections don’t work in production and won’t work with the silent auth. Click on the instructions for the right provider here for a tutorial on how to do it: Social Identity Providers

No, we are not using Google login. The only thing that changed is that we were using “passwordless” login, and it was working fine actually. Then we switched to Database authentication (as we needed additional functionality), and the app simply stopped working. So we switched back to “passwordless” and now we continue to have problems. After a user logs in, if they refresh the page, it shows a 400 error (Failed to load resource: the server responded with a status of 400 ). The resource that fails to load is the auth0 url (domain/authorize?client_id) etc. The strange thing is that if you never refresh the page, it navigates fine and you are logged in. But, if you refresh, then it just doesn’t load the auth0.

So we have run many more tests. The error we get on a page refresh is:
**invalid_request** : The specified redirect_uri 'https://www.OURWEBSITE.com/callback' does not have a registered origin.

Obviously, ourwebsite.com is changed to our website. This is a strange error, because the callback works fine without a page refresh, so why would the callback not work on a page refresh?? Our settings are fine and we have the callback page, and when a user clicks on the website, the callback is called without any errors.

Just a quick update, we believe we have solved the problem, by dropping the trailing slash from our origin URL. So we had it as: http://www.ourwebsite.com/ - but when we change the origin URL to http://www.ourwebsite.com , without the ‘/’- it seems to work fine.