This is a relatively basic (but complete) implementation of a React Single Page Application with User Authentication.
- React for the SPA (single-page application) framework
- Boostrap (react-bootstrap) for the visual styling
- Integration with Auth0 for user creation and authentication
- auth0-spa-js which is Auth0's authentication framework for SPA with PKCE
- Auth0 integration uses Authorization Code grant with PKCE for Single Page Apps
- Additional pop-up modal by me to display errors from Auth0 (e.g. unverified email account)
- Additional error handling by me (e.g. try/catch for 401's from Auth0)
This started as the 3rd part of my 2nd attempt at using Auth0 for user authentication in a React app. The goals of this POC was to use my existing Auth0 configuration, with the addition of manual admin approval required for new accounts. I essentially followed this suggestion: https://community.auth0.com/t/how-to-prevent-logging-in-until-admin-approval/9340/2
I built this POC using:
- React
- Bootstrap (via react-bootstrap, not reactstrap)
- Auth0 (via @auth0/auth0-spa-js)
To start, I copied this repo: https://github.com/cburkins/poc-react-reactBootstrap-auth0
From there, did the following:
- Added popup bootstrap modal to share error message from Auth0 (e.g. "Your registration must be approved by an administrator")
- Added "useEffect()" to sense the error within the callback URL params
- Added 2nd "useEffect()" that waits for the modal to be dismissed, waits for Auth0 lib to be loaded, then forces a logout (so the user doesn't use cached cookie for next login attempt)
- Clone this repo
- Install all dependencies via
npm install
- Create Auth0 application (via their portal)
Name:
can be anything you'd like, just a convenience thing- Application Type should be "Single Page Web Application"
Allowed Callback URLs
, addhttp://localhost:3000
Allowed Logout URLs
, addhttp://localhost:3000
Allowed Web Origins
, addhttp://localhost:3000
- Create a file called
- Copy "client_id" and "domain" into
src/auth_config.json
, should look like this:{ "domain": "dev-8snzgxfi.auth0.com", "clientId": "5XF0vALgtkN6t1Z3KJTChqyaCQiQ94Tm" }
- Start app locally on port 3000 via
npm start
- Click "Login" and create new user
- Click "Login" and confirm success (ie.g. "Status: Authenticated" in app header)
- Optional: set "Application Type:" to "Single Page Application" which removes the option of using grant type of "Passwordless OTP"
-
Go to Auth0 Portal
-
Click on "Auth Pipeline->Rules"
-
Click on "Create Rule", then "Force email verification"
NOTE: This is pre-configured rule from Auth0, but for reference, it looks like this:
function (user, context, callback) { if (!user.email_verified) { return callback(new UnauthorizedError('Please verify your email before logging in.')); } else { return callback(null, user, context); } }
-
Confirm that Login/Logout fails and displays error Modal now (assuming your email address is unverified)
-
Check your email address to approve email address
-
Confirm that Login/Lougout works now
-
Go back to Auth0 Portal/Rules
-
Click on "Create Rule"
-
Click on "Empty Rule"
-
Name: Allow only approved users
-
Script: (see below)
function (user, context, callback) { if (user.app_metadata && user.app_metadata.approved) { // user has been approved return callback(null, user, context); } // anything else is false (user not approved) return callback(new UnauthorizedError('Your account must be approved by an administrator.')); }
-
Verify that Login/Logout is failing again (with modal popup)
-
Approve User via Auth0 Portal
- Edit user in Portal
- Within "app_metadata":
{ "approved": true }
-
Verify that Login/Logout works again
NOTE: This was an Auth0 defect that I discovered in June 2020, and was fixed in Oct 2020, and therefore is no longer relevant (removed from directions above)
Defect Description: User login works fine, receiving "code" from login works fine, the problem is when you try to send in your "code" to /oath/token to receive a "token" in return. For some strange reason, you get a 401/Unauthorized
This was the shortest way to work around the bug I discovered in Auth0's configuration, which I reported here: https://community.auth0.com/t/401-unauthorized-when-obtaining-token-in-authorization-code-grant/44685/2
- Create new Auth0 tenant (which creates "Default App")
- Within default app, fill in "http://localhost:3000" to the following:
- "Callback URL", "Allowed Origins", "Logout URL"
- Change "Application Type" to "Regular Web Application"
- Change "Token Endpoint Authentication" to "None"
- Confirm modal "... will disable the Client Credentials grant for.."
- Save
This was the long version for working around the same defect as above. This is superflous, simply documentation of the original journey I took to discover the correct way to configure my Auth0 Application for SPA using PKCE.
- Create new Auth0 tenant (which creates "Default App")
- Within default app, fill in "http://localhost:3000" to the following:
- "Callback URL", "Allowed Origins", "Logout URL"
- Veryify default settings and test login within SPA:
- Application Type: is set to "Select an application type"
- Token Endpoint Authentication Method: field is inactive (greyed out) and set to "Post"
- Advanced Setting->Grant Types: The following are checked (and unable to be changed): Implicit, Authorization Code, Refresh Token, Client Credentials
- When using "Authorization Code" flow within a SPA using Auth0 Quickstart example
- new user: User creation works fine
- code: Obtaining "code" works fine
- token failure: When you exchange "code" for "token" via /oauth/token endpoint, you get a 401/Unauthorized
- SPA: Changing "Application Type" from "Select an application type" to "Single Page Application"
- "Token Endpoint Authentication Method" remains grayed out, and set to "Post"
- Within Advanced Setting->Grant Types:
- "Client Credentials" is now grayed out, but still checked.
- Implicit, Authorization Code, and Refresh Token remain checked.
- code: Obtaining "code" works fine
- token failure: When you exchange "code" for "token" via /oauth/token endpoint, you get a 401/Unauthorized
- Native: Changing "Application Type" from "Single Page Application" to "Native"
- "Token Endpoint Authentication Method" remains grayed out, and set to "Post"
- Within Advanced Setting->Grant Types:
- "Client Credentials" is now grayed out, but still checked.
- Implicit, Authorization Code, and Refresh Token remain checked.
- code: Obtaining "code" works fine
- token failure: When you exchange "code" for "token" via /oauth/token endpoint, you get a 401/Unauthorized
- Regular Web Application: Changing "Application Type" from "Native" to "Regular Web Application"
- "Token Endpoint Authentication Method" field is now active (not greyed out), and set to "Post"
- Advanced Settings->Grant Types: Implicit, Authorization Code, Refresh Token, and Client Credentials are checked
- code: Obtaining "code" works fine
- token failure: When you exchange "code" for "token" via /oauth/token endpoint, you get a 401/Unauthorized
- SPA: Changing "Application Type" from "Regular Web Application" to "Single Page Application"
- Receive modal saying "Changing the Application Type from Regular Web Application to Single Page Application will disable the Client Credentials grant for this application"
- Clicked on "confirm"
- "Token Endpoint Authentication Method" is now grayed out, and set to "None"
- Advanced Settings->Grant Types:
- Implicit, Authorization Code, and Refresh Token are now checked.
- Client Credentials is grayed out, and not checked
- Still successfully get "code" when you login
- Now get a 200/OK when exchanging code for token at /oauth/token