docs: fix authentication callback descriptions to include all query parameters (#10932)

This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-01-13 15:12:06 +02:00
1 parent 55ff7e4dcd
commit 4250c292eb
18 files changed
+178 -188

No files matched your search

@@ -43374,9 +43374,7 @@ paths:
operationId: PostActor_typeAuth_providerCallback operationId: PostActor_typeAuth_providerCallback
summary: Validate Authentication Callback summary: Validate Authentication Callback
description: | description: |
This API route is used by your dashboard or frontend application when a third-party provider redirects to it after authentication. This API route is used by your dashboard or frontend application when a third-party provider redirects to it after authentication. It validates the authentication with the third-party provider and, if successful, returns an authentication token. All query parameters received from the third-party provider, such as `code`, `state`, and `error`, must be passed as query parameters to this route.
It validates the authentication with the third-party provider and, if successful, returns an authentication token.
You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the frontend. If the decoded data doesn't have an `actor_id` property, then you must create a user, typically using the Accept Invite route passing the token in the request's Authorization header. You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the frontend. If the decoded data doesn't have an `actor_id` property, then you must create a user, typically using the Accept Invite route passing the token in the request's Authorization header.
externalDocs: externalDocs:
@@ -3,11 +3,11 @@ post:
summary: Validate Authentication Callback summary: Validate Authentication Callback
description: > description: >
This API route is used by your dashboard or frontend application when a This API route is used by your dashboard or frontend application when a
third-party provider redirects to it after authentication. third-party provider redirects to it after authentication. It validates the
authentication with the third-party provider and, if successful, returns an
authentication token. All query parameters received from the third-party
It validates the authentication with the third-party provider and, if provider, such as `code`, `state`, and `error`, must be passed as query
successful, returns an authentication token. parameters to this route.
You can decode the JWT token using libraries like You can decode the JWT token using libraries like
@@ -219,10 +219,7 @@ paths:
operationId: PostActor_typeAuth_providerCallback operationId: PostActor_typeAuth_providerCallback
summary: Validate Authentication Callback summary: Validate Authentication Callback
description: | description: |
This API route is used by your storefront or frontend application when a third-party provider redirects to it after authentication. This API route is used by your storefront or frontend application when a third-party provider redirects to it after authentication. It validates the authentication with the third-party provider and, if successful, returns an authentication token. All query parameters received from the third-party provider, such as `code`, `state`, and `error`, must be passed as query parameters to this route.
It validates the authentication with the third-party provider and, if successful, returns an authentication token.
You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the storefront. If the decoded data doesn't have an `actor_id` property, then you must register the customer using the Create Customer API route passing the token in the request's Authorization header. You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the storefront. If the decoded data doesn't have an `actor_id` property, then you must register the customer using the Create Customer API route passing the token in the request's Authorization header.
externalDocs: externalDocs:
url: https://docs.medusajs.com/v2/storefront-development/customers/third-party-login url: https://docs.medusajs.com/v2/storefront-development/customers/third-party-login
@@ -3,12 +3,11 @@ post:
summary: Validate Authentication Callback summary: Validate Authentication Callback
description: > description: >
This API route is used by your storefront or frontend application when a This API route is used by your storefront or frontend application when a
third-party provider redirects to it after authentication. third-party provider redirects to it after authentication. It validates the
authentication with the third-party provider and, if successful, returns an
authentication token. All query parameters received from the third-party
It validates the authentication with the third-party provider and, if provider, such as `code`, `state`, and `error`, must be passed as query
successful, returns an authentication token. parameters to this route.
You can decode the JWT token using libraries like You can decode the JWT token using libraries like
[react-jwt](https://www.npmjs.com/package/react-jwt) in the storefront. If [react-jwt](https://www.npmjs.com/package/react-jwt) in the storefront. If
@@ -144,7 +144,7 @@ const { success, authIdentity, location } = await authModuleService.authenticate
// passed to auth provider // passed to auth provider
{ {
// ... // ...
callback_url: "example.com" callback_url: "example.com",
} }
) )
``` ```
@@ -176,6 +176,12 @@ if (success) {
} }
``` ```
<Note title="Tip">
For providers like Google, the `query` object contains the query parameters from the original callback URL, such as the `code` and `state` parameters.
</Note>
If the returned `success` property is `true`, the authentication with the third-party provider was successful. If the returned `success` property is `true`, the authentication with the third-party provider was successful.
![Diagram showcasing the second part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711375123/Medusa%20Resources/third-party-auth-2_kmjxju.jpg) ![Diagram showcasing the second part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711375123/Medusa%20Resources/third-party-auth-2_kmjxju.jpg)
@@ -41,7 +41,7 @@ module.exports = defineConfig({
}, },
], ],
}, },
} },
], ],
}) })
``` ```
@@ -60,7 +60,7 @@ module.exports = defineConfig({
}, },
], ],
}, },
} },
], ],
}) })
``` ```
@@ -63,7 +63,7 @@ module.exports = defineConfig({
], ],
}, },
}, },
} },
], ],
}) })
``` ```
@@ -59,7 +59,7 @@ It requires the following steps:
1. Authenticate the user with the [Auth Route](#login-route). 1. Authenticate the user with the [Auth Route](#login-route).
2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a storefront), when it receives a `location` property in the response, must redirect to the returned location. 2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a 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 frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication. 3. Once the authentication with the third-party service finishes, it redirects back to the frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication.
4. The frontend sends a request to the [Callback Route](#callback-route) passing the `code` query parameter. 4. The frontend sends a request to the [Validate Callback Route](#validate-callback-route) passing it the query parameters received from the third-party service, such as the `code` and `state` query parameters.
5. If the callback validation is successful, the frontend receives the authentication token. 5. If the callback validation is successful, the frontend receives the authentication token.
6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt). 6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt).
- If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests. - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests.
@@ -221,7 +221,7 @@ Redirect to that URL in the frontend to continue the authentication process with
The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google. The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google.
```bash ```bash
curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123 curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123&state=456
``` ```
<Note title="Tip"> <Note title="Tip">
@@ -239,7 +239,7 @@ Its path parameters are:
### Query Parameters ### Query Parameters
This route accepts a `code` query parameter, which is the code received from the third-party provider. This route accepts all the query parameters that the third-party service sends to the frontend after the user completes the authentication process, such as the `code` and `state` query parameters.
### Response Fields ### Response Fields
@@ -54,7 +54,7 @@ export const multiPartsHighlights1 = [
```ts highlights={multiPartsHighlights1} ```ts highlights={multiPartsHighlights1}
import { import {
createInventoryItemsWorkflow, createInventoryItemsWorkflow,
useQueryGraphStep useQueryGraphStep,
} from "@medusajs/medusa/core-flows" } from "@medusajs/medusa/core-flows"
import { createWorkflow } from "@medusajs/framework/workflows-sdk" import { createWorkflow } from "@medusajs/framework/workflows-sdk"
@@ -66,8 +66,8 @@ export const createMultiPartProductsWorkflow = createWorkflow(
entity: "stock_location", entity: "stock_location",
fields: ["*"], fields: ["*"],
filters: { filters: {
name: "European Warehouse" name: "European Warehouse",
} },
}) })
const inventoryItems = createInventoryItemsWorkflow.runAsStep({ const inventoryItems = createInventoryItemsWorkflow.runAsStep({
@@ -79,9 +79,9 @@ export const createMultiPartProductsWorkflow = createWorkflow(
location_levels: [ location_levels: [
{ {
stocked_quantity: 100, stocked_quantity: 100,
location_id: stockLocations[0].id location_id: stockLocations[0].id,
} },
] ],
}, },
{ {
sku: "WHEEL", sku: "WHEEL",
@@ -89,9 +89,9 @@ export const createMultiPartProductsWorkflow = createWorkflow(
location_levels: [ location_levels: [
{ {
stocked_quantity: 100, stocked_quantity: 100,
location_id: stockLocations[0].id location_id: stockLocations[0].id,
} },
] ],
}, },
{ {
sku: "SEAT", sku: "SEAT",
@@ -99,12 +99,12 @@ export const createMultiPartProductsWorkflow = createWorkflow(
location_levels: [ location_levels: [
{ {
stocked_quantity: 100, stocked_quantity: 100,
location_id: stockLocations[0].id location_id: stockLocations[0].id,
} },
] ],
} },
] ],
} },
}) })
// TODO create the product // TODO create the product
@@ -127,11 +127,11 @@ export const multiPartHighlights2 = [
```ts highlights={multiPartHighlights2} ```ts highlights={multiPartHighlights2}
import { import {
// ... // ...
transform transform,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { import {
// ... // ...
createProductsWorkflow createProductsWorkflow,
} from "@medusajs/medusa/core-flows" } from "@medusajs/medusa/core-flows"
export const createMultiPartProductsWorkflow = createWorkflow( export const createMultiPartProductsWorkflow = createWorkflow(
@@ -140,7 +140,7 @@ export const createMultiPartProductsWorkflow = createWorkflow(
// ... // ...
const inventoryItemIds = transform({ const inventoryItemIds = transform({
inventoryItems inventoryItems,
}, (data) => { }, (data) => {
return data.inventoryItems.map((inventoryItem) => { return data.inventoryItems.map((inventoryItem) => {
return { return {
@@ -161,24 +161,24 @@ export const createMultiPartProductsWorkflow = createWorkflow(
prices: [ prices: [
{ {
amount: 100, amount: 100,
currency_code: "usd" currency_code: "usd",
} },
], ],
options: { options: {
"Default Option": "Default Variant" "Default Option": "Default Variant",
}, },
inventory_items: inventoryItemIds inventory_items: inventoryItemIds,
} },
], ],
options: [ options: [
{ {
title: "Default Option", title: "Default Option",
values: ["Default Variant"] values: ["Default Variant"],
} },
] ],
} },
] ],
} },
}) })
} }
) )
@@ -235,21 +235,21 @@ export const createBundledProducts = createWorkflow(
prices: [ prices: [
{ {
amount: 10, amount: 10,
currency_code: "usd" currency_code: "usd",
} },
], ],
options: { options: {
"Default Option": "Default Variant" "Default Option": "Default Variant",
}, },
manage_inventory: true manage_inventory: true,
} },
], ],
options: [ options: [
{ {
title: "Default Option", title: "Default Option",
values: ["Default Variant"] values: ["Default Variant"],
} },
] ],
}, },
{ {
title: "Pants", title: "Pants",
@@ -259,21 +259,21 @@ export const createBundledProducts = createWorkflow(
prices: [ prices: [
{ {
amount: 10, amount: 10,
currency_code: "usd" currency_code: "usd",
} },
], ],
options: { options: {
"Default Option": "Default Variant" "Default Option": "Default Variant",
}, },
manage_inventory: true manage_inventory: true,
} },
], ],
options: [ options: [
{ {
title: "Default Option", title: "Default Option",
values: ["Default Variant"] values: ["Default Variant"],
} },
] ],
}, },
{ {
title: "Shoes", title: "Shoes",
@@ -283,24 +283,24 @@ export const createBundledProducts = createWorkflow(
prices: [ prices: [
{ {
amount: 10, amount: 10,
currency_code: "usd" currency_code: "usd",
} },
], ],
options: { options: {
"Default Option": "Default Variant" "Default Option": "Default Variant",
}, },
manage_inventory: true manage_inventory: true,
} },
], ],
options: [ options: [
{ {
title: "Default Option", title: "Default Option",
values: ["Default Variant"] values: ["Default Variant"],
} },
] ],
} },
] ],
} },
}) })
// TODO re-retrieve with inventory // TODO re-retrieve with inventory
@@ -321,10 +321,10 @@ export const bundledHighlights2 = [
```ts highlights={bundledHighlights2} ```ts highlights={bundledHighlights2}
import { import {
// ... // ...
transform transform,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { import {
useQueryGraphStep useQueryGraphStep,
} from "@medusajs/medusa/core-flows" } from "@medusajs/medusa/core-flows"
export const createBundledProducts = createWorkflow( export const createBundledProducts = createWorkflow(
@@ -332,7 +332,7 @@ export const createBundledProducts = createWorkflow(
() => { () => {
// ... // ...
const productIds = transform({ const productIds = transform({
products products,
}, (data) => data.products.map((product) => product.id)) }, (data) => data.products.map((product) => product.id))
// @ts-ignore // @ts-ignore
@@ -340,19 +340,19 @@ export const createBundledProducts = createWorkflow(
entity: "product", entity: "product",
fields: [ fields: [
"variants.*", "variants.*",
"variants.inventory_items.*" "variants.inventory_items.*",
], ],
filters: { filters: {
id: productIds id: productIds,
} },
}) })
const inventoryItemIds = transform({ const inventoryItemIds = transform({
productsWithInventory productsWithInventory,
}, (data) => { }, (data) => {
return data.productsWithInventory.map((product) => { return data.productsWithInventory.map((product) => {
return { return {
inventory_item_id: product.variants[0].inventory_items?.[0]?.inventory_item_id inventory_item_id: product.variants[0].inventory_items?.[0]?.inventory_item_id,
} }
}) })
}) })
@@ -387,24 +387,24 @@ export const createBundledProducts = createWorkflow(
prices: [ prices: [
{ {
amount: 30, amount: 30,
currency_code: "usd" currency_code: "usd",
} },
], ],
options: { options: {
"Default Option": "Default Variant" "Default Option": "Default Variant",
}, },
inventory_items: inventoryItemIds, inventory_items: inventoryItemIds,
} },
], ],
options: [ options: [
{ {
title: "Default Option", title: "Default Option",
values: ["Default Variant"] values: ["Default Variant"],
} },
] ],
} },
] ],
} },
}).config({ name: "create-bundled-product" }) }).config({ name: "create-bundled-product" })
} }
) )
@@ -36,15 +36,15 @@ For example:
<CodeTab label="Using Workflow" value="workflow"> <CodeTab label="Using Workflow" value="workflow">
```ts ```ts
import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"; import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
// ... // ...
await createPaymentCollectionForCartWorkflow(req.scope) await createPaymentCollectionForCartWorkflow(req.scope)
.run({ .run({
input: { input: {
cart_id: "cart_123" cart_id: "cart_123",
} },
}) })
``` ```
@@ -77,7 +77,7 @@ For example:
<CodeTab label="Using Workflow" value="workflow"> <CodeTab label="Using Workflow" value="workflow">
```ts ```ts
import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"; import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
// ... // ...
@@ -86,7 +86,7 @@ const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
input: { input: {
payment_collection_id: "paycol_123", payment_collection_id: "paycol_123",
provider_id: "stripe", provider_id: "stripe",
} },
}) })
``` ```
@@ -124,13 +124,13 @@ For example:
<CodeTab label="Using Step" value="workflow"> <CodeTab label="Using Step" value="workflow">
```ts ```ts
import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"; import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
// ... // ...
authorizePaymentSessionStep({ authorizePaymentSessionStep({
id: "payses_123", id: "payses_123",
context: {} context: {},
}) })
``` ```
@@ -140,7 +140,7 @@ authorizePaymentSessionStep({
```ts ```ts
const payment = authorizePaymentSessionStep({ const payment = authorizePaymentSessionStep({
id: "payses_123", id: "payses_123",
context: {} context: {},
}) })
``` ```
@@ -273,7 +273,7 @@ import { defineLink } from "@medusajs/framework/utils"
export default defineLink( export default defineLink(
{ {
linkable: DigitalProductModule.linkable.digitalProduct, linkable: DigitalProductModule.linkable.digitalProduct,
deleteCascade: true deleteCascade: true,
}, },
ProductModule.linkable.productVariant ProductModule.linkable.productVariant
) )
@@ -1534,17 +1534,17 @@ export const retrieveDigitalProductsToDeleteStep = createStep(
const query = container.resolve("query") const query = container.resolve("query")
const productVariants = await productService.listProductVariants({ const productVariants = await productService.listProductVariants({
product_id: product_id product_id: product_id,
}, { }, {
withDeleted: true withDeleted: true,
}) })
const { data } = await query.graph({ const { data } = await query.graph({
entity: DigitalProductVariantLink.entryPoint, entity: DigitalProductVariantLink.entryPoint,
fields: ["digital_product.*"], fields: ["digital_product.*"],
filters: { filters: {
product_variant_id: productVariants.map((v) => v.id) product_variant_id: productVariants.map((v) => v.id),
} },
}) })
const digitalProductIds = data.map((d) => d.digital_product.id) const digitalProductIds = data.map((d) => d.digital_product.id)
@@ -1612,9 +1612,9 @@ You can now create the workflow that executes those steps.
Create the file `src/workflows/delete-product-digital-products/index.ts` with the following content: Create the file `src/workflows/delete-product-digital-products/index.ts` with the following content:
```ts title="src/workflows/delete-product-digital-products/index.ts" ```ts title="src/workflows/delete-product-digital-products/index.ts"
import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk"; import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { deleteDigitalProductsSteps } from "./steps/delete-digital-products"; import { deleteDigitalProductsSteps } from "./steps/delete-digital-products"
import { retrieveDigitalProductsToDeleteStep } from "./steps/retrieve-digital-products-to-delete"; import { retrieveDigitalProductsToDeleteStep } from "./steps/retrieve-digital-products-to-delete"
type DeleteProductDigitalProductsInput = { type DeleteProductDigitalProductsInput = {
id: string id: string
@@ -1624,11 +1624,11 @@ export const deleteProductDigitalProductsWorkflow = createWorkflow(
"delete-product-digital-products", "delete-product-digital-products",
(input: DeleteProductDigitalProductsInput) => { (input: DeleteProductDigitalProductsInput) => {
const digitalProductsToDelete = retrieveDigitalProductsToDeleteStep({ const digitalProductsToDelete = retrieveDigitalProductsToDeleteStep({
product_id: input.id product_id: input.id,
}) })
deleteDigitalProductsSteps({ deleteDigitalProductsSteps({
ids: digitalProductsToDelete ids: digitalProductsToDelete,
}) })
return new WorkflowResponse({}) return new WorkflowResponse({})
@@ -1656,10 +1656,10 @@ So, you'll listen to the `product.deleted` event in a subscriber, and execute th
Create the file `src/subscribers/handle-product-deleted.ts` with the following content: Create the file `src/subscribers/handle-product-deleted.ts` with the following content:
```ts title="src/subscribers/handle-product-deleted.ts" ```ts title="src/subscribers/handle-product-deleted.ts"
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"; import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
import { import {
deleteProductDigitalProductsWorkflow deleteProductDigitalProductsWorkflow,
} from "../workflows/delete-product-digital-products"; } from "../workflows/delete-product-digital-products"
export default async function handleProductDeleted({ export default async function handleProductDeleted({
event: { data }, event: { data },
@@ -1711,7 +1711,7 @@ import {
FulfillmentDTO, FulfillmentDTO,
FulfillmentItemDTO, FulfillmentItemDTO,
FulfillmentOption, FulfillmentOption,
FulfillmentOrderDTO FulfillmentOrderDTO,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
class DigitalProductFulfillmentService extends AbstractFulfillmentProviderService { class DigitalProductFulfillmentService extends AbstractFulfillmentProviderService {
@@ -1750,7 +1750,7 @@ class DigitalProductFulfillmentService extends AbstractFulfillmentProviderServic
// No data is being sent anywhere // No data is being sent anywhere
return { return {
data, data,
labels: [] labels: [],
} }
} }
@@ -2606,7 +2606,7 @@ In `src/types/global.ts`, add the following types that you’ll use in your cust
```ts title="src/types/global.ts" ```ts title="src/types/global.ts"
import { import {
// other imports... // other imports...
StoreProductVariant StoreProductVariant,
} from "@medusajs/types" } from "@medusajs/types"
// ... // ...
@@ -2668,7 +2668,7 @@ export const listProducts = async ({
query: { query: {
// ... // ...
fields: "*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags,*variants.calculated_price,*variants.digital_product", fields: "*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags,*variants.calculated_price,*variants.digital_product",
} },
} }
) )
// ... // ...
@@ -2707,7 +2707,7 @@ export const getDigitalProductPreview = async function ({
{ {
headers, headers,
next, next,
cache: "force-cache" cache: "force-cache",
} }
) )
@@ -72,8 +72,8 @@ export const fetchHighlights = [
cart_id: cart.id, cart_id: cart.id,
data: { data: {
// pass any data useful for calculation with third-party provider. // pass any data useful for calculation with third-party provider.
} },
}) }),
}) })
.then((res) => res.json()) .then((res) => res.json())
) )
@@ -160,7 +160,7 @@ export const highlights = [
import { useCart } from "../../../providers/cart" import { useCart } from "../../../providers/cart"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
export default function CheckoutShippingStep () { export default function CheckoutShippingStep() {
const { cart, setCart } = useCart() const { cart, setCart } = useCart()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [shippingOptions, setShippingOptions] = useState< const [shippingOptions, setShippingOptions] = useState<
@@ -171,7 +171,7 @@ export const highlights = [
>({}) >({})
const [ const [
selectedShippingOption, selectedShippingOption,
setSelectedShippingOption setSelectedShippingOption,
] = useState<string | undefined>() ] = useState<string | undefined>()
useEffect(() => { useEffect(() => {
@@ -211,8 +211,8 @@ export const highlights = [
cart_id: cart.id, cart_id: cart.id,
data: { data: {
// pass any data useful for calculation with third-party provider. // pass any data useful for calculation with third-party provider.
} },
}) }),
}) })
.then((res) => res.json()) .then((res) => res.json())
) )
@@ -253,8 +253,8 @@ export const highlights = [
data: { data: {
// TODO add any data necessary for // TODO add any data necessary for
// fulfillment provider // fulfillment provider
} },
}) }),
}) })
.then((res) => res.json()) .then((res) => res.json())
.then(({ cart: updatedCart }) => { .then(({ cart: updatedCart }) => {
@@ -50,12 +50,12 @@ export const fetchHighlights = [
credentials: "include", credentials: "include",
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
email, email,
password password,
}) }),
} }
) )
.then((res) => res.json()) .then((res) => res.json())
@@ -72,12 +72,12 @@ export const fetchHighlights = [
credentials: "include", credentials: "include",
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
email, email,
password password,
}) }),
} }
) )
.then((res) => res.json()) .then((res) => res.json())
@@ -166,12 +166,12 @@ export const highlights = [
credentials: "include", credentials: "include",
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
email, email,
password password,
}) }),
} }
) )
.then((res) => res.json()) .then((res) => res.json())
@@ -188,12 +188,12 @@ export const highlights = [
credentials: "include", credentials: "include",
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
email, email,
password password,
}) }),
} }
) )
.then((res) => res.json()) .then((res) => res.json())
@@ -218,13 +218,13 @@ export const highlights = [
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": `Bearer ${token}`, "Authorization": `Bearer ${token}`,
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp" "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
}, },
body: JSON.stringify({ body: JSON.stringify({
first_name: firstName, first_name: firstName,
last_name: lastName, last_name: lastName,
email email,
}) }),
} }
) )
.then((res) => res.json()) .then((res) => res.json())
@@ -21,8 +21,8 @@ To login a customer with a third-party service, such as Google or GitHub, you mu
1. Authenticate the customer with the [Authenticate Customer API route](!api!/store#auth_postactor_typeauth_provider). 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. 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 a `code` query parameter. So, make sure your third-party service is configured to redirect to your storefront page after successful authentication. 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.
4. The storefront sends a request to the [Validate Authentication Callback API route](!api!/store#auth_postactor_typeauth_providercallback) passing the `code` query parameter. 4. The storefront sends a request to the [Validate Authentication Callback API route](!api!/store#auth_postactor_typeauth_providercallback) passing the query parameters received from the third-party service.
5. If the callback validation is successful, the storefront receives the authentication token. 5. If the callback validation is successful, the storefront receives the authentication token.
6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt). 6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt).
- If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests. - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests.
@@ -194,8 +194,7 @@ Then, in a new page in your storefront that will be used as the callback / redir
<CodeTab label="Fetch API" value="fetch"> <CodeTab label="Fetch API" value="fetch">
export const sendCallbackFetchHighlights = [ export const sendCallbackFetchHighlights = [
["6", "code", "The code received from Google as a query parameter."], ["6", "queryParams", "The query parameters received from Google, such as `code` and `state`."],
["7", "state", "The state received from Google as a query parameter."],
["10", "fetch", "Send a request to the Validate Authentication Callback API route"], ["10", "fetch", "Send a request to the Validate Authentication Callback API route"],
["18", "!token", "If the token isn't returned, the authentication has failed."], ["18", "!token", "If the token isn't returned, the authentication has failed."],
] ]
@@ -205,13 +204,12 @@ import { decodeToken } from "react-jwt"
// ... // ...
const queryParams = new URLSearchParams(window.location.search) const searchParams = new URLSearchParams(window.location.search)
const code = queryParams.get("code") const queryParams = Object.fromEntries(searchParams.entries())
const state = queryParams.get("state")
const sendCallback = async () => { const sendCallback = async () => {
const { token } = await fetch( const { token } = await fetch(
`http://localhost:9000/auth/customer/google/callback?code=${code}&state=${state}`, `http://localhost:9000/auth/customer/google/callback?${new URLSearchParams(queryParams).toString()}`,
{ {
credentials: "include", credentials: "include",
method: "POST", method: "POST",
@@ -250,17 +248,14 @@ export default function GoogleCallback() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [customer, setCustomer] = useState<HttpTypes.StoreCustomer>() const [customer, setCustomer] = useState<HttpTypes.StoreCustomer>()
// for other than Next.js // for other than Next.js
const { code, state } = useMemo(() => { const queryParams = useMemo(() => {
const queryParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search)
return { return Object.fromEntries(searchParams.entries())
code: queryParams.get("code"), }, [])
state: queryParams.get("state"),
};
}, []);
const sendCallback = async () => { const sendCallback = async () => {
const { token } = await fetch( const { token } = await fetch(
`http://localhost:9000/auth/customer/google/callback?code=${code}&state=${state}`, `http://localhost:9000/auth/customer/google/callback?${new URLSearchParams(queryParams).toString()}`,
{ {
credentials: "include", credentials: "include",
method: "POST", method: "POST",
@@ -289,7 +284,7 @@ export default function GoogleCallback() {
</CodeTab> </CodeTab>
</CodeTabs> </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 `code` received from Google. 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: Then, replace the `TODO` with the following:
@@ -566,12 +561,12 @@ export default function GoogleCallback() {
const [customer, setCustomer] = useState<HttpTypes.StoreCustomer>() const [customer, setCustomer] = useState<HttpTypes.StoreCustomer>()
// for other than Next.js // for other than Next.js
const { code, state } = useMemo(() => { const { code, state } = useMemo(() => {
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search)
return { return {
code: queryParams.get("code"), code: queryParams.get("code"),
state: queryParams.get("state"), state: queryParams.get("state"),
}; }
}, []); }, [])
const sendCallback = async () => { const sendCallback = async () => {
const { token } = await fetch( const { token } = await fetch(
+11 -11
View File
@@ -1,7 +1,7 @@
export const generatedEditDates = { export const generatedEditDates = {
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2025-01-07T12:47:24.380Z", "app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2025-01-13T11:31:35.361Z",
"app/commerce-modules/auth/auth-providers/page.mdx": "2024-10-08T07:27:21.859Z", "app/commerce-modules/auth/auth-providers/page.mdx": "2024-10-08T07:27:21.859Z",
"app/commerce-modules/auth/authentication-route/page.mdx": "2025-01-07T09:26:27.809Z", "app/commerce-modules/auth/authentication-route/page.mdx": "2025-01-13T11:31:17.572Z",
"app/commerce-modules/auth/examples/page.mdx": "2024-10-15T15:02:13.794Z", "app/commerce-modules/auth/examples/page.mdx": "2024-10-15T15:02:13.794Z",
"app/commerce-modules/auth/module-options/page.mdx": "2025-01-07T12:47:35.073Z", "app/commerce-modules/auth/module-options/page.mdx": "2025-01-07T12:47:35.073Z",
"app/commerce-modules/auth/page.mdx": "2025-01-09T13:41:05.476Z", "app/commerce-modules/auth/page.mdx": "2025-01-09T13:41:05.476Z",
@@ -47,7 +47,7 @@ export const generatedEditDates = {
"app/commerce-modules/payment/module-options/page.mdx": "2024-10-15T12:51:40.574Z", "app/commerce-modules/payment/module-options/page.mdx": "2024-10-15T12:51:40.574Z",
"app/commerce-modules/payment/payment/page.mdx": "2024-10-09T10:59:08.463Z", "app/commerce-modules/payment/payment/page.mdx": "2024-10-09T10:59:08.463Z",
"app/commerce-modules/payment/payment-collection/page.mdx": "2024-10-09T10:56:49.510Z", "app/commerce-modules/payment/payment-collection/page.mdx": "2024-10-09T10:56:49.510Z",
"app/commerce-modules/payment/payment-flow/page.mdx": "2025-01-08T12:51:39.100Z", "app/commerce-modules/payment/payment-flow/page.mdx": "2025-01-13T11:31:35.361Z",
"app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2024-12-16T13:21:03.554Z", "app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2024-12-16T13:21:03.554Z",
"app/commerce-modules/payment/payment-provider/page.mdx": "2024-10-09T11:07:27.269Z", "app/commerce-modules/payment/payment-provider/page.mdx": "2024-10-09T11:07:27.269Z",
"app/commerce-modules/payment/payment-session/page.mdx": "2024-10-09T10:58:00.960Z", "app/commerce-modules/payment/payment-session/page.mdx": "2024-10-09T10:58:00.960Z",
@@ -112,7 +112,7 @@ export const generatedEditDates = {
"app/nextjs-starter/page.mdx": "2025-01-06T12:19:31.143Z", "app/nextjs-starter/page.mdx": "2025-01-06T12:19:31.143Z",
"app/recipes/b2b/page.mdx": "2024-10-03T13:07:44.153Z", "app/recipes/b2b/page.mdx": "2024-10-03T13:07:44.153Z",
"app/recipes/commerce-automation/page.mdx": "2024-10-16T08:52:01.585Z", "app/recipes/commerce-automation/page.mdx": "2024-10-16T08:52:01.585Z",
"app/recipes/digital-products/examples/standard/page.mdx": "2025-01-07T14:39:31.335Z", "app/recipes/digital-products/examples/standard/page.mdx": "2025-01-13T11:31:35.362Z",
"app/recipes/digital-products/page.mdx": "2025-01-06T11:19:35.623Z", "app/recipes/digital-products/page.mdx": "2025-01-06T11:19:35.623Z",
"app/recipes/ecommerce/page.mdx": "2024-10-22T11:01:01.218Z", "app/recipes/ecommerce/page.mdx": "2024-10-22T11:01:01.218Z",
"app/recipes/integrate-ecommerce-stack/page.mdx": "2024-12-09T13:03:35.846Z", "app/recipes/integrate-ecommerce-stack/page.mdx": "2024-12-09T13:03:35.846Z",
@@ -147,14 +147,14 @@ export const generatedEditDates = {
"app/storefront-development/checkout/email/page.mdx": "2024-12-19T16:30:40.122Z", "app/storefront-development/checkout/email/page.mdx": "2024-12-19T16:30:40.122Z",
"app/storefront-development/checkout/payment/stripe/page.mdx": "2024-12-19T16:30:39.173Z", "app/storefront-development/checkout/payment/stripe/page.mdx": "2024-12-19T16:30:39.173Z",
"app/storefront-development/checkout/payment/page.mdx": "2025-01-02T08:47:20.531Z", "app/storefront-development/checkout/payment/page.mdx": "2025-01-02T08:47:20.531Z",
"app/storefront-development/checkout/shipping/page.mdx": "2024-12-26T15:34:51.431Z", "app/storefront-development/checkout/shipping/page.mdx": "2025-01-13T11:31:35.361Z",
"app/storefront-development/checkout/page.mdx": "2024-06-12T19:46:06+02:00", "app/storefront-development/checkout/page.mdx": "2024-06-12T19:46:06+02:00",
"app/storefront-development/customers/addresses/page.mdx": "2025-01-06T16:59:53.878Z", "app/storefront-development/customers/addresses/page.mdx": "2025-01-06T16:59:53.878Z",
"app/storefront-development/customers/context/page.mdx": "2024-12-19T16:38:43.703Z", "app/storefront-development/customers/context/page.mdx": "2024-12-19T16:38:43.703Z",
"app/storefront-development/customers/log-out/page.mdx": "2024-12-19T16:31:28.347Z", "app/storefront-development/customers/log-out/page.mdx": "2024-12-19T16:31:28.347Z",
"app/storefront-development/customers/login/page.mdx": "2024-12-19T16:31:34.194Z", "app/storefront-development/customers/login/page.mdx": "2024-12-19T16:31:34.194Z",
"app/storefront-development/customers/profile/page.mdx": "2024-12-19T16:31:43.978Z", "app/storefront-development/customers/profile/page.mdx": "2024-12-19T16:31:43.978Z",
"app/storefront-development/customers/register/page.mdx": "2025-01-07T09:28:42.723Z", "app/storefront-development/customers/register/page.mdx": "2025-01-13T11:31:35.362Z",
"app/storefront-development/customers/retrieve/page.mdx": "2025-01-06T16:07:12.542Z", "app/storefront-development/customers/retrieve/page.mdx": "2025-01-06T16:07:12.542Z",
"app/storefront-development/customers/page.mdx": "2024-06-13T12:21:54+03:00", "app/storefront-development/customers/page.mdx": "2024-06-13T12:21:54+03:00",
"app/storefront-development/products/categories/list/page.mdx": "2024-12-19T16:33:06.547Z", "app/storefront-development/products/categories/list/page.mdx": "2024-12-19T16:33:06.547Z",
@@ -192,7 +192,7 @@ export const generatedEditDates = {
"app/usage/page.mdx": "2024-05-13T18:55:11+03:00", "app/usage/page.mdx": "2024-05-13T18:55:11+03:00",
"app/page.mdx": "2025-01-09T11:40:57.826Z", "app/page.mdx": "2025-01-09T11:40:57.826Z",
"app/commerce-modules/auth/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/auth/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/auth/auth-flows/page.mdx": "2025-01-07T09:29:52.153Z", "app/commerce-modules/auth/auth-flows/page.mdx": "2025-01-13T11:31:35.361Z",
"app/commerce-modules/auth/_events/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/auth/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2025-01-07T09:02:27.235Z", "app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2025-01-07T09:02:27.235Z",
"app/commerce-modules/api-key/page.mdx": "2025-01-09T13:41:04.492Z", "app/commerce-modules/api-key/page.mdx": "2025-01-09T13:41:04.492Z",
@@ -861,9 +861,9 @@ export const generatedEditDates = {
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminClaimPreviewResponse/page.mdx": "2025-01-07T12:54:21.225Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminClaimPreviewResponse/page.mdx": "2025-01-07T12:54:21.225Z",
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminOrderEditPreviewResponse/page.mdx": "2025-01-07T12:54:21.543Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminOrderEditPreviewResponse/page.mdx": "2025-01-07T12:54:21.543Z",
"references/types/interfaces/types.BaseClaim/page.mdx": "2025-01-07T12:54:20.996Z", "references/types/interfaces/types.BaseClaim/page.mdx": "2025-01-07T12:54:20.996Z",
"app/commerce-modules/auth/auth-providers/github/page.mdx": "2025-01-07T12:47:30.196Z", "app/commerce-modules/auth/auth-providers/github/page.mdx": "2025-01-13T11:31:35.361Z",
"app/commerce-modules/auth/auth-providers/google/page.mdx": "2025-01-07T12:47:32.501Z", "app/commerce-modules/auth/auth-providers/google/page.mdx": "2025-01-13T11:31:35.361Z",
"app/storefront-development/customers/third-party-login/page.mdx": "2025-01-06T16:05:41.159Z", "app/storefront-development/customers/third-party-login/page.mdx": "2025-01-13T11:31:35.362Z",
"references/types/HttpTypes/types/types.HttpTypes.AdminWorkflowRunResponse/page.mdx": "2024-12-09T13:21:34.761Z", "references/types/HttpTypes/types/types.HttpTypes.AdminWorkflowRunResponse/page.mdx": "2024-12-09T13:21:34.761Z",
"references/types/HttpTypes/types/types.HttpTypes.BatchResponse/page.mdx": "2024-12-09T13:21:33.549Z", "references/types/HttpTypes/types/types.HttpTypes.BatchResponse/page.mdx": "2024-12-09T13:21:33.549Z",
"references/types/WorkflowsSdkTypes/types/types.WorkflowsSdkTypes.Acknowledgement/page.mdx": "2024-12-09T13:21:35.873Z", "references/types/WorkflowsSdkTypes/types/types.WorkflowsSdkTypes.Acknowledgement/page.mdx": "2024-12-09T13:21:35.873Z",
@@ -5788,7 +5788,7 @@ export const generatedEditDates = {
"references/types/StockLocationTypes/interfaces/types.StockLocationTypes.FilterableStockLocationAddressProps/page.mdx": "2025-01-07T12:54:23.060Z", "references/types/StockLocationTypes/interfaces/types.StockLocationTypes.FilterableStockLocationAddressProps/page.mdx": "2025-01-07T12:54:23.060Z",
"references/types/StockLocationTypes/types/types.StockLocationTypes.UpdateStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.057Z", "references/types/StockLocationTypes/types/types.StockLocationTypes.UpdateStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.057Z",
"references/types/StockLocationTypes/types/types.StockLocationTypes.UpsertStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.058Z", "references/types/StockLocationTypes/types/types.StockLocationTypes.UpsertStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.058Z",
"app/commerce-modules/inventory/inventory-kit/page.mdx": "2025-01-09T09:39:50.221Z", "app/commerce-modules/inventory/inventory-kit/page.mdx": "2025-01-13T11:31:35.362Z",
"app/commerce-modules/api-key/workflows/page.mdx": "2025-01-09T13:41:46.573Z", "app/commerce-modules/api-key/workflows/page.mdx": "2025-01-09T13:41:46.573Z",
"app/commerce-modules/api-key/js-sdk/page.mdx": "2025-01-09T12:04:39.787Z", "app/commerce-modules/api-key/js-sdk/page.mdx": "2025-01-09T12:04:39.787Z",
"app/commerce-modules/auth/js-sdk/page.mdx": "2025-01-09T13:27:54.016Z", "app/commerce-modules/auth/js-sdk/page.mdx": "2025-01-09T13:27:54.016Z",
@@ -3,10 +3,8 @@
* operationId: PostActor_typeAuth_providerCallback * operationId: PostActor_typeAuth_providerCallback
* summary: Validate Authentication Callback * summary: Validate Authentication Callback
* description: > * description: >
* This API route is used by your dashboard or frontend application when a third-party provider redirects to it after authentication. * This API route is used by your dashboard or frontend application when a third-party provider redirects to it after authentication. It validates the authentication with the third-party provider and, if successful, returns an authentication token.
* * All query parameters received from the third-party provider, such as `code`, `state`, and `error`, must be passed as query parameters to this route.
*
* It validates the authentication with the third-party provider and, if successful, returns an authentication token.
* *
* *
* You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the frontend. If the decoded data doesn't * You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the frontend. If the decoded data doesn't
@@ -3,11 +3,8 @@
* operationId: PostActor_typeAuth_providerCallback * operationId: PostActor_typeAuth_providerCallback
* summary: Validate Authentication Callback * summary: Validate Authentication Callback
* description: > * description: >
* This API route is used by your storefront or frontend application when a third-party provider redirects to it after authentication. * This API route is used by your storefront or frontend application when a third-party provider redirects to it after authentication. It validates the authentication with the third-party provider and, if successful, returns an authentication token.
* * All query parameters received from the third-party provider, such as `code`, `state`, and `error`, must be passed as query parameters to this route.
*
* It validates the authentication with the third-party provider and, if successful, returns an authentication token.
*
* *
* You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the storefront. If the decoded data doesn't * You can decode the JWT token using libraries like [react-jwt](https://www.npmjs.com/package/react-jwt) in the storefront. If the decoded data doesn't
* have an `actor_id` property, then you must register the customer using the Create Customer API route passing the token in the request's Authorization header. * have an `actor_id` property, then you must register the customer using the Create Customer API route passing the token in the request's Authorization header.