In this project, we'll use a service called Auth0 to do the heavy lifting of auth for us, including allowing for social integrations (github, google, etc). We'll build a site that looks similar to the auth-bcrypt-mini project but does not store any sensitive data in our tables.
Fork
andclone
this repository.cd
into the project directory.- Run
yarn
. You know to use yarn instead of npm because there is ayarn.lock
file. - Create a Postgres database. Use this project's
db/init.sql
file to create the schema. - Copy the
env.example
file to a new file called.env
and paste in the connection string to the postgres database. Later, we'll fill out your Auth0 domain and client ID. - Start the server with
nodemon
. - Start the web dev server with
npm start
. In your browser, openhttp://localhost:3000
.
In this step, we'll go to manage.auth0.com
to create an account. We'll create a 'client' that represents this particular app.
- Go to
manage.auth0.com
. - Register for an account.
- Set the account type to
Personal
. - Set the role to
Developer
. - Set the project to
Just playing around
.
- Set the account type to
- Log in to your Auth0 account.
- Go to
Clients
using the left navigation bar. - Click the
Create Client
button in the top right.- Pick a name (recommendation:
auth0-lock-mini
) - Change the
Client Type
toSingle Page Web Applications
. - Click Create.
- Switch to the client's
Settings
tab. - Change the
Allowed Callback URLs
tohttp://localhost:3000
. - Scroll toward the bottom and click the
Show Advanced Settings
, and switch to the OAuth tab. Change theOIDC Conformant
toggle to off.
- Pick a name (recommendation:
- Click
Save Changes
. - Back at the top of the settings, copy the domain and client ID to your project's
.env
file. Be sure to restart your React web dev server to use these values. - On the Auth0 website, click on Connections on the left hand side navigation, and then Social.
- Enable the Github connection.
- Then click the Github connection to configure it. In the Attributes section, check Email address. Click Save.
- Click on APIs on the left hand side navigation. Click Create API. Set the friendly name to
management
, and set the identifier tomanagement
too. Click Create.- In the Settings tab, set the Token Expiration to 7776000 (90 days). Click Save.
- In the Scopes tab, add a scope for
read:users
with a description ofRead Users
, and a scope forread:user_idp_tokens
with a description ofRead Users IDP tokens
. - On the Non Interactive Clients tab, you'll notice that one of the clients in the list was created for you, and was named
management (Test Client)
. Ensure it's Authorized. - Down in the Response section, copy the access token to the
.env
file as the AUTH0_MANAGEMENT_ACCESS_TOKEN.
In this step, we'll add the Auth0 Lock library to the React website and initialize it.
- First add the
auth0-lock
library withyarn add auth0-lock
. - Open the
src/App.js
file. - In the
constructor
:- set state for
user
andsecureDataResponse
tonull
. - Set a member property named
lock
tonull
.
- set state for
- Add a
componentDidMount()
method:- Set
this.lock
to equalnew Auth0Lock()
, and pass in your client ID and domain, using the environment variables fromprocess.env.<ENV VAR NAME HERE>
. - Add a handler to Lock's
authenticated
event usingthis.lock.on(...)
. The handler should be a prototype method namedonAuthenticated
. We'll fill out that method in the next step. - Make an axios GET request to
/user-data
. Use the response data to set the React user state.- If there is no response data, set the React user state to
null
. By setting the state to null instead ofundefined
, the displayed user state will show as the word "null" instead of blank.
- If there is no response data, set the React user state to
- Set
App.js
import Auth0Lock from 'auth0-lock';
class App extends Component {
constructor() {
super();
this.state = {
user: null,
secureDataResponse: null
};
this.lock = null;
this.logout = this.logout.bind(this);
this.fetchSecureData = this.fetchSecureData.bind(this);
}
componentDidMount() {
this.lock = new Auth0Lock(process.env.REACT_APP_AUTH0_CLIENT_ID, process.env.REACT_APP_AUTH0_DOMAIN);
this.lock.on('authenticated', this.onAuthenticated);
axios.get('/user-data').then(response => {
this.setState({ user: response.data.user || null });
});
}
In this step, we'll add a handler for when someone clicks the Log in button, and handle when authentication has happened on the client side.
- Open the
src/App.js
file. - Bind the
onAuthenticated
prototype method in the constructor. - In the previous step, we set up the handler for
this.lock.on('authenticated')
. Create that method.- It should take in a variable named
authResult
. - Make a call to
this.lock.getUserInfo()
, passing in theaccessToken
value fromauthResult
, and an arrow function callback that takes anerror
parameter and auser
parameter.- In the callback, if there is an error, be sure to log it to the console for your debugging convenience.
- Otherwise, make an axios POST to
/login
. Send as the body an object with a property nameduserId
. The value should be thesub
fromuser
. Take theuser
value from the response data and set it as the Reactuser
state.
- It should take in a variable named
- Add a
login
prototype method.- Show the Lock screen with
this.lock.show()
.
- Show the Lock screen with
App.js
class App extends Component {
constructor() {
// ...
this.onAuthenticated = this.onAuthenticated.bind(this);
}
// ...
onAuthenticated(authResult) {
this.lock.getUserInfo(authResult.accessToken, (error, user) => {
if (error) {
console.error(error);
return;
}
axios.post('/login', { idToken: authResult.idToken} ).then(response => {
this.setState({ user: response.data.user });
});
});
};
login() {
this.lock.show();
};
In this step, we'll handle the authentication on the server side.
- Open the
server/index.js
file. - Navigate to the code for the
/login
endpoint. - Destructure the
userId
value from the request body. - Construct a variable named
auth0Url
that looks like this:https://<YOUR AUTH0 DOMAIN>/api/v2/users/{userId}
. Useprocess.env.<ENV VARIABLE NAME HERE>
to get the domain. - Make an axios GET to that URL. You'll need to set the Authorization header to equal
Bearer ${X}
, where X is the Auth0 management access token environment variable.- Put a
.catch()
on the end of that call. If there's an error, it likely represents an invalid login. Simply return a 500 with a message. - The response data will include a property called
user_id
. Use that anddb/find_user_by_auth0_id.sql
to look up the user in the database. - If a user is found, create a
user
object on the session that is that user object from the database, and send back a response with that user in a property calleduser
. - If the user is not found, it means they have never logged in before. This is conceptually a "register" situation. Use the
user_id
andemail
field from the response to create a user record. Thedb/create_user.sql
file will be helpful for this.- After the record has been created, put the user object on the session in a property named
user
, and send back a response with that user in a property calleduser
.
- After the record has been created, put the user object on the session in a property named
- Put a
server/index.js
app.post('/login', (req, res) => {
const { userId } = req.body;
const auth0Url = `https://${process.env.REACT_APP_AUTH0_DOMAIN}/api/v2/users/${userId}`;
axios.get(auth0Url, { headers: { Authorization: 'Bearer ' + process.env.AUTH0_MANAGEMENT_ACCESS_TOKEN } }).then(response => {
const userData = response.data;
app.get('db').find_user_by_auth0_id(userData.user_id).then(users => {
if (users.length) {
req.session.user = users[0];
res.json({ user: req.session.user });
} else {
app.get('db').create_user([userData.user_id, userData.email]).then((newUsers) => {
req.session.user = newUsers[0];
res.json({ user: req.session.user });
})
}
})
}).catch(error => {
console.log('error A', error);
res.status(500).json({ message: "An error occurred; for security reasons it can't be disclosed" });
});
});
If you see a problem or a typo, please fork, make the necessary changes, and create a pull request so we can review your changes and merge them into the master repo and branch.
© DevMountain LLC, 2018. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.