docs: update storefront development guides to use JS SDK [2] (#12015)
This commit is contained in:
1 parent
1895d8cc11
commit
bf882b5aff
43 files changed
+13257
-13309
No files matched your search
@@ -12,52 +12,29 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how to manage a customer's addresses in a storefront.
|
||||
In this guide, you'll learn how to manage a customer's addresses in a storefront. This is useful in the customer's profile page, or when the customer adds an address during checkout and you want to save it for future orders.
|
||||
|
||||
## List Customer Addresses
|
||||
|
||||
To retrieve the list of customer addresses, send a request to the [List Customer Addresses API route](!api!/store#customers_getcustomersmeaddressesaddress_id):
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
- Since only authenticated customers can view their addresses, this example assumes that the JS SDK is already configured for authentication and the customer's authentication token was set as explained in the [Login in Storefront](../login/page.mdx) and [Third-Party Login](../third-party-login/page.mdx) guides.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["2", "limit", "The number of addresses to retrieve"],
|
||||
["3", "offset", "The number of addresses to skip before those retrieved."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const searchParams = new URLSearchParams({
|
||||
limit: `${limit}`,
|
||||
offset: `${offset}`,
|
||||
})
|
||||
|
||||
fetch(`http://localhost:9000/store/customers/me/addresses?${
|
||||
searchParams.toString()
|
||||
}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ addresses, count }) => {
|
||||
// use addresses...
|
||||
console.log(addresses, count)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["20", "offset", "Calculate the number of addresses to skip based on the current page and limit."],
|
||||
["27", "fetch", "Send a request to retrieve the addresses."],
|
||||
["28", "searchParams.toString()", "Pass the pagination parameters in the query."],
|
||||
["36", "count", "The total number of addresses in the Medusa application."],
|
||||
["48", "setHasMorePages", "Set whether there are more pages based on the total count."],
|
||||
["62", "", "Using only two address fields for simplicity."],
|
||||
["68", "button", "Show a button to load more addresses if there are more pages."]
|
||||
["21", "offset", "Calculate the number of addresses to skip based on the current page and limit."],
|
||||
["23", "listAddress", "Send a request to retrieve the addresses."],
|
||||
["27", "count", "The total number of addresses in the Medusa application."],
|
||||
["39", "setHasMorePages", "Set whether there are more pages based on the total count."],
|
||||
["52", "", "Using only two address fields for simplicity."],
|
||||
["59", "button", "Show a button to load more addresses if there are more pages."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights} collapsibleLines="50-77" expandButtonLabel="Show render"
|
||||
@@ -65,6 +42,7 @@ export const highlights = [
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useEffect, useState } from "react"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function Addresses() {
|
||||
const [addresses, setAddresses] = useState<
|
||||
@@ -82,20 +60,10 @@ export const highlights = [
|
||||
|
||||
const offset = (currentPage - 1) * limit
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
limit: `${limit}`,
|
||||
offset: `${offset}`,
|
||||
sdk.store.customer.listAddress({
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
|
||||
fetch(`http://localhost:9000/store/customers/me/addresses?${
|
||||
searchParams.toString()
|
||||
}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ addresses: addressesData, count }) => {
|
||||
setAddresses((prev) => {
|
||||
if (prev.length > offset) {
|
||||
@@ -143,10 +111,33 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["2", "limit", "The number of addresses to retrieve"],
|
||||
["3", "offset", "The number of addresses to skip before those retrieved."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
sdk.store.customer.listAddress({
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
.then(({ addresses, count }) => {
|
||||
// use addresses...
|
||||
console.log(addresses, count)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
The [List Customer Addresses API route](!api!/store#customers_getcustomersmeaddresses) accepts pagination parameters to paginate the address. It returns in the response the `addresses` field, which is an array of addresses.
|
||||
The [List Customer Addresses API route](!api!/store#customers_getcustomersmeaddresses) accepts pagination parameters to paginate the address.
|
||||
|
||||
The request returns in the response the `addresses` field, which is an array of addresses. You can check its structure in the [customer schema](!api!/store#customers_customer_schema).
|
||||
|
||||
The request also returns the `count` field, which is the total number of addresses in the Medusa application. You can use it to check if there are more addresses to retrieve.
|
||||
|
||||
---
|
||||
|
||||
@@ -154,53 +145,31 @@ The [List Customer Addresses API route](!api!/store#customers_getcustomersmeaddr
|
||||
|
||||
To add a new address for the customer, send a request to the [Add Customer Address API route](!api!/store#customers_postcustomersmeaddresses):
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useRegion` hook defined in the [Region Context guide](../../regions/context/page.mdx).
|
||||
- This example uses the `useCustomer` hook defined in the [Customer Context guide](../context/page.mdx).
|
||||
- Since only authenticated customers can add addresses, this example assumes that the JS SDK is already configured for authentication and the customer's authentication token was set as explained in the [Login in Storefront](../login/page.mdx) and [Third-Party Login](../third-party-login/page.mdx) guides.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
```ts
|
||||
fetch(`http://localhost:9000/store/customers/me/addresses`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ customer }) => {
|
||||
// use customer
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const addHighlights = [
|
||||
["4", "useRegion", "Use the hook defined in the Region Context guide."],
|
||||
["5", "useCustomer", "Use the hook defined in the Customer Context guide."],
|
||||
["28"], ["29"], ["30"], ["31"], ["32"], ["33"], ["34"], ["35"], ["36"], ["37"],
|
||||
["38"], ["39"], ["40"], ["41"], ["42"], ["43"], ["44"], ["45"], ["46"], ["47"],
|
||||
["48"], ["49"], ["50"]
|
||||
["29"], ["30"], ["31"], ["32"], ["33"], ["34"], ["35"], ["36"], ["37"],
|
||||
["38"], ["39"], ["40"], ["41"], ["42"], ["43"],
|
||||
]
|
||||
|
||||
```tsx highlights={addHighlights} collapsibleLines="53-124" expandButtonLabel="Show form"
|
||||
```tsx highlights={addHighlights} collapsibleLines="46-117" expandButtonLabel="Show form"
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRegion } from "../../../../providers/region"
|
||||
import { useCustomer } from "../../../../providers/customer"
|
||||
import { useRegion } from "@/providers/region"
|
||||
import { useCustomer } from "@/providers/customer"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function AddAddress() {
|
||||
const { region } = useRegion()
|
||||
@@ -223,26 +192,17 @@ export const addHighlights = [
|
||||
e.preventDefault()
|
||||
setLoading(false)
|
||||
|
||||
fetch(`http://localhost:9000/store/customers/me/addresses`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
}),
|
||||
sdk.store.customer.createAddress({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ customer }) => {
|
||||
setCustomer(customer)
|
||||
})
|
||||
@@ -323,10 +283,33 @@ export const addHighlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
```ts
|
||||
sdk.store.customer.createAddress({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
// use customer
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
The Add Address API route returns in the response a `customer` field, which is a [customer object](!api!/store#customers_customer_schema).
|
||||
In this example, you send a request to the [Add Customer Address API route](!api!/store#customers_postcustomersmeaddresses) to add a new address for the customer.
|
||||
|
||||
The response of the request has a `customer` field, which is a [customer object](!api!/store#customers_customer_schema). You can access the new address in the `addresses` property of the customer object.
|
||||
|
||||
---
|
||||
|
||||
@@ -334,59 +317,32 @@ The Add Address API route returns in the response a `customer` field, which is a
|
||||
|
||||
To edit an address, send a request to the [Update Customer Address API route](!api!/store#customers_postcustomersmeaddressesaddress_id):
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useRegion` hook defined in the [Region Context guide](../../regions/context/page.mdx).
|
||||
- This example uses the `useCustomer` hook defined in the [Customer Context guide](../context/page.mdx).
|
||||
- Since only authenticated customers can edit their addresses, this example assumes that the JS SDK is already configured for authentication and the customer's authentication token was set as explained in the [Login in Storefront](../login/page.mdx) and [Third-Party Login](../third-party-login/page.mdx) guides.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`http://localhost:9000/store/customers/me/addresses/${
|
||||
address.id
|
||||
}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const editHighlights = [
|
||||
["4", "useRegion", "Use the hook defined in the Region Context guide."],
|
||||
["5", "useCustomer", "Use the hook defined in the Customer Context guide."],
|
||||
["17", "address", "Retrieve the address from the customer's `addresses` property."],
|
||||
["58"], ["59"], ["60"], ["61"], ["62"], ["63"], ["64"], ["65"], ["66"], ["67"],
|
||||
["68"], ["69"], ["70"], ["71"], ["72"], ["73"], ["74"], ["75"], ["76"], ["77"],
|
||||
["78"], ["79"], ["80"]
|
||||
["18", "address", "Retrieve the address from the customer's `addresses` property."],
|
||||
["59"], ["60"], ["61"], ["62"], ["63"], ["64"], ["65"], ["66"], ["67"],
|
||||
["68"], ["69"], ["70"], ["71"], ["72"], ["73"],
|
||||
]
|
||||
|
||||
```tsx highlights={editHighlights} collapsibleLines="90-161" expandButtonLabel="Show form"
|
||||
```tsx highlights={editHighlights} collapsibleLines="76-147" expandButtonLabel="Show form"
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRegion } from "../../../../../providers/region"
|
||||
import { useCustomer } from "../../../../../providers/customer"
|
||||
import { useRegion } from "@/providers/region"
|
||||
import { useCustomer } from "@/providers/customer"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
@@ -439,31 +395,17 @@ export const editHighlights = [
|
||||
}
|
||||
setLoading(true)
|
||||
|
||||
fetch(
|
||||
`http://localhost:9000/store/customers/me/addresses/${
|
||||
address.id
|
||||
}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
sdk.store.customer.updateAddress(address.id, {
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
setCustomer(customer)
|
||||
})
|
||||
@@ -544,10 +486,33 @@ export const editHighlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
```ts
|
||||
sdk.store.customer.updateAddress(address.id, {
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
address_1: address1,
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
The Update Address API route returns in the response a `customer` field, which is a [customer object](!api!/store#customers_customer_schema).
|
||||
In this example, you send a request to the [Update Customer Address API route](!api!/store#customers_postcustomersmeaddressesaddress_id) to edit an address.
|
||||
|
||||
The response of the request has a `customer` field, which is a [customer object](!api!/store#customers_customer_schema). You can access the updated address in the `addresses` property of the customer object.
|
||||
|
||||
---
|
||||
|
||||
@@ -555,28 +520,25 @@ The Update Address API route returns in the response a `customer` field, which i
|
||||
|
||||
To delete a customer's address, send a request to the [Delete Customer Address API route](!api!/store#customers_deletecustomersmeaddressesaddress_id):
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Since only authenticated customers can delete their addresses, this example assumes that the JS SDK is already configured for authentication and the customer's authentication token was set as explained in the [Login in Storefront](../login/page.mdx) and [Third-Party Login](../third-party-login/page.mdx) guides.
|
||||
|
||||
</Note>
|
||||
|
||||
export const deleteHighlights = [
|
||||
["3", "addrId", "The ID of the address to delete."]
|
||||
["1", "addrId", "The ID of the address to delete."],
|
||||
["2", "parent", "The updated customer object."]
|
||||
]
|
||||
|
||||
```ts highlights={deleteHighlights}
|
||||
fetch(
|
||||
`http://localhost:9000/store/customers/me/addresses/${
|
||||
addrId
|
||||
}`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
method: "DELETE",
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
sdk.store.customer.deleteAddress(addrId)
|
||||
.then(({ parent: customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
The Delete Customer Address API route returns a `parent` field in the response, which is a [customer object](!api!/store#customers_customer_schema).
|
||||
In this example, you send a request to the [Delete Customer Address API route](!api!/store#customers_deletecustomersmeaddressesaddress_id) to delete an address.
|
||||
|
||||
The response of the request has a `parent` field, which is a [customer object](!api!/store#customers_customer_schema). You can access the updated customer in the `parent` property of the response.
|
||||
@@ -10,21 +10,33 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
Throughout your storefront, you'll need to access the logged-in customer to perform different actions, such as associate it with a cart.
|
||||
In this guide, you'll learn how to create a customer context in your storefront.
|
||||
|
||||
So, if your storefront is React-based, create a customer context and add it at the top of your components tree. Then, you can access the logged-in customer anywhere in your storefront.
|
||||
## Why Create a Customer Context?
|
||||
|
||||
Throughout your storefront, you'll need to access the logged-in customer to perform different actions, such as associating it with a cart.
|
||||
|
||||
So, if your storefront is React-based, you can create a customer context and add it at the top of your components tree. Then, you can access the logged-in customer anywhere in your storefront.
|
||||
|
||||
---
|
||||
|
||||
## Create Customer Context Provider
|
||||
|
||||
For example, create the following file that exports a `CustomerProvider` component and a `useCustomer` hook:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
|
||||
export const highlights = [
|
||||
["12", "customer", "Expose customer to children of the context provider."],
|
||||
["13", "setCustomer", "Allow the context provider's\nchildren to change the logged-in customer."],
|
||||
["24", "CustomerProvider", "The provider component to use in your component tree."],
|
||||
["36", "fetch", "Try to retrieve the customer's details,\nif the customer is authentiated."],
|
||||
["37", `credentials: "include"`, "Important to include this option for cookie session authentication.\nFor token authentication, pass the authorization header instead."],
|
||||
["61", "useCustomer", "The hook that child components of the provider use to access the customer."]
|
||||
["13", "customer", "Expose customer to children of the context provider."],
|
||||
["14", "setCustomer", "Allow the context provider's\nchildren to change the logged-in customer."],
|
||||
["25", "CustomerProvider", "The provider component to use in your component tree."],
|
||||
["37", "retrieve", "Try to retrieve the customer's details,\nif the customer is authentiated."],
|
||||
["56", "useCustomer", "The hook that child components of the provider use to access the customer."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
@@ -37,6 +49,7 @@ import {
|
||||
useState,
|
||||
} from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
type CustomerContextType = {
|
||||
customer: HttpTypes.StoreCustomer | undefined
|
||||
@@ -63,13 +76,7 @@ export const CustomerProvider = ({
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/customers/me`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
sdk.store.customer.retrieve()
|
||||
.then(({ customer }) => {
|
||||
setCustomer(customer)
|
||||
})
|
||||
@@ -99,7 +106,7 @@ export const useCustomer = () => {
|
||||
}
|
||||
```
|
||||
|
||||
The `CustomerProvider` handles retrieving the authenticated customer from the Medusa application.
|
||||
The `CustomerProvider` handles retrieving the authenticated customer from the Medusa application. This assumes that the JS SDK is already configured for authentication and the customer's authentication token was set as explained in the [Login in Storefront](../login/page.mdx) and [Third-Party Login](../third-party-login/page.mdx) guides.
|
||||
|
||||
The `useCustomer` hook returns the value of the `CustomerContext`. Child components of `CustomerProvider` use this hook to access `customer` or `setCustomer`.
|
||||
|
||||
@@ -115,9 +122,9 @@ For example, if you're using Next.js, add it to the `app/layout.tsx` or `src/app
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { CartProvider } from "../providers/cart"
|
||||
import { RegionProvider } from "../providers/region"
|
||||
import { CustomerProvider } from "../providers/customer"
|
||||
import { CartProvider } from "@/providers/cart"
|
||||
import { RegionProvider } from "@/providers/region"
|
||||
import { CustomerProvider } from "@/providers/customer"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
@@ -146,12 +153,9 @@ export default function RootLayout({
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use useCustomer Hook
|
||||
### Use useCustomer Hook
|
||||
|
||||
Now, you can use the `useCustomer` hook in child components of `CustomerProvider`.
|
||||
|
||||
@@ -160,7 +164,7 @@ For example:
|
||||
```tsx
|
||||
"use client" // include with Next.js 13+
|
||||
// ...
|
||||
import { useCustomer } from "../providers/customer"
|
||||
import { useCustomer } from "@/providers/customer"
|
||||
|
||||
export default function Profile() {
|
||||
const { customer } = useCustomer()
|
||||
|
||||
@@ -13,83 +13,59 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how to log-out a customer in the storefront based on the authentication method.
|
||||
In this guide, you'll learn how to log-out a customer in the storefront based on the authentication method.
|
||||
|
||||
## Log-Out for JWT Token
|
||||
## Log-out using the JS SDK
|
||||
|
||||
If you're authenticating the customer with their JWT token, remove the stored token from the browser.
|
||||
If you're using the JS SDK, you can use the `auth.logout` method to log-out the customer:
|
||||
|
||||
For example, if you've stored the JWT token in the `localStorage`, remove the item from it:
|
||||
```ts
|
||||
sdk.auth.logout()
|
||||
.then(() => {
|
||||
// TODO redirect customer to login page
|
||||
})
|
||||
```
|
||||
|
||||
The JS SDK will handle the necessary actions based on the [authentication method](../login/page.mdx#js-sdk-authentication-configuration) you're using:
|
||||
|
||||
1. If you're using the `session` authentication method, the JS SDK will send a `DELETE` request to the `/auth/session` route. Then, it will remove any stored tokens from the configured storage method (by default, `localStorage`).
|
||||
2. If you're using the `jwt` authentication method, the JS SDK will only remove the JWT token from the configured storage method (by default, `localStorage`).
|
||||
|
||||
Once the operation succeeds, you can redirect the customer to the login page.
|
||||
|
||||
---
|
||||
|
||||
## Log-out without the JS SDK
|
||||
|
||||
If you're not using the JS SDK, you need to log out the customer based on the authentication method you're using.
|
||||
|
||||
### Log-out for JWT Token
|
||||
|
||||
If you're authenticating the customer with their JWT token, remove the JWT token stored locally in your storefront based on your storage method.
|
||||
|
||||
For example, if you're storing the JWT token in `localStorage`, remove the item from it:
|
||||
|
||||
```ts
|
||||
localStorage.removeItem(`token`)
|
||||
```
|
||||
|
||||
---
|
||||
Where `token` is the key of the JWT token in the `localStorage`.
|
||||
|
||||
## Log-Out for Cookie Session
|
||||
### Log-out for Cookie Session
|
||||
|
||||
If you're authenticating the customer with their cookie session ID, send a `DELETE` request to the `/auth/session`.
|
||||
If you're authenticating the customer with their cookie session ID, you need to send a `DELETE` request to the `/auth/session` route. This will remove the session cookie from the customer's browser.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
```ts
|
||||
fetch(`http://localhost:9000/auth/session`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(() => {
|
||||
// TODO redirect customer to login page
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["3", "useCustomer", "Use the hook defined in the Customer Context guide."],
|
||||
["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useCustomer } from "../../../providers/customer"
|
||||
|
||||
export default function Profile() {
|
||||
const { setCustomer } = useCustomer()
|
||||
|
||||
const handleLogOut = () => {
|
||||
fetch(`http://localhost:9000/auth/session`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(() => {
|
||||
setCustomer(undefined)
|
||||
// TODO redirect to login page
|
||||
alert("Logged out")
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Profile details... */}
|
||||
<button
|
||||
onClick={handleLogOut}
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
```ts
|
||||
fetch(`http://localhost:9000/auth/session`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(() => {
|
||||
// TODO redirect customer to login page
|
||||
})
|
||||
```
|
||||
|
||||
The API route returns nothing in the response. If the request was successful, you can perform any necessary work to unset the customer and redirect them to the login page.
|
||||
@@ -5,7 +5,7 @@ tags:
|
||||
- auth
|
||||
---
|
||||
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
import { CodeTabs, CodeTab, Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Login Customer in Storefront`,
|
||||
@@ -13,74 +13,148 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the two ways to login a customer in a storefront.
|
||||
In this guide, you'll learn about the two ways to login a customer in your storefront.
|
||||
|
||||
<Note>
|
||||
|
||||
This guide covers login using email and password. For authentication with third-party providers, refer to the [Third-Party Login](../third-party-login/page.mdx) guide.
|
||||
|
||||
</Note>
|
||||
|
||||
## Login Customer Methods
|
||||
|
||||
There are two ways to login a customer in your storefront:
|
||||
|
||||
1. [Using a JWT token](#1-using-a-jwt-token). This JWT token is obtained from the `/auth/customer/emailpass` API route and is used as a bearer token in the authorization header of all requests.
|
||||
- When using the JS SDK, you can set the token using the `client.setToken` method. Then, the JS SDK will use that token in the authorization header of all subsequent requests.
|
||||
2. [Using a cookie session](#2-using-a-cookie-session). This method uses the `/auth/session` API route to set the authenticated session ID in the cookies.
|
||||
- When using the JS SDK, you can configure it to use sessions to manage authentication and pass the session ID cookie in all requests.
|
||||
|
||||
The next sections explain how to implement each method.
|
||||
|
||||
### Which Method Should You Use?
|
||||
|
||||
The authentication method you choose depends on your use case and the type of storefront you're building.
|
||||
|
||||
When making a choice, consider the following:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>
|
||||
Method
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>
|
||||
When to use
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
JWT token
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
- You need stateless authentication. For example, you're building a mobile storefront with React Native.
|
||||
- Keep in mind: when logging out, you must clear the token in the JS SDK using the `client.clearToken` method. If the user still has access to the token, they can still send authenticated requests.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
Cookie session
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
- You need stateful authentication. For example, you're building a web storefront with Next.js.
|
||||
- You want to ensure the session is revoked when the user logs out.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### JS SDK Authentication Configuration
|
||||
|
||||
Before implementing the login flow, you need to configure in the JS SDK the authentication method you're using in your storefront. This defines how the JS SDK will handle sending authenticated requests after the customer is authenticated.
|
||||
|
||||
<Note>
|
||||
|
||||
If you're not using the JS SDK, the next sections explain the general approach of how to pass the necessary authentication headers or cookies in your requests.
|
||||
|
||||
</Note>
|
||||
|
||||
For example, add the following configuration to your JS SDK initialization:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="authenticated-configuration">
|
||||
<CodeTab label="JWT" value="jwt">
|
||||
|
||||
```ts
|
||||
export const sdk = new Medusa({
|
||||
// ...
|
||||
auth: {
|
||||
type: "jwt",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Session" value="session">
|
||||
|
||||
```ts
|
||||
export const sdk = new Medusa({
|
||||
// ...
|
||||
auth: {
|
||||
type: "session",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
The JS SDK will now pass the JWT token or the session ID cookie in the authorization header of all subsequent requests based on the authentication method you've configured.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
By default, when you choose the `jwt` method, the JWT token is stored in the browser's `localStorage`. However, you can change how the token is stored, which is useful in environments where `localStorage` is not available. For example, in React Native.
|
||||
|
||||
To learn how to change the storage method with an example for a React Native storefront, refer to the [JS SDK documentation](../../../js-sdk/page.mdx#custom-storage-methods).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## 1. Using a JWT Token
|
||||
|
||||
Using the `/auth/customer/emailpass` API route, you obtain a JSON Web Token (JWT) for the customer. Then, use that token as a bearer token in the authorization header of subsequent requests, and the customer is considered authenticated.
|
||||
The first authentication approach is to pass an authenticated JWT token in the authorization header of all requests. You can do that by:
|
||||
|
||||
For example:
|
||||
- Retrieving a JWT token from the `/auth/customer/emailpass` API route.
|
||||
- Passing the token in the authorization header of all subsequent requests, as explained in the [API reference](!api!/store#1-bearer-authorization-with-jwt-tokens).
|
||||
|
||||
The JS SDK simplifies passing the JWT token by passing it in the authorization header of all subsequent requests if you configure the JS SDK to use JWT authentication.
|
||||
|
||||
After [configuring the JS SDK](#js-sdk-authentication-configuration) to use JWT authentication, you can now implement the following login flow:
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["3", "fetch", "Send a request to obtain a JWT token."],
|
||||
["21", "fetch", "Send a request as an authenticated customer."],
|
||||
["27", "token", "Pass as a Bearer token in the authorization header."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const handleLogin = async () => {
|
||||
// obtain JWT token
|
||||
const { token } = 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())
|
||||
|
||||
// use token in the authorization header of
|
||||
// all follow up requests. For example:
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
console.log(customer)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["21", "fetch", "Send a request to obtain a JWT token."],
|
||||
["39", "fetch", "Send a request as an authenticated customer."],
|
||||
["45", "token", "Pass as a Bearer token in the authorization header."],
|
||||
["24", "login", "Send a request to obtain a JWT token."],
|
||||
["28", "catch", "If an error occurs, show an alert and exit execution."],
|
||||
["33", "", "If the token is not a string, show an alert and exit execution."],
|
||||
["40", "setToken", "Set the token in the JS SDK to pass it in the header of subsequent requests."],
|
||||
["43", "retrieve", "Retrieve the customer's details as an example of testing authentication."],
|
||||
]
|
||||
|
||||
```tsx highlights={highlights} collapsibleLines="55-79" expandButtonLabel="Show form"
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function Login() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -97,37 +171,29 @@ export const highlights = [
|
||||
|
||||
setLoading(true)
|
||||
|
||||
// obtain JWT token
|
||||
const { token } = 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())
|
||||
let token: string | { location: string }
|
||||
|
||||
try {
|
||||
token = await sdk.auth.login("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
} catch (error) {
|
||||
alert(`An error occured while logging in: ${error}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof token !== "string") {
|
||||
alert("Authentication requires more actions, which isn't supported by this flow.")
|
||||
return
|
||||
}
|
||||
|
||||
// use token in the authorization header of
|
||||
// all follow up requests. For example:
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
// all follow up requests.
|
||||
sdk.client.setToken(token)
|
||||
|
||||
// the next request will be authenticated
|
||||
const { customer } = await sdk.store.customer.retrieve()
|
||||
|
||||
console.log(customer)
|
||||
setLoading(false)
|
||||
@@ -160,6 +226,47 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["5", "login", "Send a request to obtain a JWT token."],
|
||||
["9", "catch", "If an error occurs, show an alert and exit execution."],
|
||||
["14", "", "If the token is not a string, show an alert and exit execution."],
|
||||
["21", "setToken", "Set the token in the JS SDK to pass it in the header of subsequent requests."],
|
||||
["24", "retrieve", "Retrieve the customer's details as an example of testing authentication."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const handleLogin = async () => {
|
||||
let token: string | { location: string }
|
||||
|
||||
try {
|
||||
token = await sdk.auth.login("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
} catch (error) {
|
||||
alert(`An error occured while logging in: ${error}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof token !== "string") {
|
||||
alert("Authentication requires more actions, which isn't supported by this flow.")
|
||||
return
|
||||
}
|
||||
|
||||
// use token in the authorization header of
|
||||
// all follow up requests.
|
||||
sdk.client.setToken(token)
|
||||
|
||||
// the next request will be authenticated
|
||||
const { customer } = await sdk.store.customer.retrieve()
|
||||
|
||||
console.log(customer)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
@@ -167,94 +274,42 @@ In the example above, you:
|
||||
|
||||
1. Create a `handleLogin` function that logs in a customer.
|
||||
2. In the function, you obtain a JWT token by sending a request to the `/auth/customer/emailpass`.
|
||||
3. You can then use that token in the authorization header of subsequent requests, and the customer is considered authenticated. As an example, you send a request to obtain the customer's details.
|
||||
- If an error occurs, show an alert and exit execution.
|
||||
- The request may return an object with a `location` property. This occurs when using third-party authentication providers. Learn more about implementing third-party authentication in the [Third-Party Login](../third-party-login/page.mdx) guide.
|
||||
3. To use the token in the authorization header of subsequent requests, you must set the token in the JS SDK using the `client.setToken` method.
|
||||
4. All subsequent requests are now authenticated. As an example, you send a request to obtain the logged-in customer's details.
|
||||
|
||||
---
|
||||
|
||||
## 2. Using a Cookie Session
|
||||
|
||||
Authenticating the customer with a cookie session means the customer is authenticated in subsequent requests that use that cookie.
|
||||
The second authentication approach is to authenticate the customer with a cookie session. You do that by:
|
||||
|
||||
If you're using the Fetch API, using the `credentials: include` option ensures that your cookie session is passed in every request.
|
||||
- Retrieving a JWT token from the `/auth/customer/emailpass` API route.
|
||||
- Sending a request to the `/auth/session` API route passing in the authorization header the token as a Bearer token. This sets the authenticated session ID in the cookies.
|
||||
|
||||
For example:
|
||||
Then, you must ensure that all subsequent requests include the session ID cookie.
|
||||
|
||||
The JS SDK simplifies this by passing the session ID cookie in all requests if you configure the JS SDK to use sessions.
|
||||
|
||||
After [configuring the JS SDK](#js-sdk-authentication-configuration) to use sessions, you can now implement the following login flow:
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchSessionHighlights = [
|
||||
["3", "fetch", "Send a request to obtain a JWT token."],
|
||||
["20", "fetch", "Send a request to set the authenticated session ID in the cookies."],
|
||||
["27", "token", "Pass as a Bearer token in the authorization header."],
|
||||
["35", "fetch", "Retrieve the customer's details as an example of testing authentication."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchSessionHighlights}
|
||||
const handleLogin = async () => {
|
||||
// obtain JWT token
|
||||
const { token } = 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())
|
||||
|
||||
// set session
|
||||
await fetch(
|
||||
`http://localhost:9000/auth/session`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
// customer is now authenticated using the
|
||||
// cookie session. For example
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
console.log(customer)
|
||||
setLoading(false)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const sessionHighlights = [
|
||||
["21", "fetch", "Send a request to obtain a JWT token."],
|
||||
["38", "fetch", "Send a request to set the authenticated session ID in the cookies."],
|
||||
["45", "token", "Pass as a Bearer token in the authorization header."],
|
||||
["53", "fetch", "Retrieve the customer's details as an example of testing authentication."],
|
||||
["24", "login", "Send a request to obtain a JWT token."],
|
||||
["28", "catch", "If an error occurs, show an alert and exit execution."],
|
||||
["33", "", "If the token is not a string, show an alert and exit execution."],
|
||||
["39", "setToken", "Set the token in the JS SDK, which will retrieve and pass the session ID cookie in all subsequent requests."],
|
||||
["43", "retrieve", "Retrieve the customer's details as an example of testing authentication."],
|
||||
]
|
||||
|
||||
```tsx highlights={sessionHighlights} collapsibleLines="68-92" expandButtonLabel="Show form"
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
|
||||
import { useState } from "react"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function Login() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -271,50 +326,29 @@ export const sessionHighlights = [
|
||||
|
||||
setLoading(true)
|
||||
|
||||
// obtain JWT token
|
||||
const { token } = 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())
|
||||
let token: string | { location: string }
|
||||
|
||||
try {
|
||||
token = await sdk.auth.login("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
} catch (error) {
|
||||
alert(`An error occured while logging in: ${error}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof token !== "string") {
|
||||
alert("Authentication requires more actions, which isn't supported by this flow.")
|
||||
return
|
||||
}
|
||||
|
||||
// set session
|
||||
await fetch(
|
||||
`http://localhost:9000/auth/session`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
sdk.client.setToken(token)
|
||||
|
||||
// customer is now authenticated using the
|
||||
// cookie session. For example
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
const { customer } = await sdk.store.customer.retrieve()
|
||||
|
||||
console.log(customer)
|
||||
setLoading(false)
|
||||
@@ -347,12 +381,56 @@ export const sessionHighlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const fetchSessionHighlights = [
|
||||
["5", "login", "Send a request to obtain a JWT token."],
|
||||
["9", "catch", "If an error occurs, show an alert and exit execution."],
|
||||
["14", "", "If the token is not a string, show an alert and exit execution."],
|
||||
["20", "setToken", "Set the token in the JS SDK, which will retrieve and pass the session ID cookie in all subsequent requests."],
|
||||
["24", "retrieve", "Retrieve the customer's details as an example of testing authentication."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchSessionHighlights}
|
||||
const handleLogin = async () => {
|
||||
let token: string | { location: string }
|
||||
|
||||
try {
|
||||
token = await sdk.auth.login("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
} catch (error) {
|
||||
alert(`An error occured while logging in: ${error}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof token !== "string") {
|
||||
alert("Authentication requires more actions, which isn't supported by this flow.")
|
||||
return
|
||||
}
|
||||
|
||||
// set session
|
||||
sdk.client.setToken(token)
|
||||
|
||||
// customer is now authenticated using the
|
||||
// cookie session. For example
|
||||
const { customer } = await sdk.store.customer.retrieve()
|
||||
|
||||
console.log(customer)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
In the example above, you:
|
||||
|
||||
1. Create a `handleLogin` function that logs in a customer.
|
||||
2. In the function, you obtain a JWT token by sending a request to the `/auth/customer/emailpass`.
|
||||
3. You send a request to the `/auth/session` API route passing in the authorization header the token as a Bearer token. This sets the authenticated session ID in the cookies.
|
||||
4. You can now send authenticated requests, as long as you include the `credentials: include` option in your fetch requests. For example, you send a request to retrieve the customer's details.
|
||||
2. In the function, you obtain a JWT token by sending a request to the `/auth/customer/emailpass` API route.
|
||||
- If an error occurs, show an alert and exit execution.
|
||||
- The request may return an object with a `location` property. This occurs when using third-party authentication providers. Learn more about implementing third-party authentication in the [Third-Party Login](../third-party-login/page.mdx) guide.
|
||||
3. To obtain the session ID cookie and ensure it's included in all subsequent requests, you must use the JS SDK's `client.setToken` method, passing it the JWT token.
|
||||
- Because you set the JS SDK's authentication type to `session`, the `client.setToken` method will send a request to the `/auth/session` API route passing in the authorization header the token as a Bearer token. This sets the authenticated session ID in the cookies, which are then included in all subsequent requests.
|
||||
4. All subsequent requests are now authenticated. As an example, you send a request to obtain the logged-in customer's details.
|
||||
@@ -12,52 +12,38 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you'll learn how to edit the customer's profile in the storefront, which is useful if you're adding a profile page to your storefront that allows the customer to view and edit their details.
|
||||
|
||||
To edit the customer's profile in the storefront, send a request to the [Update Customer API route](!api!/store#customers_postcustomersme).
|
||||
|
||||
For example:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
- This example uses the `useCustomer` hook defined in the [Customer Context guide](../context/page.mdx).
|
||||
- Since only authenticated customers can edit their profile, this example assumes that the JS SDK is already configured for authentication and the customer's authentication token was set as explained in the [Login in Storefront](../login/page.mdx) and [Third-Party Login](../third-party-login/page.mdx) guides.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
```ts
|
||||
fetch(`http://localhost:9000/store/customers/me`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name,
|
||||
last_name,
|
||||
company_name,
|
||||
phone,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["4", "useCustomer", "Use the hook defined in the Customer Context guide."],
|
||||
["33"], ["34"], ["35"], ["36"], ["37"], ["38"], ["39"], ["40"], ["41"], ["42"],
|
||||
["43"], ["44"], ["45"], ["46"], ["47"], ["48"], ["49"], ["50"]
|
||||
["35"], ["36"], ["37"], ["38"], ["39"], ["40"], ["41"], ["42"], ["43"], ["44"],
|
||||
]
|
||||
|
||||
```tsx highlights={highlights} collapsibleLines="53-91" expandButtonLabel="Show form"
|
||||
```tsx highlights={highlights} collapsibleLines="47-85" expandButtonLabel="Show form"
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { useCustomer } from "../../../providers/customer"
|
||||
import { useCustomer } from "@/providers/customer"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function EditProfile() {
|
||||
const { customer, setCustomer } = useCustomer()
|
||||
console.log(customer)
|
||||
const [firstName, setFirstName] = useState(
|
||||
customer?.first_name || ""
|
||||
)
|
||||
@@ -83,21 +69,12 @@ export const highlights = [
|
||||
|
||||
setLoading(true)
|
||||
|
||||
fetch(`http://localhost:9000/store/customers/me`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
company_name: company,
|
||||
phone,
|
||||
}),
|
||||
sdk.store.customer.update({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
company_name: company,
|
||||
phone,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ customer: updatedCustomer }) => {
|
||||
setCustomer(updatedCustomer)
|
||||
})
|
||||
@@ -145,9 +122,25 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
```ts
|
||||
sdk.store.customer.update({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
company_name: company,
|
||||
phone,
|
||||
})
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
In the example above, you send a request to the Update Customer API route to update the customer's details.
|
||||
In the example above, you send a request to the [Update Customer API route](!api!/store#customers_postcustomersme) to update the customer's details.
|
||||
|
||||
The response of the request has a `customer` field which is a [customer object](!api!/store#customers_customer_schema).
|
||||
@@ -13,135 +13,60 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you'll learn how to register a customer in your storefront.
|
||||
|
||||
This guide covers registration using email and password. For authentication with third-party providers, refer to the [Third-Party Login](../third-party-login/page.mdx) guide.
|
||||
|
||||
## Register Customer Flow
|
||||
|
||||
To register a customer, you implement the following steps:
|
||||
|
||||

|
||||
|
||||
1. Show the customer a form to enter their details.
|
||||
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.
|
||||
2. Send a `POST` request to the `/auth/customer/emailpass/register` API route to obtain a registration JWT token.
|
||||
3. Send a request to the [Create Customer API route](!api!/store#customers_postcustomers) passing the registration JWT token in the header.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## How to Implement the Register Customer Flow
|
||||
|
||||
An example implemetation of the registration flow in a storefront:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["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
|
||||
let registerResponse = await fetch(
|
||||
`http://localhost:9000/auth/customer/emailpass/register`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
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 customerResponse = await fetch(
|
||||
`http://localhost:9000/store/customers`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
if (!customerResponse.customer) {
|
||||
alert("Error: " + customerResponse.message)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(customerResponse.customer)
|
||||
// TODO redirect to login page
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["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."]
|
||||
["26", "register", "Send a request to obtain a registration JWT token."],
|
||||
["30", "catch", "Maybe another identity exists with the same email."],
|
||||
["33", "", "If an unexpected error occurs, exit the flow."],
|
||||
["40", "login", "Try to obtain a login JWT token."],
|
||||
["43", "catch", "The existing account belongs to another customer, so authentication failed."],
|
||||
["56", "registrationToken", "Set the token to the login JWT token"],
|
||||
["59", "setToken", "Set the token in the JS SDK to pass it in the header of subsequent requests."],
|
||||
["63", "create", "Send a request to create the customer."],
|
||||
["72", "clearToken", "Clear the token in the JS SDK so that it's not used in subsequent requests."],
|
||||
["75", "TODO", "Redirect the customer to the log in page."],
|
||||
["76", "catch", "Handle registration failure"],
|
||||
]
|
||||
|
||||
```tsx highlights={highlights} collapsibleLines="101-140" expandButtonLabel="Show form"
|
||||
```tsx highlights={highlights} collapsibleLines="83-121" expandButtonLabel="Show form"
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
|
||||
export default function Register() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -159,85 +84,64 @@ export const highlights = [
|
||||
}
|
||||
setLoading(true)
|
||||
|
||||
// obtain JWT token
|
||||
let registerResponse = await fetch(
|
||||
`http://localhost:9000/auth/customer/emailpass/register`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
let registrationToken = ""
|
||||
|
||||
if (!registerResponse.token) {
|
||||
if (registerResponse.type === "unauthorized" && registerResponse.message === "Identity with email already exists") {
|
||||
try {
|
||||
registrationToken = await sdk.auth.register("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
} catch (error) {
|
||||
const fetchError = error as FetchError
|
||||
|
||||
if (fetchError.statusText !== "Unauthorized" || fetchError.message !== "Identity with email already exists") {
|
||||
alert(`An error occured while creating account: ${fetchError}`)
|
||||
return
|
||||
}
|
||||
// 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())
|
||||
// exists with the same email. So, use the auth
|
||||
// flow to login and create a customer.
|
||||
const loginResponse = (await sdk.auth.login("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
}).catch((e) => {
|
||||
alert(`An error occured while creating account: ${e}`)
|
||||
}))
|
||||
|
||||
if (!registerResponse.token) {
|
||||
alert("Error: " + registerResponse.message)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
alert("Error: " + registerResponse.message)
|
||||
if (!loginResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof loginResponse !== "string") {
|
||||
alert("Authentication requires more actions, which isn't supported by this flow.")
|
||||
return
|
||||
}
|
||||
|
||||
registrationToken = loginResponse
|
||||
}
|
||||
|
||||
const token = registerResponse.token as string
|
||||
sdk.client.setToken(registrationToken)
|
||||
|
||||
// create customer
|
||||
const customerResponse = await fetch(
|
||||
`http://localhost:9000/store/customers`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
try {
|
||||
const { customer } = await sdk.store.customer.create({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email,
|
||||
})
|
||||
|
||||
setLoading(false)
|
||||
|
||||
setLoading(false)
|
||||
// clear the token
|
||||
sdk.client.clearToken()
|
||||
|
||||
if (!customerResponse.customer) {
|
||||
alert("Error: " + customerResponse.message)
|
||||
console.log(customer)
|
||||
// TODO redirect to login page
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
alert("Error: " + error)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(customerResponse.customer)
|
||||
// TODO redirect to login page
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -281,15 +185,102 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["10", "register", "Send a request to obtain a registration JWT token."],
|
||||
["14", "catch", "Maybe another identity exists with the same email."],
|
||||
["24", "login", "Try to obtain a login JWT token."],
|
||||
["27", "catch", "The existing account belongs to another customer, so authentication failed."],
|
||||
["40", "registrationToken", "Set the token to the login JWT token"],
|
||||
["43", "setToken", "Set the token in the JS SDK to pass it in the header of subsequent requests."],
|
||||
["47", "create", "Send a request to create the customer."],
|
||||
["54", "clearToken", "Clear the token in the JS SDK so that it's not used in subsequent requests."],
|
||||
["57", "TODO", "Redirect the customer to the log in page."],
|
||||
["58", "catch", "Handle registration failure"],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
// other imports...
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
|
||||
const handleRegistration = async () => {
|
||||
|
||||
let registrationToken = ""
|
||||
|
||||
// obtain registration JWT token
|
||||
try {
|
||||
registrationToken = await sdk.auth.register("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
} catch (error) {
|
||||
const fetchError = error as FetchError
|
||||
|
||||
if (fetchError.statusText !== "Unauthorized" || fetchError.message !== "Identity with email already exists") {
|
||||
alert(`An error occured while creating account: ${fetchError}`)
|
||||
return
|
||||
}
|
||||
// another identity (for example, admin user)
|
||||
// exists with the same email. So, use the auth
|
||||
// flow to login and create a customer.
|
||||
const loginResponse = (await sdk.auth.login("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
}).catch((e) => {
|
||||
alert(`An error occured while creating account: ${e}`)
|
||||
}))
|
||||
|
||||
if (!loginResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof loginResponse !== "string") {
|
||||
alert("Authentication requires more actions, which isn't supported by this flow.")
|
||||
return
|
||||
}
|
||||
|
||||
registrationToken = loginResponse
|
||||
}
|
||||
|
||||
sdk.client.setToken(registrationToken)
|
||||
|
||||
// create customer
|
||||
try {
|
||||
const { customer } = await sdk.store.customer.create({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email,
|
||||
})
|
||||
|
||||
// clear the token
|
||||
sdk.client.clearToken()
|
||||
|
||||
console.log(customer)
|
||||
// TODO redirect to login page
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
alert("Error: " + error)
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
In the above example, you create a `handleRegistration` function that:
|
||||
|
||||
- Obtains a JWT token from the `/auth/customer/emailpass/register` API route.
|
||||
- If an error is returned instead of a token:
|
||||
- Obtains a registration JWT token from the `/auth/customer/emailpass/register` API route.
|
||||
- If an error is thrown:
|
||||
- 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.
|
||||
- In the JS SDK, set the registration or login token using the `client.setToken` method. Then, all subsequent requests will use that token in the request header.
|
||||
- If you're not using the JS SDK, you must pass manually pass the registration or login JWT token as a Bearer token in the authorization header of the next request.
|
||||
- Send a request to the [Create Customer API route](!api!/store#customers_postcustomers) to create the customer in Medusa.
|
||||
- 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.
|
||||
- Make sure to clear the token in the JS SDK using the `client.clearToken` method so that it's not used in subsequent requests.
|
||||
|
||||
Refer to the [Login guide](../login/page.mdx) to learn how to log in the customer manually or automatically.
|
||||
@@ -13,77 +13,54 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
Customers reset their password if they forget it.
|
||||
In this guide, you'll learn how to implement the flow to reset a customer's password in your storefront.
|
||||
|
||||
To implement the flow to reset a customer's password, you need two pages in your storefront:
|
||||
## Reset Password Flow in Storefront
|
||||
|
||||
1. A page to request the password reset.
|
||||
2. A page that prompts the customer to enter a new password.
|
||||
Customers need to reset their password if they forget it. To implement the flow to reset a customer's password, you need two pages in your storefront:
|
||||
|
||||
---
|
||||
|
||||
## 1. Request Reset Password Page
|
||||
|
||||
The request password reset page prompts the customer to enter their email. Then, it sends a request to the [Request Reset Password Token API route](!api!/store#auth_postactor_typeauth_providerresetpassword) to send the customer an email with the URL to reset their password.
|
||||
1. [Request Reset Password Page](#1-request-reset-password-page): A page to request the password reset.
|
||||
- When the customer requests to reset their password, they would receive an email (or other notification) with a URL to the Reset Password Page.
|
||||
2. [Reset Password Page](#2-reset-password-page): A page that prompts the customer to enter a new password.
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "While it's not required, it's recommended to implement the subscriber that sends the customer an email with the URL to reset their password.",
|
||||
text: "To send the customer an email (or other notification) with the URL to reset their password, you must implement the subscriber that handles the notification.",
|
||||
link: "/commerce-modules/auth/reset-password"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
For example:
|
||||
---
|
||||
|
||||
## 1. Request Reset Password Page
|
||||
|
||||
The request password reset page prompts the customer to enter their email. Then, it sends a request to the [Request Reset Password Token API route](!api!/store#auth_postactor_typeauth_providerresetpassword) to request resetting the password.
|
||||
|
||||
This API route will then handle sending an email or another type of notification, if you handle it as explained in the [Reset Password Guide](../../../commerce-modules/auth/reset-password/page.mdx).
|
||||
|
||||
For example, you can implement the following functionality in your storefront to request resetting the password:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["5", "email", "Assuming the email is retrieved from an input field."],
|
||||
["10", "fetch", "Send a request to send the token to the customer."],
|
||||
["17", "identifier", "Pass the email in the `identifier` request body parameter."]
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const handleSubmit = async (
|
||||
e: React.FormEvent<HTMLFormElement> // or other form event
|
||||
) => {
|
||||
e.preventDefault()
|
||||
if (!email) {
|
||||
alert("Email is required")
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/auth/customer/emailpass/reset-password`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
identifier: email,
|
||||
}),
|
||||
})
|
||||
.then(() => {
|
||||
alert("If an account exists with the specified email, it'll receive instructions to reset the password.")
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["19", "fetch", "Send a request to send the token to the customer."],
|
||||
["26", "identifier", "Pass the email in the `identifier` request body parameter."]
|
||||
["20", "resetPassword", "Request resetting the password."],
|
||||
["21", "identifier", "Pass the email in the `identifier` request body parameter."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function RequestResetPassword() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -99,18 +76,16 @@ export const highlights = [
|
||||
}
|
||||
setLoading(true)
|
||||
|
||||
fetch(`http://localhost:9000/auth/customer/emailpass/reset-password`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
identifier: email,
|
||||
}),
|
||||
sdk.auth.resetPassword("customer", "emailpass", {
|
||||
identifier: email,
|
||||
})
|
||||
.then(() => {
|
||||
alert("If an account exists with the specified email, it'll receive instructions to reset the password.")
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(error.message)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
@@ -132,16 +107,47 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["5", "email", "Assuming the email is retrieved from an input field."],
|
||||
["10", "resetPassword", "Request resetting the password."],
|
||||
["11", "identifier", "Pass the email in the `identifier` request body parameter."]
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const handleSubmit = async (
|
||||
e: React.FormEvent<HTMLFormElement> // or other form event
|
||||
) => {
|
||||
e.preventDefault()
|
||||
if (!email) {
|
||||
alert("Email is required")
|
||||
return
|
||||
}
|
||||
|
||||
sdk.auth.resetPassword("customer", "emailpass", {
|
||||
identifier: email,
|
||||
})
|
||||
.then(() => {
|
||||
alert("If an account exists with the specified email, it'll receive instructions to reset the password.")
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(error.message)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
In this example, you send a request to `http://localhost:9000/auth/customer/emailpass/reset-password` API route when the form that has the email field is submitted.
|
||||
In this example, you send a request to the [Request Reset Password Token API route](!api!/store#auth_postactor_typeauth_providerresetpassword) when the form that has the email field is submitted.
|
||||
|
||||
In the request body, you pass an `identifier` parameter, which is the customer's email.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The Request Reset Password Token API route returns a successful response always, even if the customer's email doesn't exist. However, the customer only receives an email if they have an account with that email.
|
||||
The [Request Reset Password Token API route](!api!/store#auth_postactor_typeauth_providerresetpassword) returns a successful response always, even if the customer's email doesn't exist. This ensures that customer emails that don't exist are not exposed.
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -149,7 +155,11 @@ The Request Reset Password Token API route returns a successful response always,
|
||||
|
||||
## 2. Reset Password Page
|
||||
|
||||
The reset password page is the URL used in the email sent to the customer. It receives a `token` and `email` query parameters, prompts the customer for a new password, and sends a request to the [Reset Password API route](!api!/store#auth_postactor_typeauth_providerupdate).
|
||||
Once the customer requests to reset their password, you should handle sending them a notification, such as an email, as explained in the [Reset Password Guide](../../../commerce-modules/auth/reset-password/page.mdx).
|
||||
|
||||
The notification should include a URL in your storefront that allows the customer to update their password. In this step, you'll implement this page.
|
||||
|
||||
The reset password page should receive a `token` and `email` query parameters. Then, it prompts the customer for a new password, and sends a request to the [Reset Password API route](!api!/store#auth_postactor_typeauth_providerupdate) to update the password.
|
||||
|
||||
<Note>
|
||||
|
||||
@@ -160,65 +170,20 @@ If you followed [this guide](../../../commerce-modules/auth/reset-password/page.
|
||||
For example:
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const resetPasswordFetchHighlights = [
|
||||
["2", "token", "Receive the token from a query parameter."],
|
||||
["3", "email", "Receive the email from a query parameter."],
|
||||
["9", "password", "Assuming the password is retrieved from an input field."],
|
||||
["14", "fetch", "Send a request to update the customer's password."],
|
||||
["19", "token", "Pass the token in the Authorization header."],
|
||||
["21", "body", "Pass the email and password in the request body."]
|
||||
]
|
||||
|
||||
```ts highlights={resetPasswordFetchHighlights}
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
const token = queryParams.get("token")
|
||||
const email = queryParams.get("email")
|
||||
|
||||
const handleSubmit = async (
|
||||
e: React.FormEvent<HTMLFormElement>
|
||||
) => {
|
||||
e.preventDefault()
|
||||
if (!password) {
|
||||
alert("Password is required")
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/auth/customer/emailpass/update`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ success }) => {
|
||||
alert(success ? "Password reset successfully!" : "Couldn't reset password")
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const resetPasswordHighlights = [
|
||||
["18", "token", "Receive the token from a query parameter."],
|
||||
["21", "email", "Receive the email from a query parameter."],
|
||||
["35", "fetch", "Send a request to update the customer's password."],
|
||||
["40", "token", "Pass the token in the Authorization header."],
|
||||
["42", "body", "Pass the email and password in the request body."]
|
||||
["19", "token", "Receive the token from a query parameter."],
|
||||
["22", "email", "Receive the email from a query parameter."],
|
||||
["39", "updateProvider", "Send a request to update the customer's password."],
|
||||
["41", "password", "Pass the new password in the request body."]
|
||||
]
|
||||
|
||||
```tsx highlights={resetPasswordHighlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function ResetPassword() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -244,27 +209,26 @@ export const resetPasswordHighlights = [
|
||||
e: React.FormEvent<HTMLFormElement>
|
||||
) => {
|
||||
e.preventDefault()
|
||||
if (!token) {
|
||||
return
|
||||
}
|
||||
if (!password) {
|
||||
alert("Password is required")
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
|
||||
fetch(`http://localhost:9000/auth/customer/emailpass/update`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
}),
|
||||
sdk.auth.updateProvider("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
}, token)
|
||||
.then(() => {
|
||||
alert("Password reset successfully!")
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ success }) => {
|
||||
alert(success ? "Password reset successfully!" : "Couldn't reset password")
|
||||
.catch((error) => {
|
||||
alert(`Couldn't reset password: ${error.message}`)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
@@ -286,15 +250,57 @@ export const resetPasswordHighlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const resetPasswordFetchHighlights = [
|
||||
["2", "token", "Receive the token from a query parameter."],
|
||||
["3", "email", "Receive the email from a query parameter."],
|
||||
["17", "updateProvider", "Send a request to update the customer's password."],
|
||||
["19", "password", "Pass the new password in the request body."]
|
||||
]
|
||||
|
||||
```ts highlights={resetPasswordFetchHighlights}
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
const token = queryParams.get("token")
|
||||
const email = queryParams.get("email")
|
||||
|
||||
const handleSubmit = async (
|
||||
e: React.FormEvent<HTMLFormElement>
|
||||
) => {
|
||||
e.preventDefault()
|
||||
if (!token) {
|
||||
return
|
||||
}
|
||||
if (!password) {
|
||||
alert("Password is required")
|
||||
return
|
||||
}
|
||||
|
||||
sdk.auth.updateProvider("customer", "emailpass", {
|
||||
email,
|
||||
password,
|
||||
}, token)
|
||||
.then(() => {
|
||||
alert("Password reset successfully!")
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(`Couldn't reset password: ${error.message}`)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
In this example, you receive the `token` and `email` from the page's query parameters.
|
||||
|
||||
Then, when the form that has the password field is submitted, you send a request to the `http://localhost:9000/auth/customer/emailpass/update` API route. You pass it the token as in the Authorization header as a bearer token, and the email and password in the request body.
|
||||
Then, when the form that has the password field is submitted, you send a request to the [Reset Password API route](!api!/store#auth_postactor_typeauth_providerupdate), passing it the token, email, and new password.
|
||||
|
||||
Notice that the JS SDK passes the token in the `Authorization Bearer` header. So, if you're implementing this flow without using the JS SDK, make sure to pass the token accordingly.
|
||||
|
||||
<Note>
|
||||
|
||||
Before [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6), you passed the token as a query parameter. Now, you must pass it in the `Authorization` header.
|
||||
Before [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6), you passed the token as a query parameter. Now, you must pass it in the `Authorization Bearer` header.
|
||||
|
||||
</Note>
|
||||
@@ -13,63 +13,68 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
To retrieve a customer after it's been authenticated in your storefront, send a request to the [Get Customer API route](!api!/store#customers_getcustomersme):
|
||||
In this guide, you'll learn how to retrieve a customer after they've been authenticated in your storefront.
|
||||
|
||||
<CodeTabs group="authenticated-request">
|
||||
<CodeTab label="Using Bearer Token" value="bearer">
|
||||
## Prerequisites: Set the Customer's Authentication Token
|
||||
|
||||
export const bearerHighlights = [
|
||||
["7", "", "Pass JWT token as bearer token in authorization header."],
|
||||
]
|
||||
When using the [JS SDK](../../../js-sdk/page.mdx), make sure that you've set the customer's authentication token using the `setToken` function:
|
||||
|
||||
```ts highlights={bearerHighlights}
|
||||
fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
```ts
|
||||
sdk.client.setToken(token)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Using Cookie Session" value="session">
|
||||
You can learn more in the [Login Customer](../login/page.mdx) and [Third-Party Login](../third-party-login/page.mdx) guides.
|
||||
|
||||
export const sessionHighlights = [
|
||||
["4", "", "Pass this option to ensure the cookie session is passed in the request."],
|
||||
]
|
||||
---
|
||||
|
||||
```ts highlights={sessionHighlights}
|
||||
fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
## Retrieve Logged-In Customer
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
To retrieve the logged-in customer, send a request to the [Get Customer API route](!api!/store#customers_getcustomersme):
|
||||
|
||||
```ts
|
||||
sdk.store.customer.retrieve()
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
```
|
||||
|
||||
This will retrieve the authenticated customer's details. The [Get Customer API route](!api!/store#customers_getcustomersme) returns a `customer` field, which is a [customer object](!api!/store#customers_customer_schema).
|
||||
|
||||
Notice that the JS SDK automatically passes the necessary authentication headers or cookies based on your authentication configurations, as explained in the [Login Customer](../login/page.mdx) guide.
|
||||
|
||||
If you're not using the JS SDK, you need to pass the authentication token in the request headers or cookies accordingly:
|
||||
|
||||
- If you authenticate the customer with bearer authorization, pass the token in the authorization header of the request.
|
||||
- If you authenticate the customer with cookie session, pass the `credentials: include` option to the `fetch` function.
|
||||
|
||||
The Get Customer API route returns a `customer` field, which is a [customer object](!api!/store#customers_customer_schema).
|
||||
---
|
||||
|
||||
## Restrict Access to Authenticated Customers
|
||||
|
||||
In your storefront, it's common to restrict access to certain pages to authenticated customers only.
|
||||
|
||||
For example, you may want to restrict access to the customer's profile page to authenticated customers only.
|
||||
|
||||
To do this, you can try to retrieve the customer's details. If the request fails, you can redirect the customer to the login page.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
sdk.store.customer.retrieve()
|
||||
.then(({ customer }) => {
|
||||
// use customer...
|
||||
console.log(customer)
|
||||
})
|
||||
.catch(() => {
|
||||
// redirect to login page
|
||||
})
|
||||
```
|
||||
|
||||
The `catch` block will only execute if the request fails, which means that the customer is not authenticated. You can add the redirect logic to the `catch` block based on your storefront framework.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
If you're building a React storefront, you can use the `useCustomer` hook defined in the [Customer Context guide](../context/page.mdx) to check if the customer is set. If not, you can redirect the customer to the login page.
|
||||
|
||||
</Note>
|
||||
+314
-343
@@ -13,12 +13,14 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
To login a customer with a third-party service, such as Google or GitHub, you must follow the following flow:
|
||||
In this guide, you'll learn how to implement third-party or social login in your storefront. You'll implement the flow using Google as an example.
|
||||
|
||||
## Third-Party Login Flow in Storefront
|
||||
|
||||
Assuming you already set up the [Auth Module Provider](../../../commerce-modules/auth/auth-providers/page.mdx) in your Medusa application, you can login a customer with a third-party service, such as Google or GitHub, using the following flow:
|
||||
|
||||

|
||||
|
||||
<Details summaryContent="Explanation" className="my-1">
|
||||
|
||||
1. Authenticate the customer with the [Authenticate Customer API route](!api!/store#auth_postactor_typeauth_provider).
|
||||
2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The storefront, when it receives a `location` property in the response, must redirect to the returned location.
|
||||
3. Once the authentication with the third-party service finishes, it redirects back to the storefront with query parameters such as `code` and `state`. So, make sure your third-party service is configured to redirect to your storefront page after successful authentication.
|
||||
@@ -28,9 +30,7 @@ To login a customer with a third-party service, such as Google or GitHub, you mu
|
||||
- If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests.
|
||||
- If not, follow the rest of the steps.
|
||||
7. The storefront uses the authentication token to create the customer using the [Create Customer API route](!api!/store#customers_postcustomers).
|
||||
8. The storefront sends a request to the [Refresh Token Route](#refresh-token-route) to retrieve a new token for the customer.
|
||||
|
||||
</Details>
|
||||
8. The storefront sends a request to the [Refresh Token Route](#add-the-function-to-refresh-the-token) to retrieve a new token for the customer.
|
||||
|
||||
You'll implement the flow in this guide using Google as an example.
|
||||
|
||||
@@ -43,114 +43,67 @@ You'll implement the flow in this guide using Google as an example.
|
||||
]}
|
||||
/>
|
||||
|
||||
## JS SDK Authentication Configuration
|
||||
|
||||
Before implementing the third-party login flow, you need to configure in the JS SDK the authentication method you're using in your storefront. This defines how the JS SDK will handle sending authenticated requests after the customer is authenticated.
|
||||
|
||||
Learn more about the authentication methods and how to configure them in the [Login Customer](../login/page.mdx) guide.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Authenticate Customer in Medusa
|
||||
|
||||
When the customer clicks on a "Login with Google" button, send a request to the [Authenticate Customer API route](!api!/store#auth_postactor_typeauth_provider).
|
||||
|
||||
For example:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="authenticated-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["2", "fetch", "Send a request to the Authenticate Customer API route"],
|
||||
["10", "result.location", "If the request returns a location, redirect to that location to continue the authentication."],
|
||||
["17", "!result.token", "If the token isn't returned, the authentication has failed."],
|
||||
["26", "fetch", "Send a request as an authenticated customer."]
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const loginWithGoogle = async () => {
|
||||
const result = await fetch(
|
||||
`http://localhost:9000/auth/customer/google`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
}
|
||||
).then((res) => res.json())
|
||||
|
||||
if (result.location) {
|
||||
// redirect to Google for authentication
|
||||
window.location.href = result.location
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!result.token) {
|
||||
// result failed, show an error
|
||||
alert("Authentication failed")
|
||||
return
|
||||
}
|
||||
|
||||
// authentication successful
|
||||
// use token in the authorization header of
|
||||
// all follow up requests. For example:
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${result.token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const reactHighlights = [
|
||||
["5", "fetch", "Send a request to the Authenticate Customer API route"],
|
||||
["13", "result.location", "If the request returns a location, redirect to that location to continue the authentication."],
|
||||
["20", "!result.token", "If the token isn't returned, the authentication has failed."],
|
||||
["29", "fetch", "Send a request as an authenticated customer."]
|
||||
["7", "login", "Send a request to the Authenticate Customer API route"],
|
||||
["9", "result.location", "If the request returns a location, redirect to that location to continue the authentication."],
|
||||
["16", "", "If the token isn't returned, the authentication has failed."],
|
||||
["24", "setToken", "Set the token in the client to be used in subsequent requests."],
|
||||
["27", "retrieve", "Retrieve the customer's details as an example of testing authentication."]
|
||||
]
|
||||
|
||||
```tsx highlights={reactHighlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function Login() {
|
||||
const loginWithGoogle = async () => {
|
||||
const result = await fetch(
|
||||
`http://localhost:9000/auth/customer/google`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
}
|
||||
).then((res) => res.json())
|
||||
const result = await sdk.auth.login("customer", "google", {})
|
||||
|
||||
if (result.location) {
|
||||
if (typeof result === "object" && result.location) {
|
||||
// redirect to Google for authentication
|
||||
window.location.href = result.location
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!result.token) {
|
||||
if (typeof result !== "string") {
|
||||
// result failed, show an error
|
||||
alert("Authentication failed")
|
||||
return
|
||||
}
|
||||
|
||||
// authentication successful
|
||||
// use token in the authorization header of
|
||||
// all follow up requests. For example:
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${result.token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
// set the token in the client to be used in subsequent requests
|
||||
sdk.client.setToken(result)
|
||||
|
||||
// retrieve the customer using the token
|
||||
const { customer } = await sdk.store.customer.retrieve()
|
||||
|
||||
console.log(customer)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -159,18 +112,61 @@ export default function Login() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const jsSdkHighlights = [
|
||||
["2", "login", "Send a request to the Authenticate Customer API route"],
|
||||
["4", "", "If the request returns a location, redirect to that location to continue the authentication."],
|
||||
["11", "", "If the token isn't returned, the authentication has failed."],
|
||||
["19", "setToken", "Set the token in the client to be used in subsequent requests."],
|
||||
["22", "retrieve", "Retrieve the customer's details as an example of testing authentication."]
|
||||
]
|
||||
|
||||
```ts highlights={jsSdkHighlights}
|
||||
const loginWithGoogle = async () => {
|
||||
const result = await sdk.auth.login("customer", "google", {})
|
||||
|
||||
if (typeof result === "object" && result.location) {
|
||||
// redirect to Google for authentication
|
||||
window.location.href = result.location
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof result !== "string") {
|
||||
// result failed, show an error
|
||||
alert("Authentication failed")
|
||||
return
|
||||
}
|
||||
|
||||
// authentication successful
|
||||
// set the token in the client to be used in subsequent requests
|
||||
sdk.client.setToken(result)
|
||||
|
||||
// retrieve the customer using the token
|
||||
const { customer } = await sdk.store.customer.retrieve()
|
||||
|
||||
console.log(customer)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
If the Authenticate Customer API route returns a `location`, then you redirect to the returned page for authentication with the third-party service.
|
||||
You define a `loginWithGoogle` function that:
|
||||
|
||||
If the route returns a `token`, then the customer has been authenticated before. You can use the token for subsequent authenticated request.
|
||||
- Sends a request to the `/auth/customer/google` API route.
|
||||
- If the response is an object with a `location` property, then you redirect to the returned page for authentication with the third-party service.
|
||||
- If the response is a string, then the customer has been authenticated before. You can use the token for subsequent authenticated request.
|
||||
- To use the token for subsequent authenticated request, you must set it in the JS SDK using the `client.setToken` method.
|
||||
- Now, subsequent requests are authenticated. As an example, you can retrieve the customer's details using the `store.customer.retrieve` method.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
If you're using a provider other than Google, or if you've configured the Google provider with an ID other than `google`, replace `google` in the URL `http://localhost:9000/auth/customer/google` with your provider ID.
|
||||
If you're using a provider other than Google, or if you've configured the Google provider with an ID other than `google`, replace `google` in the parameter `login("customer", "google", {})` with your provider ID.
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -178,63 +174,38 @@ If you're using a provider other than Google, or if you've configured the Google
|
||||
|
||||
## Step 2: Callback Page in Storefront
|
||||
|
||||
The next step is to create a page in your storefront that the customer is redirected to after they authenticate with Google.
|
||||
In the previous step, you implemented as part of the login flow redirecting the customer to the third-party service for authentication.
|
||||
|
||||
You'll use this page's URL as the Redirect Uri in your Google settings, and set it in the `callbackUrl` of your Google provider's configurations.
|
||||
Once the customer authenticates with the third-party service, the service redirects the customer back to your storefront with query parameters such as `code` and `state`.
|
||||
|
||||
First, install the [react-jwt library](https://www.npmjs.com/package/react-jwt) in your storefront to use it for decoding the token:
|
||||
The next step is to create the page in your storefront that the customer is redirected to after they authenticate with Google. You'll use this page's URL as the Redirect Uri in your Google settings, and set it in the `callbackUrl` of your [Google Auth Module Provider](../../../commerce-modules/auth/auth-providers/google/page.mdx)'s configurations.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The callback page is implemented step-by-step to explain the different parts of the flow. You can copy the full page code in the [Full Code Example for Third-Party Login Callback Page](#full-code-example-for-third-party-login-callback-page) section, and then add the functions one by one to test the flow.
|
||||
|
||||
</Note>
|
||||
|
||||
### Install the React-JWT Library
|
||||
|
||||
First, install the [react-jwt library](https://www.npmjs.com/package/react-jwt) in your storefront to use it for decoding the token received from Google:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install react-jwt
|
||||
```
|
||||
|
||||
### Implement the Callback Page
|
||||
|
||||
Then, in a new page in your storefront that will be used as the callback / redirect uri destination, add the following:
|
||||
|
||||
<CodeTabs group="authenticated-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const sendCallbackFetchHighlights = [
|
||||
["6", "queryParams", "The query parameters received from Google, such as `code` and `state`."],
|
||||
["10", "fetch", "Send a request to the Validate Authentication Callback API route"],
|
||||
["18", "!token", "If the token isn't returned, the authentication has failed."],
|
||||
]
|
||||
|
||||
```ts highlights={sendCallbackFetchHighlights}
|
||||
import { decodeToken } from "react-jwt"
|
||||
|
||||
// ...
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search)
|
||||
const queryParams = Object.fromEntries(searchParams.entries())
|
||||
|
||||
const sendCallback = async () => {
|
||||
const { token } = await fetch(
|
||||
`http://localhost:9000/auth/customer/google/callback?${new URLSearchParams(queryParams).toString()}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
}
|
||||
).then((res) => res.json())
|
||||
|
||||
if (!token) {
|
||||
alert("Authentication Failed")
|
||||
return
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// TODO add more functions...
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const sendCallbackReactHighlights = [
|
||||
["11", "code", "The code received from Google as a query parameter."],
|
||||
["11", "state", "The state received from Google as a query parameter."],
|
||||
["20", "fetch", "Send a request to the Validate Authentication Callback API route"],
|
||||
["28", "!token", "If the token isn't returned, the authentication has failed."],
|
||||
["12", "queryParams", "The query parameters received from Google, such as `code` and `state`."],
|
||||
["21", "callback", "Send a request to the Validate Authentication Callback API route"],
|
||||
["28", "catch", "If an error occurs, show an alert and exit execution."],
|
||||
["36", "setToken", "Set the token in the client to be used in subsequent requests."]
|
||||
]
|
||||
|
||||
```tsx highlights={sendCallbackReactHighlights}
|
||||
@@ -243,6 +214,7 @@ export const sendCallbackReactHighlights = [
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { decodeToken } from "react-jwt"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function GoogleCallback() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -254,19 +226,26 @@ export default function GoogleCallback() {
|
||||
}, [])
|
||||
|
||||
const sendCallback = async () => {
|
||||
const { token } = await fetch(
|
||||
`http://localhost:9000/auth/customer/google/callback?${new URLSearchParams(queryParams).toString()}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
}
|
||||
).then((res) => res.json())
|
||||
let token = ""
|
||||
|
||||
if (!token) {
|
||||
try {
|
||||
token = await sdk.auth.callback(
|
||||
"customer",
|
||||
"google",
|
||||
// pass all query parameters received from the
|
||||
// third party provider
|
||||
queryParams
|
||||
)
|
||||
} catch (error) {
|
||||
alert("Authentication Failed")
|
||||
return
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
// set the token in the client
|
||||
// to be used in subsequent requests
|
||||
sdk.client.setToken(token)
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
@@ -282,57 +261,95 @@ export default function GoogleCallback() {
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
This adds in the new page the function `sendCallback` which sends a request to the [Validate Callback API route](!api!/store#auth_postactor_typeauth_providercallback), passing it the query parameters received from Google.
|
||||
|
||||
Then, replace the `TODO` with the following:
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const createCustomerHighlights = [
|
||||
["1", "token", "The token received from the Validate Callback API route."],
|
||||
["2", "fetch", "Create a customer"]
|
||||
export const sendCallbackFetchHighlights = [
|
||||
["6", "queryParams", "The query parameters received from Google, such as `code` and `state`."],
|
||||
["12", "callback", "Send a request to the Validate Authentication Callback API route"],
|
||||
["19", "catch", "If an error occurs, show an alert and exit execution."],
|
||||
["27", "setToken", "Set the token in the client to be used in subsequent requests."]
|
||||
]
|
||||
|
||||
```ts highlights={createCustomerHighlights} title="Fetch API / React Applicable"
|
||||
const createCustomer = async (token: string) => {
|
||||
await fetch(`http://localhost:9000/store/customers`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
// TODO show form to retrieve email from customer
|
||||
email: "example@medusajs.com",
|
||||
}),
|
||||
}).then((res) => res.json())
|
||||
```ts highlights={sendCallbackFetchHighlights}
|
||||
import { decodeToken } from "react-jwt"
|
||||
|
||||
// ...
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search)
|
||||
const queryParams = Object.fromEntries(searchParams.entries())
|
||||
|
||||
const sendCallback = async () => {
|
||||
let token = ""
|
||||
|
||||
try {
|
||||
token = await sdk.auth.callback(
|
||||
"customer",
|
||||
"google",
|
||||
// pass all query parameters received from the
|
||||
// third party provider
|
||||
queryParams
|
||||
)
|
||||
} catch (error) {
|
||||
alert("Authentication Failed")
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
// set the token in the client
|
||||
// to be used in subsequent requests
|
||||
sdk.client.setToken(token)
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// TODO add more functions...
|
||||
```
|
||||
|
||||
This adds to the page the function `createCustomer` which, if the customer is new, it uses the token received from the Validate Callback API route to create a new customer.
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
This adds in the new page the function `sendCallback` which sends a request to the [Validate Callback API route](!api!/store#auth_postactor_typeauth_providercallback), passing it all query parameters received from Google. Those include the `code` and `state` parameters.
|
||||
|
||||
After that, you set the token in the JS SDK using the `client.setToken` method. This ensures that the token is passed to subsequent requests, such as the request to create the customer.
|
||||
|
||||
### Add the Function to Create a Customer
|
||||
|
||||
Next, replace the `TODO` after the `sendCallback` function with the following:
|
||||
|
||||
export const createCustomerHighlights = [
|
||||
["3", "create", "Create a customer"]
|
||||
]
|
||||
|
||||
```ts highlights={createCustomerHighlights} title="JS SDK / React Applicable"
|
||||
const createCustomer = async () => {
|
||||
// create customer
|
||||
await sdk.store.customer.create({
|
||||
email: "example@medusajs.com",
|
||||
})
|
||||
}
|
||||
|
||||
// TODO add more functions...
|
||||
```
|
||||
|
||||
This adds to the page the function `createCustomer` which creates a customer if this is the first time the customer is authenticating with the third-party service.
|
||||
|
||||
Notice that this method assumes that the token received from the [Validate Callback API route](!api!/store#auth_postactor_typeauth_providercallback) is already set in the JS SDK, as done at the end of the `sendCallback` function. So, if you're implemeting this flow without using the JS SDK, make sure to pass the token in the authorization Bearer header.
|
||||
|
||||
### Add the Function to Refresh the Token
|
||||
|
||||
Next, replace the new `TODO` with the following:
|
||||
|
||||
export const refreshTokenHighlights = [
|
||||
["1", "token", "The token received from the Validate Callback API route."],
|
||||
["2", "fetch", "Fetch a new token for the created customer."]
|
||||
["3", "refresh", "Fetch a new token for the created customer."]
|
||||
]
|
||||
|
||||
```ts highlights={refreshTokenHighlights} title="Fetch API / React Applicable"
|
||||
const refreshToken = async (token: string) => {
|
||||
const result = await fetch(`http://localhost:9000/auth/token/refresh`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
|
||||
return result.token
|
||||
```ts highlights={refreshTokenHighlights} title="JS SDK / React Applicable"
|
||||
const refreshToken = async () => {
|
||||
// refresh the token
|
||||
const result = await sdk.auth.refresh()
|
||||
|
||||
// set the new token
|
||||
sdk.client.setToken(result)
|
||||
}
|
||||
|
||||
// TODO add more functions...
|
||||
@@ -340,48 +357,15 @@ const refreshToken = async (token: string) => {
|
||||
|
||||
This adds to the page the function `refreshToken` which is used after the new customer is created to refresh their authentication token. This ensures that the authentication token includes the details of the created customer.
|
||||
|
||||
Notice that this method assumes that the token received from the [Validate Callback API route](!api!/store#auth_postactor_typeauth_providercallback) is already set in the JS SDK, as done at the end of the `sendCallback` function. So, if you're implemeting this flow without using the JS SDK, make sure to pass the token in the authorization Bearer header.
|
||||
|
||||
Then, this method also sets the new token in the JS SDK to be used in subsequent authenticated requests.
|
||||
|
||||
### Add the Function to Validate the Callback
|
||||
|
||||
Finally, add in the place of the new `TODO` the `validateCallback` function that runs when the page first loads to validate the authentication:
|
||||
|
||||
<CodeTabs group="authenticated-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const validateFetchHighlights = [
|
||||
["2", "sendCallback", "Validate the callback in Medusa and retrieve the authentication token"],
|
||||
["4", "shouldCreateCustomer", "Check if the decoded token has an `actor_id` property to decide whether a customer to be created."],
|
||||
["7", "createCustomer", "Create a customer if the decoded token doesn't have `actor_id`."],
|
||||
["9", "refreshToken", "Fetch a new token for the created customer."],
|
||||
["13", "fetch", "Send an authenticated request using the token."]
|
||||
]
|
||||
|
||||
```ts highlights={validateFetchHighlights}
|
||||
const validateCallback = async () => {
|
||||
let { token } = await sendCallback()
|
||||
|
||||
const shouldCreateCustomer = (decodeToken(token) as { actor_id: string }).actor_id === ""
|
||||
|
||||
if (shouldCreateCustomer) {
|
||||
await createCustomer(token)
|
||||
|
||||
token = await refreshToken(token)
|
||||
}
|
||||
|
||||
// use token to send authenticated requests
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
).then((res) => res.json())
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const validateReactHighlights = [
|
||||
@@ -389,34 +373,23 @@ export const validateReactHighlights = [
|
||||
["4", "shouldCreateCustomer", "Check if the decoded token has an `actor_id` property to decide whether a customer to be created."],
|
||||
["7", "createCustomer", "Create a customer if the decoded token doesn't have `actor_id`."],
|
||||
["9", "refreshToken", "Fetch a new token for the created customer."],
|
||||
["13", "fetch", "Send an authenticated request using the token."]
|
||||
["13", "retrieve", "Retrieve the customer's details as an example of testing authentication."]
|
||||
]
|
||||
|
||||
```tsx highlights={validateReactHighlights}
|
||||
const validateCallback = async () => {
|
||||
let { token } = await sendCallback()
|
||||
const token = await sendCallback()
|
||||
|
||||
const shouldCreateCustomer = (decodeToken(token) as { actor_id: string }).actor_id === ""
|
||||
|
||||
if (shouldCreateCustomer) {
|
||||
await createCustomer(token)
|
||||
await createCustomer()
|
||||
|
||||
token = await refreshToken(token)
|
||||
await refreshToken()
|
||||
}
|
||||
|
||||
// use token to send authenticated requests
|
||||
const { customer: customerData } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
).then((res) => res.json())
|
||||
const { customer: customerData } = await sdk.store.customer.retrieve()
|
||||
|
||||
setCustomer(customerData)
|
||||
setLoading(false)
|
||||
@@ -430,6 +403,37 @@ useEffect(() => {
|
||||
|
||||
validateCallback()
|
||||
}, [loading])
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const validateFetchHighlights = [
|
||||
["2", "sendCallback", "Validate the callback in Medusa and retrieve the authentication token"],
|
||||
["4", "shouldCreateCustomer", "Check if the decoded token has an `actor_id` property to decide whether a customer to be created."],
|
||||
["7", "createCustomer", "Create a customer if the decoded token doesn't have `actor_id`."],
|
||||
["9", "refreshToken", "Fetch a new token for the created customer."],
|
||||
["13", "retrieve", "Retrieve the customer's details as an example of testing authentication."]
|
||||
]
|
||||
|
||||
```ts highlights={validateFetchHighlights}
|
||||
const validateCallback = async () => {
|
||||
const token = await sendCallback()
|
||||
|
||||
const shouldCreateCustomer = (decodeToken(token) as { actor_id: string }).actor_id === ""
|
||||
|
||||
if (shouldCreateCustomer) {
|
||||
await createCustomer()
|
||||
|
||||
await refreshToken()
|
||||
}
|
||||
|
||||
// use token to send authenticated requests
|
||||
const { customer: customerData } = await sdk.store.customer.retrieve()
|
||||
|
||||
setCustomer(customerData)
|
||||
setLoading(false)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
@@ -437,20 +441,22 @@ useEffect(() => {
|
||||
|
||||
The `validateCallback` function uses the functions added earlier to:
|
||||
|
||||
1. Send a request to the Validate Callback API route, which returns an authentication token.
|
||||
1. Send a request to the [Validate Callback API route](!api!/store#auth_postactor_typeauth_providercallback), which returns an authentication token.
|
||||
- The `sendCallback` function also sets the token in the JS SDK to be passed in subsequent requests.
|
||||
2. Decodes the token to check if it has an `actor_id` property.
|
||||
- If so, then the customer is previously registered, and the authentication token can be used for subsequent authenticated requests.
|
||||
- If not:
|
||||
1. Create a customer using the Create Customer API route.
|
||||
2. Refetch the customer's token after it's created using the Refresh Token API route.
|
||||
- If not, this is the first time the customer is authenticating with the third-party service, so:
|
||||
1. Create a customer using the [Create Customer API route](!api!/store#customers_postcustomers).
|
||||
2. Refetch the customer's authentication token after it's created using the [Refresh Token API route](!api!/store#auth_postactor_typeauth_providerrefresh).
|
||||
3. Use the token for subsequent authenticated requests.
|
||||
3. Retrieve the customer's details as an example of testing authentication.
|
||||
|
||||
### Full Code Example for Callback Page
|
||||
The customer is now authenticated, and you can redirect them to the home page or the page they were trying to access before logging in.
|
||||
|
||||
<Details summaryContent="Full Example">
|
||||
### Full Code Example for Third-Party Login Callback Page
|
||||
|
||||
<CodeTabs group="authenticated-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
```ts
|
||||
import { decodeToken } from "react-jwt"
|
||||
@@ -463,74 +469,60 @@ const state = queryParams.get("state")
|
||||
|
||||
|
||||
const sendCallback = async () => {
|
||||
const { token } = await fetch(
|
||||
`http://localhost:9000/auth/customer/google/callback?code=${code}&state=${state}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
}
|
||||
).then((res) => res.json())
|
||||
let token = ""
|
||||
|
||||
if (!token) {
|
||||
try {
|
||||
token = await sdk.auth.callback(
|
||||
"customer",
|
||||
"google",
|
||||
// pass all query parameters received from the
|
||||
// third party provider
|
||||
queryParams
|
||||
)
|
||||
} catch (error) {
|
||||
alert("Authentication Failed")
|
||||
return
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
// set the token in the client
|
||||
// to be used in subsequent requests
|
||||
sdk.client.setToken(token)
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
const createCustomer = async (token: string) => {
|
||||
await fetch(`http://localhost:9000/store/customers`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
// TODO show form to retrieve email from customer
|
||||
email: "example@medusajs.com",
|
||||
}),
|
||||
}).then((res) => res.json())
|
||||
const createCustomer = async () => {
|
||||
// create customer
|
||||
await sdk.store.customer.create({
|
||||
email: "example@medusajs.com",
|
||||
})
|
||||
}
|
||||
|
||||
const refreshToken = async (token: string) => {
|
||||
const result = await fetch(`http://localhost:9000/auth/token/refresh`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
|
||||
return result.token
|
||||
const refreshToken = async () => {
|
||||
// refresh the token
|
||||
const result = await sdk.auth.refresh()
|
||||
|
||||
// set the new token
|
||||
sdk.client.setToken(result)
|
||||
}
|
||||
|
||||
const validateCallback = async () => {
|
||||
let { token } = await sendCallback()
|
||||
const token = await sendCallback()
|
||||
|
||||
const shouldCreateCustomer = (decodeToken(token) as { actor_id: string }).actor_id === ""
|
||||
|
||||
if (shouldCreateCustomer) {
|
||||
await createCustomer(token)
|
||||
await createCustomer()
|
||||
|
||||
token = await refreshToken(token)
|
||||
await refreshToken()
|
||||
}
|
||||
|
||||
// use token to send authenticated requests
|
||||
const { customer } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
).then((res) => res.json())
|
||||
const { customer: customerData } = await sdk.store.customer.retrieve()
|
||||
|
||||
setCustomer(customerData)
|
||||
setLoading(false)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -543,88 +535,69 @@ const validateCallback = async () => {
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { decodeToken } from "react-jwt"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function GoogleCallback() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [customer, setCustomer] = useState<HttpTypes.StoreCustomer>()
|
||||
// for other than Next.js
|
||||
const { code, state } = useMemo(() => {
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
return {
|
||||
code: queryParams.get("code"),
|
||||
state: queryParams.get("state"),
|
||||
}
|
||||
const queryParams = useMemo(() => {
|
||||
const searchParams = new URLSearchParams(window.location.search)
|
||||
return Object.fromEntries(searchParams.entries())
|
||||
}, [])
|
||||
|
||||
const sendCallback = async () => {
|
||||
const { token } = await fetch(
|
||||
`http://localhost:9000/auth/customer/google/callback?code=${code}&state=${state}`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
}
|
||||
).then((res) => res.json())
|
||||
let token = ""
|
||||
|
||||
if (!token) {
|
||||
try {
|
||||
token = await sdk.auth.callback(
|
||||
"customer",
|
||||
"google",
|
||||
// pass all query parameters received from the
|
||||
// third party provider
|
||||
queryParams
|
||||
)
|
||||
} catch (error) {
|
||||
alert("Authentication Failed")
|
||||
return
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
// set the token in the client
|
||||
// to be used in subsequent requests
|
||||
sdk.client.setToken(token)
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
const createCustomer = async (token: string) => {
|
||||
await fetch(`http://localhost:9000/store/customers`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
// TODO show form to retrieve email from customer
|
||||
email: "example@medusajs.com",
|
||||
}),
|
||||
}).then((res) => res.json())
|
||||
const createCustomer = async () => {
|
||||
// create customer
|
||||
await sdk.store.customer.create({
|
||||
email: "example@medusajs.com",
|
||||
})
|
||||
}
|
||||
|
||||
const refreshToken = async (token: string) => {
|
||||
const result = await fetch(`http://localhost:9000/auth/token/refresh`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
|
||||
return result.token
|
||||
const refreshToken = async () => {
|
||||
// refresh the token
|
||||
const result = await sdk.auth.refresh()
|
||||
|
||||
// set the new token
|
||||
sdk.client.setToken(result)
|
||||
}
|
||||
|
||||
const validateCallback = async () => {
|
||||
let { token } = await sendCallback()
|
||||
const token = await sendCallback()
|
||||
|
||||
const shouldCreateCustomer = (decodeToken(token) as { actor_id: string }).actor_id === ""
|
||||
|
||||
if (shouldCreateCustomer) {
|
||||
await createCustomer(token)
|
||||
await createCustomer()
|
||||
|
||||
token = await refreshToken(token)
|
||||
await refreshToken()
|
||||
}
|
||||
|
||||
// use token to send authenticated requests
|
||||
const { customer: customerData } = await fetch(
|
||||
`http://localhost:9000/store/customers/me`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
).then((res) => res.json())
|
||||
const { customer: customerData } = await sdk.store.customer.retrieve()
|
||||
|
||||
setCustomer(customerData)
|
||||
setLoading(false)
|
||||
@@ -649,5 +622,3 @@ export default function GoogleCallback() {
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
</Details>
|
||||
Reference in new issue
Block a user