# Password reset flow To manage forgotten passwords, implement a secure password reset flow. The password reset flow enables Customers to regain access to their accounts without compromising security. In this section, you'll learn how to implement a two-step password reset flow by using commercetools. For the canonical flow and endpoint matrix for global and Store-specific customers, see [Customer password reset](/api/customers-overview.md#customer-password-reset). ## Request a password reset token When a Customer indicates that they've forgotten their password, your application initiates the reset process by requesting a unique, temporary token from commercetools. The reset token is then sent to the Customer's registered email address. - **Endpoint**: [Create password reset token for Customer](/search.md?urn=ctp:api:endpoint:/{projectKey}/customers/password-token:POST) (`POST /{projectKey}/customers/password-token`). - To request a password reset token for a Customer in a Store, use the [Create password reset token for Customer in Store](/search.md?urn=ctp:api:endpoint:/{projectKey}/in-store/key={storeKey}/customers/password-token:POST) endpoint. - **Request**: send the Customer email address (`email` field) in a [CustomerCreatePasswordResetToken](/search.md?urn=ctp:api:type:CustomerCreatePasswordResetToken) payload. Optionally set `ttlMinutes` and `invalidateOlderTokens` when your flow needs a shorter token lifetime or must invalidate previously issued tokens. For security reasons, the flow doesn't reveal whether the email exists. - **Response**: returns a [CustomerToken](/search.md?urn=ctp:api:type:CustomerToken) containing a `value` that's the password reset token. - **Action**: after receiving the token, your backend sends it to the Customer's verified email address within a password reset link. For example, `https://www.example.com/reset-password?token=TOKEN_VALUE`. ### Example - Generate a password reset token ```ts import { apiRoot } from '../../client'; // Assuming apiRoot is initialized and exported here import { CustomerToken } from '@commercetools/platform-sdk'; // Import the type for the token response // Function to request a password reset token async function requestPasswordResetToken(email: string): Promise { // Returns the token string try { const tokenResponse = await apiRoot .customers() .passwordToken() .post({ body: { email: email, ttlMinutes: 60, // Token valid for 60 minutes (1 hour) invalidateOlderTokens: true, // Invalidate all password tokens previously issued for this Customer }, }) .execute(); const customerToken: CustomerToken = tokenResponse.body; console.log( 'Password reset token generated (but not sent to console for security):' ); // For demonstration, we'll log, but in production, NEVER log the token directly. if (process.env.NODE_ENV !== 'production') { console.log('Token Value:', customerToken.value); // Access the value from the typed object } return customerToken.value; } catch (error: any) { // Type 'any' for caught error for now, as stricter typing for HTTP errors can be complex console.error('Error requesting password reset token:', error); // For security, always return a generic success message to the user, // regardless of whether the email exists or not. console.log( 'If your email exists in our system, a password reset link has been sent.' ); throw error; } } // Simulate Alice requesting a password reset (async () => { const aliceEmail = 'alice.smith@example.com'; console.log(`\n--- Initiating Password Reset Request for ${aliceEmail} ---`); try { const resetToken = await requestPasswordResetToken(aliceEmail); const resetLink = `https://www.example.com/reset-password?token=${resetToken}`; const emailData = { toName: 'Alice', toEmail: aliceEmail, subject: 'Password Reset for your Zen Electron Account', body: ` Dear Alice, You have requested a password reset for your Zen Electron account. Please click on the following link to reset your password: ${resetLink} This link will expire in 1 hour. If you did not request a password reset, please ignore this email. `.trim(), }; // Note to learner: Implement actual email sending logic inside sendPasswordResetEmail() sendPasswordResetEmail(emailData); } catch (err) { console.error('Failed to initiate password reset for Alice.'); } })(); ``` ## Reset the password After the Customer clicks the password reset link from their email, the Customer is navigated to a page where they can enter a new password. Your application then sends this new password along with the token to commercetools. - **Endpoint**: [Reset password of Customer](/search.md?urn=ctp:api:endpoint:/{projectKey}/customers/password/reset:POST) (`POST /{projectKey}/customers/password/reset`). - To reset the password for a Customer in a Store, use [Reset password of Customer in Store](/search.md?urn=ctp:api:endpoint:/{projectKey}/in-store/key={storeKey}/customers/password/reset:POST). - **Request**: send the `tokenValue` from the email link and the `newPassword` in a [CustomerResetPassword](/search.md?urn=ctp:api:type:CustomerResetPassword) payload. The new password must enforce your backend's strong password policy (for example, minimum length and character requirements). - **Optional**: for a logged-in password change, use [Change password of Customer](/search.md?urn=ctp:api:endpoint:/{projectKey}/customers/password:POST) instead. The Change password endpoint requires the `id`, `version`, `currentPassword`, and `newPassword` details. - **Outcome**: if the token is valid and not expired, the password is updated, and previously issued **access and refresh tokens** from the [password flow](/api/authorization.md#password-flow) and refresh token flow are invalidated with eventual consistency. Otherwise, commercetools returns an error. - **Security**: always enforce strong password policies before sending the new password to commercetools, and require a fresh sign-in after the reset. ### Example - Reset a password This example assumes your backend receives the `tokenValue` and `newPassword` details from the frontend form. ```ts import { apiRoot } from '../../client'; // Assuming apiRoot is initialized and exported here import { Customer } from '@commercetools/platform-sdk'; // Import the Customer type for the response // Assuming `tokenValue` is received from the URL parameter and `newPassword` from a form const receivedResetTokenValue = 'GENERATED_RESET_TOKEN_FROM_EMAIL'; // Replace with actual token const aliceNewPassword = 'VerySecureNewPassword!2025'; // Ensure it meets strong policy // Function to reset customer password async function resetCustomerPassword( token: string, newPass: string ): Promise { try { const customerResponse = await apiRoot .customers() .passwordReset() .post({ body: { tokenValue: token, newPassword: newPass, }, }) .execute(); const customer: Customer = customerResponse.body; console.log('Password successfully reset for customer:', customer.email); // After successful password reset, you might want to immediately log the user in // or redirect them to the login page. return customer; } catch (error: any) { // Type 'any' for caught error for now, as stricter typing for HTTP errors can be complex console.error('Error resetting password:', error); // Provide a generic error message to the user, e.g., "The password reset link is invalid or has expired." throw error; } } // Execute password reset (async () => { try { console.log("\n--- Attempting to reset Alice's password ---"); const updatedCustomer = await resetCustomerPassword( receivedResetTokenValue, aliceNewPassword ); // After successful reset, you might auto-login Alice or prompt her to log in console.log( `Password reset for Alice successful! Her account is ready. Customer ID: ${updatedCustomer.id}` ); // Note: Consider having the customer log in with the new password before creating an authenticated session. // This adds an extra layer of verification, ensuring the user actually knows the new password. } catch (err) { console.error("Failed to reset Alice's password."); } })(); ``` ## Entire password reset flow The following diagram illustrates the complete password reset workflow: ```mermaid sequenceDiagram participant C as Customer (Alice) participant ZE_UI as Zen Electron UI (Frontend) participant ZE_BFF as Zen Electron BFF (Backend for Frontend) participant CT_API as Composable Commerce API participant ESP as Email Service Provider alt Request password reset token C->>ZE_UI: 1. Clicks "Forgot password?" ZE_UI->>ZE_BFF: 2. Requests password reset (email) ZE_BFF->>CT_API: 3. POST /{projectKey}/customers/password-token (email) activate CT_API CT_API-->>ZE_BFF: 4. Returns password reset token (value) deactivate CT_API ZE_BFF->>ESP: 5. Sends email with reset link (containing token) ESP-->>C: 6. Delivers password reset email end alt Reset the password C->>ZE_UI: 7. Clicks reset link (token in URL) ZE_UI->>ZE_BFF: 8. Submits new password (tokenValue, newPassword) ZE_BFF->>CT_API: 9. POST /{projectKey}/customers/password/reset (tokenValue, newPassword) activate CT_API CT_API-->>ZE_BFF: 10. Updates password, returns Customer details deactivate CT_API ZE_BFF->>ZE_UI: 11. Redirects to sign-in/success page ZE_UI-->>C: 12. Informs of password change / prompts sign-in end ``` ## Key takeaways - The password reset flow is a two-step process. First, request a token and then use that token to reset the password. - Use the `POST /{projectKey}/customers/password-token` endpoint to generate a secure, temporary token. - Always set a short time-to-live (TTL) on password reset tokens to enhance security. - Use the `POST /{projectKey}/customers/password/reset` endpoint with the token and new password to complete the reset. - Enforce strong password policies on your backend before sending the new password to the API. - For security reasons, don't provide feedback on whether an email address exists when a Customer requests a password reset. You've now learned how to implement a secure password reset flow. Next, we'll explore how to handle the transition from an anonymous to an authenticated Customer session, ensuring a seamless experience. ## Related pages - [Area overview page with navigation](/learning-implement-carts-and-shopping-lists.md) - [Previous page: Customer sign-in](/learning-implement-carts-and-shopping-lists/manage-signups-and-signins/customer-signin.md) - [Next page: Migrate anonymous resources to authenticated accounts](/learning-implement-carts-and-shopping-lists/manage-signups-and-signins/migrate-anonymous-resources-to-authenticated-accounts.md) - [Search documentation and API specs](/search.md)