Password Reset
Why password reset is essential 🔐
Users frequently forget their passwords, making a secure password reset flow a critical component of any authentication system. This guide shows how to implement password reset functionality using the Elastic Path Shopper SDK and One-Time Password Tokens.
This guide assumes you've already set up authentication following the Authentication Concepts and Quick Start guides, including properly configuring the SDK client and token handling with interceptors for both implicit and account tokens.
For a complete working example, check out the Shopper Accounts Authentication example in the Composable Frontend repository.
1 · Prerequisites for password reset
Before implementing password reset, ensure you have:
-
Account Authentication Settings: The
account_member_self_management
setting should be set toupdate_only
to allow users to reset their passwords. -
Password Profile Configuration: Set
enable_one_time_password_token
totrue
in your Password Profile. -
Webhook Setup: Create a webhook that observes the
one-time-password-token-request.created
event, which will be used to send the token to the user via email or other channels.
2 · Password reset flow overview
The standard password reset flow has three main steps:
- User requests a password reset by providing their username (typically their email)
- User receives a one-time password token via email
- User exchanges this token for an Account Management Authentication Token and sets a new password
3 · Requesting a password reset
Create a form component to capture the user's email and request a password reset token:
import React, { useState } from "react";
import { createOneTimePasswordTokenRequest } from "@epcc-sdk/sdks-shopper";
function RequestPasswordReset() {
const [username, setUsername] = useState("");
const [status, setStatus] = useState({ type: "", message: "" });
const [loading, setLoading] = useState(false);
// Get password profile ID from your configuration
const passwordProfileId = process.env.NEXT_PUBLIC_PASSWORD_PROFILE_ID;
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setStatus({ type: "", message: "" });
try {
// Request one-time password token
await createOneTimePasswordTokenRequest({
username, // The user's email or username
password_profile_id: passwordProfileId,
purpose: "reset_password",
});
setStatus({
type: "success",
message: "Check your email for instructions to reset your password.",
});
} catch (error) {
console.error("Password reset request failed:", error);
setStatus({
type: "error",
message: "Failed to request password reset. Please try again.",
});
} finally {
setLoading(false);
}
};
return (
<div className="password-reset-container">
<h2>Reset Your Password</h2>
{status.message && (
<div className={`alert alert-${status.type}`}>{status.message}</div>
)}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="username">Email Address</label>
<input
type="email"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
disabled={loading}
/>
</div>
<button type="submit" disabled={loading} className="reset-button">
{loading ? "Sending..." : "Reset Password"}
</button>
</form>
</div>
);
}
export default RequestPasswordReset;
For a complete implementation of the password reset request form, check out the ForgotPasswordForm component in the Shopper Accounts Authentication example.
4 · Confirming the password reset
Create a page component to handle the one-time password token and allow the user to set a new password:
// pages/reset-password.js (Next.js example)
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import {
createAnAccessToken,
updateUserAuthenticationPassword,
} from "@epcc-sdk/sdks-shopper";
import { useAuth } from "../context/auth-context"; // Assumed auth context for managing tokens
function ResetPassword() {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [otp, setOtp] = useState("");
const [username, setUsername] = useState("");
const [passwordProfileId, setPasswordProfileId] = useState("");
const [status, setStatus] = useState({ type: "", message: "" });
const [loading, setLoading] = useState(false);
const router = useRouter();
useEffect(() => {
// Extract parameters from URL
if (router.isReady) {
const { token, username: user, profile_id } = router.query;
if (token) setOtp(token);
if (user) setUsername(user);
if (profile_id) setPasswordProfileId(profile_id);
}
}, [router.isReady, router.query]);
const handleSubmit = async (e) => {
e.preventDefault();
// Basic validation
if (password !== confirmPassword) {
setStatus({
type: "error",
message: "Passwords do not match",
});
return;
}
if (password.length < 8) {
setStatus({
type: "error",
message: "Password must be at least 8 characters long",
});
return;
}
setLoading(true);
setStatus({ type: "", message: "" });
try {
// Get an Account Management Authentication Token using one-time password
const accountTokenResponse = await createAnAccessToken({
grant_type: "account_management_authentication_token",
client_id: process.env.NEXT_PUBLIC_CLIENT_ID,
token_type: "one_time_password_token",
one_time_password_token: otp,
username: username,
password_profile_id: passwordProfileId,
});
const accountToken =
accountTokenResponse.account_management_authentication_token;
// Update user's password
await updateUserAuthenticationPassword({
password,
password_profile_id: passwordProfileId,
username,
account_token: accountToken,
});
setStatus({
type: "success",
message: "Password has been reset successfully!",
});
// Redirect to login after a short delay
setTimeout(() => {
router.push("/login");
}, 3000);
} catch (error) {
console.error("Password reset confirmation failed:", error);
setStatus({
type: "error",
message:
"Failed to reset password. The token may be invalid or expired.",
});
} finally {
setLoading(false);
}
};
return (
<div className="reset-password-container">
<h2>Set New Password</h2>
{status.message && (
<div className={`alert alert-${status.type}`}>{status.message}</div>
)}
{otp && username && passwordProfileId ? (
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="password">New Password</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
minLength={8}
/>
</div>
<div className="form-group">
<label htmlFor="confirmPassword">Confirm Password</label>
<input
type="password"
id="confirmPassword"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
disabled={loading}
minLength={8}
/>
</div>
<button type="submit" disabled={loading} className="reset-button">
{loading ? "Resetting..." : "Reset Password"}
</button>
</form>
) : (
<div className="alert alert-error">
Missing required parameters. Please check your reset link.
</div>
)}
</div>
);
}
export default ResetPassword;
For a complete implementation of the password reset confirmation page, check out the ResetPasswordForm component in the Shopper Accounts Authentication example.
5 · Configuring the email webhook
When a user requests a password reset, an event is triggered that your webhook should handle. Your webhook implementation should:
- Extract the one-time password token and user information from the event
- Send an email to the user with a link to your reset password page
- Include the token, username, and password profile ID as parameters
Example link format:
https://your-domain.com/reset-password?token=abc123&username=user@example.com&profile_id=password_profile_123
6 · SDK functions reference
The Shopper SDK provides the following functions for password reset:
createOneTimePasswordTokenRequest
import { createOneTimePasswordTokenRequest } from "@epcc-sdk/sdks-shopper";
/**
* Request a one-time password token for reset
*/
async function requestReset(username, passwordProfileId) {
return createOneTimePasswordTokenRequest({
username,
password_profile_id: passwordProfileId,
purpose: "reset_password",
});
}
createAnAccessToken (for Account Management Authentication)
import { createAnAccessToken } from "@epcc-sdk/sdks-shopper";
/**
* Exchange a one-time password token for an Account Management Authentication Token
*/
async function getAccountManagementToken(username, passwordProfileId, token) {
return createAnAccessToken({
grant_type: "account_management_authentication_token",
client_id: process.env.CLIENT_ID,
token_type: "one_time_password_token",
one_time_password_token: token,
username: username,
password_profile_id: passwordProfileId,
});
}
updateUserAuthenticationPassword
import { updateUserAuthenticationPassword } from "@epcc-sdk/sdks-shopper";
/**
* Update a user's password
*/
async function updatePassword(
username,
passwordProfileId,
newPassword,
accountToken,
) {
return updateUserAuthenticationPassword({
password: newPassword,
password_profile_id: passwordProfileId,
username,
});
}
7 · Real-world implementation example
For a complete implementation of password reset functionality in a Next.js application, refer to the Shopper Accounts Authentication example in the Composable Frontend repository. This example includes:
- Proper authentication state management
- Form validation
- Error handling
- Integration with the Elastic Path Shopper SDK
- Complete password reset flow
Key components to review:
- ForgotPasswordForm - Initiates the password reset process
- ResetPasswordForm - Allows users to set a new password
- Authentication utility functions that handle token management
8 · Security considerations
- Token expiration: One-time password tokens expire after 10 minutes
- Rate limiting: Implement rate limiting for password reset requests to prevent abuse
- Secure emails: Ensure password reset emails don't contain the actual password
- Notification: Consider notifying users via alternative channels when password changes occur
- Password strength: Enforce strong password policies during the reset process
- HTTPS: Always use HTTPS for your reset password pages
9 · Error handling
Common errors to handle include:
- Invalid or expired tokens
- User not found
- Password doesn't meet requirements
- Network failures
try {
// Get account management token using one-time password token
const tokenResponse = await createAnAccessToken({
grant_type: "account_management_authentication_token",
client_id: process.env.CLIENT_ID,
token_type: "one_time_password_token",
one_time_password_token: token,
username: username,
password_profile_id: passwordProfileId,
});
// Success handling
} catch (error) {
if (error.status === 404) {
// User not found or token invalid
showError(
"Invalid or expired reset link. Please request a new password reset.",
);
} else if (error.status === 422) {
// Validation error
showError("Invalid information provided. Please try again.");
} else if (error.status === 401) {
// Invalid token
showError("Invalid or expired token. Please request a new password reset.");
} else {
// Generic error
showError("An error occurred. Please try again later.");
}
}
For complete implementations, check out the authentication examples in the Composable Frontend repository, specifically the Shopper Accounts Authentication example.