3.E.6: Carousell Auth
Learning Objectives
Know how to implement industry-standard authentication in an app with a SQL backend
Introduction
We will implement authentication in a simple Carousell clone that allows users to login, view, list and buy items. Users can browse items without logging in, but will need to login to list or buy items.
Setup
Fork and clone repos
Fork and clone Rocket Academy's Carousell Frontend and Carousell Backend repos.
Rocket has set up starter code such that users can:
View listings from the home page
List new items by clicking on "Sell" from the home page
Buy items by clicking "Buy" at the bottom of item-specific pages
The starter code follows the structure of our Bigfoot frontend and backend. Rocket followed backend setup instructions in Bigfoot SQL to set up the backend.
Setup frontend
Run npm i
to install packages
Setup backend
Run
npm i
to install packagesCreate and update a .env to pass crendials to
config/database.js
Model the sample that is part of the repo, feel free to remove the sample when finished.Run
npx sequelize db:create
to create thecarousell_development
databaseRun
npx sequelize db:migrate
to set up database schemaRun
npx sequelize db:seed:all
to seed users and listings in the databaseVerify seed data was added by viewing the
users
andlistings
tables in our SQL client
Verify starter code is working
Start the backend with
npm start
Start the frontend with
npm run dev
, and then open a browser of your choice and navigate to "http://localhost:5173".Verify we do not get errors in our backend or frontend, and we can see 3 seed listings at "http://localhost:5173" in our browser. Click on a listing to view its details. Buy and sell listings to observe what happens, and notice that the buyer and seller IDs have been hard-coded to 1 (the seed user).
Base: Require authentication to create listings and buy items
We will now put the theory we learnt in Rocket's Authentication submodule into practice.
Setup backend API authorisation
Refer to the following official Auth0 Express Authorization guide as a reference.
Setup Auth0 API in Auth0 dashboard
Navigate to the API section of the Auth0 dashboard (under Applications in the left navbar) and click Create API
Choose a name and identifier for our API. Rocket named ours Carousell API and used
https://carousell/api
as our identifier. Leave the signing algorithm as RS256.After creating the API we should see a Quick Start page. Ignore sample code on that page because it uses outdated libraries. We will be following setup instructions in the official Auth0 guide instead, which uses a new library that replaced the outdated libraries.
Back in the official Auth0 guide, skip the section on defining permissions for now. By default all authenticated users will be able to list and buy items in our app.
Install Auth0 library in our backend and use Auth0 middleware to verify authentication on protected routes
Install
express-oauth2-jwt-bearer
, Auth0's new, simplified library for verifying authenticationImport the
auth
property ofexpress-oauth2-jwt-bearer
in our app like in the code example in the Auth0 guide. Note that Rocket's app setup requiresimport
syntax instead ofrequire
syntax. Copy thecheckJwt
variable definition and replaceYOUR_API_IDENTIFIER
with the API identifier we chose above.Add
checkJwt
as a middleware between the route path and route handler function for our create-new-listing and buy-listing routes. See sample code under Protect API Endpoints section of Auth0 guide for reference.checkJwt
will validate authentication before Express runs our route handler functions.No need to check scopes because we are not using scopes for now.
Testing the security of our API independently is more work than it's worth right now, so we will move on and test our API together with our frontend!
Login from frontend
We will follow the following official Auth0 guide for React apps.
Setup Auth0 application in Auth0 dashboard
Create a new Auth0 application in the Auth0 dashboard. Give it the name "Carousell" and choose the Single Page Web Application application type.
Add
http://localhost:5173
to Allowed Callback URLs, Allowed Logout URLs and Allowed Web Origins in the new application's Application Settings page. Save changes at bottom of page. If we deploy our app later, we will also need to add the deployed URL to these sections.
Install Auth0 React library and configure Auth0 resources to be available in our app
Install Auth0 React SDK with
npm i @auth0/auth0-react
Configure the
Auth0Provider
higher-order component that wraps all other components inindex.js
. If we are logged in when viewing the Auth0 guide, we can choose the relevant Auth0 Application and Auth0 will auto-populate the properties we need forAuth0Provider
in the guide for us to copy.
Retrieve Auth0 resources from React context and use them to login in our app
We want our users to be able to view all listings and individual listings without logging in. We only want them to login when they have to, in our case when they wish to list or buy items.
We will use Auth0's loginWithRedirect
function in 2 places: when unauthenticated users click "Sell" to list items and when unauthenticated users click "Buy" to buy items.
Add a
useEffect
hook inNewListingForm
component that callsloginWithRedirect
if the current user is not authenticated. We can check authentication status withisAuthenticated
boolean property returned byuseAuth0
hookRetrieve the logged-in user's email with the
user
object property returned byuseAuth0
hook and send that email as the seller's email to our backend inhandleSubmit
with the other listing data.Update the relevant method within the controller attached to the routing middleware in our backend to find or create the seller user in our backend before creating a new listing associated with that seller. Use the seller's email to retrieve their user ID in our backend. You may find Sequelize's
findOrCreate
class method helpful for finding a user with a given email, or creating a new user if no user exists with that email.
Add logic in
handleClick
in theListing
component tologinWithRedirect
a user if they are not yet authenticated. This will allow users to view items without authentication, but force them to authenticate before buying an item.Retrieve the logged-in user's email with the
user
object property returned byuseAuth0
hook and send that email as the buyer's email to our backend inhandleClick
.Update the relevant route controller in our backend listingController to find or create the buyer user in our backend before updating the listing with the specified buyer's ID. Use the buyer's email to retrieve their user ID in our backend. You may find Sequelize's
findOrCreate
class method helpful.
Even though our users have logged in before selling or buying, our API server will still return 401 errors on sell or buy requests because we have not yet updated our requests to include auth information.
We are now ready to update our "sell" and "buy" API calls to include our auth tokens!
Send auth tokens from frontend
We will follow the following official Auth0 API-calling guide for React apps.
Setup frontend to retrieve auth tokens
Add
audience
andscope
props toAuth0Provider
Component inindex.jsx
in our frontend. Theaudience
value should be the API identifier that we used to initialisecheckJwt
in our backend above. This may be different from theaudience
value in the Auth0 docs, because the Auth0 docs reference the Auth0 management API, not our custom API for this app.
Update sell and buy functionality on frontend to retrieve and send access token with API requests
Retrieve
getAccessTokenSilently
function fromuseAuth0
hook inNewListingForm
andListing
components to send the access token in sell and buy API requests respectivelyIn
NewListingForm
component, in thehandleSubmit
function that runs on form submit, callgetAccessTokenSilently
to retrieve the access token, before passing the access token in a request header to the API call with Axios. See this tutorial and Axios docs on request method aliases and configs for how to set request headers in Axios requests.The
audience
parameter value should be the API identifier that we used to initialisecheckJwt
in our backend above.We will need to pass the access token as an Authorization header whose value starts with "Bearer ", followed by the access token. Sample code below.
Do the same on
handleClick
for buy functionality in theListing
component. When the user clicks "buy", we should get the access token and send it with the API request to buy the listing.
Sample code from Rocket's handleSubmit
function in NewListingForm
component.
Verify backend can authenticate with access tokens
Run backend and frontend servers locally
List a new item on our frontend and verify that the listing gets created with the seller ID of the current user
Buy the newly-created listing on our frontend and verify that the listing now shows the buyer ID of the current user
Congratulations! We now have an app that allows us to buy and sell items with authenticated users!
Submission
Submit pull requests to the main
branches of Rocket's Carousell Frontend and Carousell Backend repos respectively, and share your PR links in your section Slack channel.
If you would like to deploy, follow deployment instructions in Bigfoot SQL M-M.
Reference Solution
Here is reference code for the frontend and the backend for this exercise. You can do better!
Last updated