Enabling Users to Change their Email Address from a SPA or Native App

Last Updated: Aug 8, 2024

Overview

This article provides details on how to enable users to change their email address from a SPA or native app.

Applies To

  • SPA
  • Native App
  • Email address

Solution

Public clients (like SPAs and native applications) are restricted from requesting tokens for the management API with the proper scopes for updating a user’s email. These types of applications are limited to a few specific scopes. Because of these limitations, email change requests from public clients must flow through a backend or proxy API. This could be a serverless function with the sole purpose of handling these requests or an added endpoint on an existing backend API.

In this example, we will use an existing backend API to process the email change on request from a SPA/native app. To set up a backend API, take a look at backend/API quickstarts.

The following diagram describes the desired flow:

Refer to the following steps:

  1. The user visits the SPA/native app with the intent to log in and change the email address.
  2. The SPA/native app makes a request to authorize the user and get an access token for the backend API. The backend API should be set up and registered with Auth0 37 and the request to authorize should include a audience={YOUR_API_IDENTIFIER} parameter.
  3. The SPA/native app makes an email change request to the backend API on behalf of the user, sending the access token in the header.
  4. The backend API verifies the token, and if the backend API doesn’t have an existing, valid management API token, it requests a management API token. Verify the backend is not requesting a new token for every email change.
  5. Using the user_id from the original user access token and the management API token in the header, the backend makes a request to /api/v2/users/{id} with the updated email.

The following code utilizes auth0-node and is a rudimentary example building off of auth0-react-samples:

const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const helmet = require("helmet");
const { auth } = require("express-oauth2-jwt-bearer");
const { ManagementClient } = require("auth0");
const authConfig = require("./src/auth_config.json");
require('dotenv').config();

const app = express();

const port = process.env.API_PORT || 3001;
const appPort = process.env.SERVER_PORT || 3000;
const appOrigin = authConfig.appOrigin || `http://localhost:${appPort}`;

if (!authConfig.domain || !authConfig.audience || authConfig.audience === "YOUR_API_IDENTIFIER") {
  console.error("Exiting: Please make sure that auth_config.json is in place and populated with valid domain and audience values");
  process.exit(1);
}

app.use(morgan("dev"));
app.use(helmet());
app.use(cors({ origin: appOrigin }));
app.use(express.json());

const auth0 = new ManagementClient({
  domain: process.env.DOMAIN,
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  audience: process.env.MGMNT_AUDIENCE,
  scope: 'read:users update:users',
});

const checkJwt = auth({
  audience: authConfig.audience,
  issuerBaseURL: `https://${authConfig.domain}/`,
  algorithms: ["RS256"],
});

app.get("/api/external", checkJwt, (req, res) => {
  res.send({ msg: "Your access token was successfully validated!" });
});

app.post("/api/external/email", checkJwt, (req, res) => {
  const data = req.body;

  auth0.users.update({ id: req.auth.payload.sub }, data, (err, user) => {
    if (err) {
      console.error(err);
      return res.status(500).send({ msg: "Unable to set new email - Check logs for possible errors" });
    }
    res.send({ msg: `Your new email ${user.email} has been set successfully` });
  });
});

app.listen(port, () => console.log(`API Server listening on port ${port}`));

NOTE:

  • If the user enters an email address that they don’t have access to (e.g. they made a typo when inputting the email), they will no longer be able to complete the forgot password workflow. It is recommended to notify the user and/or ask them to confirm their address before submitting the update.
  • If the user enters an email that already exists the management API will return an error that should be communicated to the user.
  • The same pattern works to update other user profile attributes as well.
5 Likes