diff --git a/www/apps/resources/app/commerce-modules/auth/auth-flows/page.mdx b/www/apps/resources/app/commerce-modules/auth/auth-flows/page.mdx
index b1d383c82c..4061945845 100644
--- a/www/apps/resources/app/commerce-modules/auth/auth-flows/page.mdx
+++ b/www/apps/resources/app/commerce-modules/auth/auth-flows/page.mdx
@@ -53,7 +53,7 @@ This method calls the `authenticate` method of the provider specified in the fir
The basic authentication flow requires first using the `register` method, then the `authenticate` method:
```ts
-const { success, authIdentity } = await authModuleService.register(
+const { success, authIdentity, error } = await authModuleService.register(
"emailpass",
// passed to auth provider
{
@@ -61,6 +61,12 @@ const { success, authIdentity } = await authModuleService.register(
}
)
+if (error) {
+ // registration failed
+ // TODO return an error
+ return
+}
+
// later (can be another route for log-in)
const { success, authIdentity, location } = await authModuleService.authenticate(
"emailpass",
@@ -87,6 +93,15 @@ Check out the [AuthIdentity](/references/auth/models/AuthIdentity) reference for

+### Auth Identity with Same Identifier
+
+If an auth identity, such as a `customer`, tries to register with an email of another auth identity, the `register` method returns an error. This can happen either if another customer is using the same email, or an admin user has the same email.
+
+There are two ways to handle this:
+
+- Consider the customer authenticated if the `authenticate` method validates that the email and password are correct. This allows admin users, for example, to authenticate as customers.
+- Return an error message to the customer, informing them that the email is already in use.
+
---
## Auth Flow 2: Third-Party Service Authentication
diff --git a/www/apps/resources/app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx b/www/apps/resources/app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx
index 8f56549f37..6d2c6cd20c 100644
--- a/www/apps/resources/app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx
+++ b/www/apps/resources/app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx
@@ -8,9 +8,7 @@ In this document, you’ll learn about concepts related to identity and actors i
## What is an Auth Identity?
-The [AuthIdentity data model](/references/auth/models/AuthIdentity) represents a user registered by an [authentication provider](../auth-providers/page.mdx).
-
-When a user is registered using an authentication provider, it creates a record of `AuthIdentity`.
+The [AuthIdentity data model](/references/auth/models/AuthIdentity) represents a user registered by an [authentication provider](../auth-providers/page.mdx). When a user is registered using an authentication provider, the provider creates a record of `AuthIdentity`.
Then, when the user logs-in in the future with the same authentication provider, the associated auth identity is used to validate their credentials.
@@ -18,11 +16,7 @@ Then, when the user logs-in in the future with the same authentication provider,
## Actor Types
-An actor type is a type of user that can be authenticated.
-
-The Auth Module doesn't store or manage any user-like models, such as for customers or users.
-
-Instead, the user types are created and managed by other modules. For example, a customer is managed by the Customer Module.
+An actor type is a type of user that can be authenticated. The Auth Module doesn't store or manage any user-like models, such as for customers or users. Instead, the user types are created and managed by other modules. For example, a customer is managed by the [Customer Module](../../customer/page.mdx).
Then, when an auth identity is created for the actor type, the ID of the user is stored in the `app_metadata` property of the auth identity.
diff --git a/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx b/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx
index 74acc6549d..ea4434ac66 100644
--- a/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx
+++ b/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx
@@ -29,13 +29,19 @@ The steps are:

1. Register the user with the [Register Route](#register-route).
-5. Use the authentication token to create the user with their respective API route.
+2. Use the authentication token to create the user with their respective API route.
- For example, for customers you would use the [Create Customer API route](!api!/store#customers_postcustomers).
- For admin users, you accept an invite using the [Accept Invite API route](!api!/admin#invites_postinvitesaccept)
-2. Authenticate the user with the [Auth Route](#auth-route).
+3. Authenticate the user with the [Auth Route](#auth-route).
After registration, you only use the [Auth Route](#auth-route) for subsequent authentication.
+
+
+To handle errors related to existing identities, refer to [this section](#handling-existing-identities).
+
+
+
### 2. Third-Party Service Authenticate Flow
This authentication flow authenticates the user with a third-party service, such as Google.
@@ -115,6 +121,35 @@ If the authentication is successful, you'll receive a `token` field in the respo
Use that token in the header of subsequent requests to send authenticated requests.
+### Handling Existing Identities
+
+An auth identity with the same email may already exist in Medusa. This can happen if:
+
+- Another actor type is using that email. For example, an admin user is trying to register as a customer.
+- The same email belongs to a record of the same actor type. For example, another customer has the same email.
+
+In these scenarios, the Register Route will return an error instead of a token:
+
+```json
+{
+ "type": "unauthorized",
+ "message": "Identity with email already exists"
+}
+```
+
+To handle these scenarios, you can use the [Login Route](#login-route) to validate that the email and password match the existing identity. If so, you can allow the admin user, for example, to register as a customer.
+
+Otherwise, if the email and password don't match the existing identity, such as when the email belongs to another customer, the [Login Route](#login-route) returns an error:
+
+```json
+{
+ "type": "unauthorized",
+ "message": "Invalid email or password"
+}
+```
+
+You can show that error message to the customer.
+
---
## Login Route
diff --git a/www/apps/resources/app/storefront-development/customers/register/page.mdx b/www/apps/resources/app/storefront-development/customers/register/page.mdx
index 432c1e030f..6d937776e0 100644
--- a/www/apps/resources/app/storefront-development/customers/register/page.mdx
+++ b/www/apps/resources/app/storefront-development/customers/register/page.mdx
@@ -19,39 +19,82 @@ To register a customer, you implement the following steps:
2. Send a `POST` request to the `/auth/customer/emailpass/register` API route to obtain a JWT token.
3. Send a request to the [Create Customer API route](!api!/store#customers_postcustomers) pass the JWT token in the header.
-For example:
+However, a customer may enter an email that's already used either by an admin user, another customer, or a [custom actor type](../../../commerce-modules/auth/auth-identity-and-actor-types/page.mdx). To handle this scenario:
+
+- Try to obtain a login token by sending a `POST` request to the `/auth/customer/emailpass` API route. The customer is only allowed to register if their email and password match the existing identity. This allows admin users to log in or register as customers.
+- If you obtained the login token successfully, create the customer using the login JWT token instead of the registration token. This will not remove the existing identity. So, for example, an admin user can also become a customer.
+
+An example implemetation of the registration flow in a storefront:
export const fetchHighlights = [
- ["3", "fetch", "Send a request to obtain a JWT token."],
- ["20", "fetch", "Send a request to create the customer."],
- ["27", "token", "Pass as a Bearer token in the authorization header."],
- ["40", "TODO", "Redirect the customer to the log in page."]
+ ["3", "fetch", "Send a request to obtain a registration JWT token."],
+ ["20", "", "Another identity exists with the same email."],
+ ["25", "fetch", "Try to obtain a login JWT token."],
+ ["41", "", "The existing account belongs to another customer, so authentication failed."],
+ ["50", "token", "Set the token to either the registration or login JWT token"],
+ ["53", "fetch", "Send a request to create the customer."],
+ ["60", "token", "Pass as a Bearer token in the authorization header."],
+ ["72", "", "Handle registration failure"],
+ ["77", "TODO", "Redirect the customer to the log in page."]
]
```ts highlights={fetchHighlights}
const handleRegistration = async () => {
// obtain JWT token
- const { token } = await fetch(
+ let registerResponse = await fetch(
`http://localhost:9000/auth/customer/emailpass/register`,
{
credentials: "include",
method: "POST",
headers: {
- "Content-Type": "application/json",
+ "Content-Type": "application/json"
},
body: JSON.stringify({
email,
- password,
- }),
+ password
+ })
}
)
.then((res) => res.json())
+ if (!registerResponse.token) {
+ if (registerResponse.type === "unauthorized" && registerResponse.message === "Identity with email already exists") {
+ // another identity (for example, admin user)
+ // exists with the same email. It can also be another customer
+ // with the same email. In that case, obtaining the login token
+ // will fail due to incorrect password.
+ registerResponse = await fetch(
+ `http://localhost:9000/auth/customer/emailpass`,
+ {
+ credentials: "include",
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ email,
+ password
+ })
+ }
+ )
+ .then((res) => res.json())
+
+ if (!registerResponse.token) {
+ alert("Error: " + registerResponse.message)
+ return
+ }
+ } else {
+ alert("Error: " + registerResponse.message)
+ }
+ }
+
+ const token = registerResponse.token as string
+
// create customer
- const { customer } = await fetch(
+ const customerResponse = await fetch(
`http://localhost:9000/store/customers`,
{
credentials: "include",
@@ -70,7 +113,12 @@ export const fetchHighlights = [
)
.then((res) => res.json())
- console.log(customer)
+ if (!customerResponse.customer) {
+ alert("Error: " + customerResponse.message)
+ return
+ }
+
+ console.log(customerResponse.customer)
// TODO redirect to login page
}
```
@@ -79,13 +127,18 @@ export const fetchHighlights = [
export const highlights = [
- ["22", "fetch", "Send a request to obtain a JWT token."],
- ["39", "fetch", "Send a request to create the customer."],
- ["46", "token", "Pass as a Bearer token in the authorization header."],
- ["60", "TODO", "Redirect the customer to the log in page."]
+ ["22", "fetch", "Send a request to obtain a registration JWT token."],
+ ["39", "", "Another identity exists with the same email."],
+ ["44", "fetch", "Try to obtain a login JWT token."],
+ ["60", "", "The existing account belongs to another customer, so authentication failed."],
+ ["69", "token", "Set the token to either the registration or login JWT token"],
+ ["72", "fetch", "Send a request to create the customer."],
+ ["79", "token", "Pass as a Bearer token in the authorization header."],
+ ["93", "", "Handle registration failure"],
+ ["98", "TODO", "Redirect the customer to the log in page."]
]
- ```tsx highlights={highlights} collapsibleLines="61-100" expandButtonLabel="Show form"
+ ```tsx highlights={highlights} collapsibleLines="101-140" expandButtonLabel="Show form"
"use client" // include with Next.js 13+
import { useState } from "react"
@@ -107,24 +160,57 @@ export const highlights = [
setLoading(true)
// obtain JWT token
- const { token } = await fetch(
- `http://localhost:9000/auth/customer/emailpass`,
+ let registerResponse = await fetch(
+ `http://localhost:9000/auth/customer/emailpass/register`,
{
credentials: "include",
method: "POST",
headers: {
- "Content-Type": "application/json",
+ "Content-Type": "application/json"
},
body: JSON.stringify({
email,
- password,
- }),
+ password
+ })
}
)
.then((res) => res.json())
+ if (!registerResponse.token) {
+ if (registerResponse.type === "unauthorized" && registerResponse.message === "Identity with email already exists") {
+ // another identity (for example, admin user)
+ // exists with the same email. It can also be another customer
+ // with the same email. In that case, obtaining the login token
+ // will fail due to incorrect password.
+ registerResponse = await fetch(
+ `http://localhost:9000/auth/customer/emailpass`,
+ {
+ credentials: "include",
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ email,
+ password
+ })
+ }
+ )
+ .then((res) => res.json())
+
+ if (!registerResponse.token) {
+ alert("Error: " + registerResponse.message)
+ return
+ }
+ } else {
+ alert("Error: " + registerResponse.message)
+ }
+ }
+
+ const token = registerResponse.token as string
+
// create customer
- const { customer } = await fetch(
+ const customerResponse = await fetch(
`http://localhost:9000/store/customers`,
{
credentials: "include",
@@ -132,19 +218,25 @@ export const highlights = [
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
- "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
+ "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp"
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
- email,
- }),
+ email
+ })
}
)
.then((res) => res.json())
- console.log(customer)
setLoading(false)
+
+ if (!customerResponse.customer) {
+ alert("Error: " + customerResponse.message)
+ return
+ }
+
+ console.log(customerResponse.customer)
// TODO redirect to login page
}
@@ -194,6 +286,10 @@ export const highlights = [
In the above example, you create a `handleRegistration` function that:
-- Obtains a JWT token from the `/auth/customer/emailpass` API route.
-- Send a request to the Create Customer API route, and pass the JWT token as a Bearer token in the authorization header.
+- Obtains a JWT token from the `/auth/customer/emailpass/register` API route.
+- If an error is returned instead of a token:
+ - If the error is an existing identity error, try retrieving the login JWT token from `/auth/customer/emailpass` API route. This will fail if the existing identity has a different password, which doesn't allow the customer from registering.
+ - For other errors, show an alert and exit execution.
+- Send a request to the Create Customer API route, and pass the registration or login JWT token as a Bearer token in the authorization header.
+- If an error occurs, show an alert and exit execution.
- Once the customer is registered successfully, you can either redirect the customer to the login page or log them in automatically as explained in this guide.
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index 1b15160d89..ceea957fc7 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -1,7 +1,7 @@
export const generatedEditDates = {
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2024-10-08T07:35:59.167Z",
"app/commerce-modules/auth/auth-providers/page.mdx": "2024-10-08T07:27:21.859Z",
- "app/commerce-modules/auth/authentication-route/page.mdx": "2024-09-05T12:06:38.155Z",
+ "app/commerce-modules/auth/authentication-route/page.mdx": "2025-01-07T09:26:27.809Z",
"app/commerce-modules/auth/examples/page.mdx": "2024-10-15T15:02:13.794Z",
"app/commerce-modules/auth/module-options/page.mdx": "2024-10-15T12:52:08.930Z",
"app/commerce-modules/auth/page.mdx": "2024-12-25T15:40:37.154Z",
@@ -154,7 +154,7 @@ export const generatedEditDates = {
"app/storefront-development/customers/log-out/page.mdx": "2024-12-19T16:31:28.347Z",
"app/storefront-development/customers/login/page.mdx": "2024-12-19T16:31:34.194Z",
"app/storefront-development/customers/profile/page.mdx": "2024-12-19T16:31:43.978Z",
- "app/storefront-development/customers/register/page.mdx": "2024-12-19T16:31:49.314Z",
+ "app/storefront-development/customers/register/page.mdx": "2025-01-07T09:28:42.723Z",
"app/storefront-development/customers/retrieve/page.mdx": "2025-01-06T16:07:12.542Z",
"app/storefront-development/customers/page.mdx": "2024-06-13T12:21:54+03:00",
"app/storefront-development/products/categories/list/page.mdx": "2024-12-19T16:33:06.547Z",
@@ -192,9 +192,9 @@ export const generatedEditDates = {
"app/usage/page.mdx": "2024-05-13T18:55:11+03:00",
"app/page.mdx": "2024-10-16T11:40:59.669Z",
"app/commerce-modules/auth/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
- "app/commerce-modules/auth/auth-flows/page.mdx": "2024-09-05T08:50:11.671Z",
+ "app/commerce-modules/auth/auth-flows/page.mdx": "2025-01-07T09:29:52.153Z",
"app/commerce-modules/auth/_events/page.mdx": "2024-07-03T19:27:13+03:00",
- "app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2024-12-09T13:04:01.129Z",
+ "app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2025-01-07T09:02:27.235Z",
"app/commerce-modules/api-key/page.mdx": "2024-12-25T15:55:02.846Z",
"app/commerce-modules/auth/create-actor-type/page.mdx": "2024-12-25T13:26:27.176Z",
"app/architectural-modules/page.mdx": "2024-12-11T10:33:53.064Z",