Auth0 silent authentication failure

Hey everyone,

Loving RedwoodJS so far. What a great project. I’ve been following the tutorial / docs and have attempted to add authentication via Auth0 and am hitting what I think is either a bug or me not understanding the docs fully.

After running yarn rw g auth auth0

  • I have set up an Auth0 account, created both an SPA Application and an API.

  • I have put the domain and client id from the SPA Application as AUTH0_DOMAIN and AUTH0_CLIENT_ID environment variables in my .env file.

  • I have put the identifier from the API as the AUTH0_AUDIENCE environment variable in my .env file.

When I restart my dev server, I get a blank screen with the following console error:

Uncaught (in promise) TypeError: client.getUser is not a function
    at _callee4$ (authClient.js:119)
    at tryCatch (runtime.js:45)
    at Generator.invoke [as _invoke] (runtime.js:274)
    at Generator.prototype.<computed> [as next] (runtime.js:97)
    at asyncGeneratorStep (asyncToGenerator.js:5)
    at _next (asyncToGenerator.js:27)
    at asyncToGenerator.js:34
    at new Promise (<anonymous>)
    at new Wrapper (export.js:15)
    at Object.<anonymous> (asyncToGenerator.js:23)
    at Object.currentUser (authClient.js:134)
    at AuthProvider._callee3$ (AuthProvider.js:142)
    at tryCatch (runtime.js:45)
    at Generator.invoke [as _invoke] (runtime.js:274)
    at Generator.prototype.<computed> [as next] (runtime.js:97)
    at asyncGeneratorStep (asyncToGenerator.js:5)
    at _next (asyncToGenerator.js:27)

In my Auth0 logs, I am seeing the following (I’m masking the sensitive parts with ****):

Full output:

{
  "date": "2020-06-05T17:58:45.162Z",
  "type": "fsa",
  "description": "Login required",
  "client_id": "*******",
  "client_name": "GeoCities",
  "ip": "*******",
  "user_agent": "Chrome 83.0.4103 / Mac OS X 10.15.4",
  "details": {
    "body": {},
    "qs": {
      "client_id": "*******",
      "redirect_uri": "http://localhost:8910/",
      "audience": "https://******.dev/auth",
      "scope": "openid profile email",
      "response_type": "code",
      "response_mode": "web_message",
      "state": "NE95bm8ySVpWaThxWEZzZi5sRmd+bWJ5T3NDQVdSbEoxVWVZYnFPYjREUw==",
      "nonce": "RC52M0lQUXZEfnRQUTR1WWs0V3MwcVNNRmFDWTc1VzFZUDVJNGtMQWFNUw==",
      "code_challenge": "FetxtDg5-bhduZgKF8t5uiNf7iXxB7hOoTcIKc5RdIE",
      "code_challenge_method": "S256",
      "prompt": "none",
      "auth0Client": "eyJuYW1lIjoiYXV0aDAtc3BhLWpzIiwidmVyc2lvbiI6IjEuOS4wIn0="
    },
    "connection": null,
    "error": {
      "message": "Login required",
      "oauthError": "login_required",
      "type": "oauth-authorization"
    }
  },
  "hostname": "******.auth0.com",
  "audience": "https://******.dev/auth",
  "scope": [
    "openid",
    "profile",
    "email"
  ],
  "auth0_client": {
    "name": "auth0-spa-js",
    "version": "1.9.0"
  },
  "log_id": "90020200605175850402000145422265658362905777862510379090",
  "_id": "90020200605175850402000145422265658362905777862510379090",
  "isMobile": false
}

Did I miss something in configuration (either on the Redwood side or the Auth0 side)?

Thanks.

Hey there,

We messed up the generated auth0 import; it should be: import { Auth0Client } from '@auth0/auth0-spa-js'

It’s fixed over here, but not yet released: https://github.com/redwoodjs/redwood/pull/640/files

1 Like

THANK YOU. That worked. I have been pulling my hair out trying every possible configuration setting in Auth0’s admin trying to fix this. :innocent:

Ah damn, so sorry about that, I’ll get a release out ASAP.

1 Like

@peterp I noticed here: https://github.com/redwoodjs/example-invoice/issues/31 that the bit about updating the User model with user data from Auth0 was removed.

Is there any best practice or examples around authenticating with the auth provider and then creating or updating the User model in the RW app?

@pmarsceill I’m doing that over here in the example-invoice app:

In this example I’m waiting for the user to perform an authenticated request, if the auth header is verified and decoded then I get or create a user.

I used Auth0’s functions to get an email address added to the jwt, but you could also use a sub.

1 Like

I used Auth0’s functions to get an email address added to the jwt, but you could also use a sub.

Does that look like this on Auth0?:

function (user, context, callback) {
  // This rule adds the authenticated user's email address to the access token.

  var namespace = 'https://namespace.auth0.com/';

  context.accessToken[namespace + 'email'] = user.email;
  return callback(null, user, context);
}

Yup, I’ve added this to my rules:

function (user, context, callback) {
  context.accessToken['https://api.billable/email'] = user.email;
  return callback(null, user, context);
}

Thanks! I keep getting GraphQL 400 errors in my app when I sign in to my app, I assume this is because it’s not getting the email from the authToken in order to check to see if the user exists in the DB or not. I’m not sure how to debug this. I’ve never used auth0 before so I assume I have something set up incorrectly.

1 Like

I’m wondering if you have the audience set, could you see if the auth0 instructions here match your implementation: https://github.com/redwoodjs/redwood/tree/master/packages/auth#for-auth0

We’re just about to release v0.9.1, which you can upgrade to with yarn rw upgrade that’ll give you advanced debugging information.

Yeah, I have the audience set and I can login (and grab the currentUser.email for example) when I’m not trying to hit my DB and mutate the User model with data from Auth0.

Could you share your repo with me?

I realized it’s because it’s not finding the proper key in the authToken object… I had change your example code to match my namespace (which is the key) like so (also, I’m not using Typescript here):

export const getCurrentUser = async (authToken) => {
  const email =
    authToken === null || authToken === void 0
      ? void 0
      : authToken['https://api.geocities.dev/email']

  if (!email) {
    throw new AuthenticationError('Uh oh!')
  }

  let user = await db.user.findOne({ where: { email } })
  if (!user) {
    user = await db.user.create({ data: { email } })
  }
  return user
}