29
votes

I am using Auth0 to host all my user data. I also have my own backend, and I wish to have a Users table in it, which will map my db's generated userId to Auth0's user_id. I am hesitating between two flows on sign-up:

Sign-up flow 1:

  1. Frontend shows the Lock, user signs up.
  2. After Auth0 redirects back to the frontend, frontend has the Auth0 user_id.
  3. Frontend makes a call to backend on POST /users (public endpoint) to create a new user with user_id.
  4. On each authenticated request to my backend resources server, the JWT contains the auth0 user_id, so the db makes a lookup between the user_id and my userId.

Sign-up flow 2:

  1. Frontend shows the Lock, user signs up.
  2. Configure a post-registration hook on Auth0 which calls POST /users on my backend. This call will generate my db's userId and send it back to Auth0.
  3. Put this userId into Auth0's user_metadata.
  4. This user_metadata will be included in the JWT, so that all calls to my backend to fetch resources will include the db's userId (no need for additional lookup).

I feel 2 is more solid. Are there other sign-up flows? Do some auth0 customers use a similar flow to my #2? I didn't find much in their documentation.

2
FWIW, you should use app_metadata, not user_metadata to store the userId. user_metadata is intended for data the user can change.pondermatic
amaurym did you settle on a solution to this, flow 2 sounds way more sane to me but as @ron-klein pointed out in his answer, how do you handle consistency issues?riscarrott

2 Answers

17
votes

This is my first post, so excuse me if I make any newbie mistakes.

I found Sign-up flow 1 to work quite well. You didnt specify which technologes you were using, but here is a link to my github where I have a fully functioning blog using Sign-up flow 1 with React, redux and an Express backend.

https://github.com/iqbal125/react-redux-fullstack-blog

I will demonstrate with these frameworks so hopefully you can adjust your code for which ever framework(s) you are using.

My signup process is as follows:

  1. Frontend shows the Lock user signs up
  2. User is redirected to callback page.
  3. I then redirect from the callback page to an "auth-check" page. I have a nested api call in auth-check page that both gets the user data from auth0 then immediately calls api endpoint to save user data to db.
  4. The api call checks if user is already in sql db then saves the user data, otherwise does nothing.
  5. The user data is then saved to redux global state and can be used to display data on the user profile page.
  6. When user clicks logout, authcheck is called again and user info is removed from global state and then user is logged out.
  7. auth-check then redirects back to homepage.



1. Frontend shows the Lock user signs up

  login() {
    this.auth0.authorize();
   }


2. User is redirected to callback page.

My callback page is very simple and I it use as a functional component.

  <div>
    <h2>Callback</h2>
  </div>


3. I then redirect from the callback page to an "auth-check" page

I do this through the handleAuthentication() function in my auth.js util component. The code is slightly modified from the auth0 samples.

  handleAuthentication() {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.setSession(authResult);
        this.getProfile();
        setTimeout( function() { history.replace('/authcheck') }, 2000);
      } else if (err) {
        history.replace('/');
        console.log(err);
        alert(`Error: ${err.error}. Check the console for further details.`);
      }
    });
   }

You will notice I added a getProfile() function

   getProfile() {
    let accessToken = this.getAccessToken();
    if(accessToken) {
      this.auth0.client.userInfo(accessToken, (err, profile) => {
        if (profile) {
          this.userProfile = { profile };
         }
       });
     }
    }

along with a getAccessToken() function

  getAccessToken() {
    if (localStorage.getItem('access_token')) {
      const accessToken = localStorage.getItem('access_token')
      return accessToken
    }
    else {
      console.log("No accessToken")
      return null
    }
   }

These two functions in the auth.js util component will be what allow us to get the info from auth0 and save it to an empty object we declared in the class.

  userProfile = {}

Moving on to the auth-check.js container. I start by declaring the function in the constructor and followed by the function itself. Then I call the componentDidMount() lifecycle method which runs automatically when the component renders.

  constructor() {
    super()
    this.send_profile_to_db = this.send_profile_to_db.bind(this)
  }

   send_profile_to_db (profile) {
    const data = profile
    axios.post('api/post/userprofiletodb', data)
    .then(() => axios.get('api/get/userprofilefromdb', {params: {email: profile.profile.email}} )
      .then(res => this.props.db_profile_success(res.data))
      .then(history.replace('/')))
   }

My lifecycle method and Im returning an empty div.

componentDidMount() {
    if(this.props.auth.isAuthenticated()) {
      this.props.login_success()
      this.props.db_profile_success(this.props.auth.userProfile)
      this.send_profile_to_db(this.props.auth.userProfile)
    } else {
      this.props.login_failure()
      this.props.profile_failure()
      this.props.db_profile_failure()
      history.replace('/')
    }
  }

   render() {
    return (
        <div>
       </div>
    )
  }
}

I think this code right here gets to the heart of the question you asked.

I will start with the send_profile_to_db() function.

Here im using axios to make requests. I start my making a backend api call to my express server(I will explain in the next step) and Im passing the user profile as a data object parameter with axios. You might be wondering where the actual user profile data is coming from.

In my routes.js root component I imported and initialized a new instance of Auth

export const auth = new Auth();

then passed it as a prop to the AuthCheck component.

<Route path="/authcheck" render={(props) => <AuthCheck auth={auth} {...props} />} />

This allows me to access all the properties of the auth class with "this.props". So I simply use the "userProfile = {}" object that we initialized in the last step that now contains our user data.

After posting the data to the database Im using a nested ".then()" function that calls an axios get request with the users email as a parameter for looking up the profile from the database. The database profile contains data about the user's posts and user's comments. Which will be useful for the displaying data in the app. Then Im using another ".then()" statement and a Redux Thunk to save the user profile data to the global redux state asynchronously.

So in sum this authcheck component is doing 4 things:
1. Saving the user profile data we get from auth0 to our own database.
2. Then after saving the data, immediately retrieving the same profile from our database.
3. Making our app aware whether the user is authenticated or not.
4. Saving our database user profile data to the global redux state for use in other components.

Pretty awesome, if you ask me!



4. The api call checks if user is already in sql db then saves the user data, otherwise does nothing.

Now here is my server set up. For the user to database "post" and "get" requests.

router.post('/api/post/userprofiletodb', (req, res, next) => {
  const values = [req.body.profile.nickname, req.body.profile.email, req.body.profile.email_verified]
  pool.query('INSERT INTO users(username, email, date_created, email_verified) VALUES($1, $2, NOW(), $3) ON CONFLICT DO NOTHING', values, (q_err, q_res) => {
    if (q_err) return next(q_err);
    console.log(q_res)
    res.json(q_res.rows);
  });
});

/* Retrieve user profile from db */
router.get('/api/get/userprofilefromdb', (req, res, next) => {
  // const email = [ "%" + req.query.email + "%"]
  const email = String(req.query.email)
  pool.query("SELECT * FROM users WHERE email = $1", [ email ], (q_err, q_res) => {
    res.json(q_res.rows)
  });
});

A few things to note:

the router object is express.router(). Im using psql.

Remember to add "ON CONFLICT DO NOTHING" otherwise you will save multiple versions of the same user.

I think there is a couple more data points that auth0 gives you but I ended up not using them.

Here is my SQL schema for users table.

CREATE TABLE users (
  uid SERIAL PRIMARY KEY,
  username VARCHAR(255) UNIQUE,
  email VARCHAR(255),
  email_verified BOOLEAN,
  date_created DATE,
  last_login DATE
);



5. The user data is then saved to redux global state and can be used to display data on the user profile page.

I ended up just explaining this in step 3.



6. When user clicks logout, authcheck is called again and user info is removed from global state and then user is logged out.

see step 3



7. auth-check then redirects back to homepage.

Once again see step 3 lol.


Be sure to check out my repo if youre interested or if I missed anything, like I said its a complete fully functioning blog.

3
votes

Flow 1 would do an "insert-or-ignore" command against your db on every successful login, and on a large scale this is not a good practice. As a matter of fact, it's also a potential point of DoS, since this endpoint directly exposes the db.

Therefore Flow 2 seems to me like a better choice, as it will sync between your db and Auth0's db only once. The only challenge here is to handle a failure on the post-signup process.

Note also, as suggested in a comment, that app_metadata is a more suitable place to store this kind of information.