Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auth0: Create user in local database after Auth0 sign up

Tags:

auth0

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.

like image 826
jeanpaul62 Avatar asked Aug 10 '18 23:08

jeanpaul62


People also ask

Do you need a database for Auth0?

Auth0 stores user information for your tenant in a hosted cloud database, or you can choose to store user data in your own custom external database. To store user data beyond the basic information Auth0 uses for authentication, you can use the Auth0 data store or a custom database.

How do I use Auth0 database?

Navigate to Auth0 Dashboard > Authentication > Database, and select Create DB Connection. Enter a name for your connection, and select Create. Select the Applications view, enable the switch for each Auth0 application that should be able to use this connection.

How does Auth0 store passwords?

Auth0 helps you prevent critical identity data from falling into the wrong hands. We never store passwords in cleartext. Passwords are always hashed and salted using bcrypt. Additionally, data at rest and in motion is always encrypted by using TLS with at least 128-bit AES encryption.

What is Auth0 implementation?

Auth0 is a flexible, drop-in solution to add authentication and authorization services to your applications. Your team and organization can avoid the cost, time, and risk that come with building your own solution to authenticate and authorize users.


1 Answers

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.

like image 177
iqbal125 Avatar answered Sep 21 '22 00:09

iqbal125