docs: fix callback validation for third-party authentication (#14109)

* docs: fix callback validation for third-party authentication

* address comment
This commit is contained in:
Shahed Nasser
2025-11-24 15:43:05 +02:00
committed by GitHub
parent b81f958d41
commit 22ca22a2f0
11 changed files with 272 additions and 166 deletions

View File

@@ -1,4 +1,5 @@
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
export const sdk = new Medusa({
baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
@@ -8,7 +9,7 @@ export const sdk = new Medusa({
},
})
await sdk.auth.callback(
const token = await sdk.auth.callback(
"user",
"google",
{
@@ -16,16 +17,25 @@ await sdk.auth.callback(
state: "456"
}
)
// all subsequent requests will use the token in the header
sdk.admin.invite.accept(
{
email: "user@gmail.com",
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
.then(({ user }) => {
console.log(user)
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateUser = decodedToken.actor_id === ""
if (shouldCreateUser) {
const user = await sdk.admin.invite.accept(
{
email: decodedToken.user_metadata.email as string,
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// User already exists and is authenticated
}

View File

@@ -1,4 +1,5 @@
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
export const sdk = new Medusa({
baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
@@ -8,24 +9,33 @@ export const sdk = new Medusa({
},
})
const authToken = await sdk.auth.callback(
const token = await sdk.auth.callback(
"user",
"google",
"github",
{
code: "123",
state: "456"
}
)
// all subsequent requests will use the token in the header
sdk.admin.invite.accept(
{
email: "user@gmail.com",
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
.then(({ user }) => {
console.log(user)
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateUser = decodedToken.actor_id === ""
if (shouldCreateUser) {
const user = await sdk.admin.invite.accept(
{
email: decodedToken.user_metadata.email as string,
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// User already exists and is authenticated
}

View File

@@ -64080,6 +64080,7 @@ paths:
label: Google Provider
source: |-
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
export const sdk = new Medusa({
baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
@@ -64089,7 +64090,7 @@ paths:
},
})
await sdk.auth.callback(
const token = await sdk.auth.callback(
"user",
"google",
{
@@ -64097,23 +64098,33 @@ paths:
state: "456"
}
)
// all subsequent requests will use the token in the header
sdk.admin.invite.accept(
{
email: "user@gmail.com",
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
.then(({ user }) => {
console.log(user)
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateUser = decodedToken.actor_id === ""
if (shouldCreateUser) {
const user = await sdk.admin.invite.accept(
{
email: decodedToken.user_metadata.email as string,
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// User already exists and is authenticated
}
- lang: TypeScript
label: GitHub Provider
source: |-
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
export const sdk = new Medusa({
baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
@@ -64123,27 +64134,36 @@ paths:
},
})
const authToken = await sdk.auth.callback(
const token = await sdk.auth.callback(
"user",
"google",
"github",
{
code: "123",
state: "456"
}
)
// all subsequent requests will use the token in the header
sdk.admin.invite.accept(
{
email: "user@gmail.com",
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
.then(({ user }) => {
console.log(user)
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateUser = decodedToken.actor_id === ""
if (shouldCreateUser) {
const user = await sdk.admin.invite.accept(
{
email: decodedToken.user_metadata.email as string,
first_name: "John",
last_name: "Smith",
invite_token: "12345..."
},
)
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// User already exists and is authenticated
}
tags:
- Auth
responses:

View File

@@ -1,4 +1,5 @@
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
let MEDUSA_BACKEND_URL = "http://localhost:9000"
@@ -12,7 +13,7 @@ export const sdk = new Medusa({
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
})
await sdk.auth.callback(
const token = await sdk.auth.callback(
"customer",
"google",
{
@@ -20,9 +21,20 @@ await sdk.auth.callback(
state: "456"
}
)
// all subsequent requests will use the token in the header
const { customer } = await sdk.store.customer.create({
email: "customer@gmail.com",
password: "supersecret"
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateCustomer = decodedToken.actor_id === ""
if (shouldCreateCustomer) {
const { customer } = await sdk.store.customer.create({
email: decodedToken.user_metadata.email as string,
})
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// Customer already exists and is authenticated
}

View File

@@ -1,4 +1,5 @@
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
let MEDUSA_BACKEND_URL = "http://localhost:9000"
@@ -12,7 +13,7 @@ export const sdk = new Medusa({
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
})
await sdk.auth.callback(
const token = await sdk.auth.callback(
"customer",
"github",
{
@@ -20,9 +21,20 @@ await sdk.auth.callback(
state: "456"
}
)
// all subsequent requests will use the token in the header
const { customer } = await sdk.store.customer.create({
email: "customer@gmail.com",
password: "supersecret"
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateCustomer = decodedToken.actor_id === ""
if (shouldCreateCustomer) {
const { customer } = await sdk.store.customer.create({
email: decodedToken.user_metadata.email as string,
})
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// Customer already exists and is authenticated
}

View File

@@ -290,6 +290,7 @@ paths:
label: Google Provider
source: |-
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
let MEDUSA_BACKEND_URL = "http://localhost:9000"
@@ -303,7 +304,7 @@ paths:
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
})
await sdk.auth.callback(
const token = await sdk.auth.callback(
"customer",
"google",
{
@@ -311,16 +312,28 @@ paths:
state: "456"
}
)
// all subsequent requests will use the token in the header
const { customer } = await sdk.store.customer.create({
email: "customer@gmail.com",
password: "supersecret"
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateCustomer = decodedToken.actor_id === ""
if (shouldCreateCustomer) {
const { customer } = await sdk.store.customer.create({
email: decodedToken.user_metadata.email as string,
})
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// Customer already exists and is authenticated
}
- lang: TypeScript
label: GitHub Provider
source: |-
import Medusa from "@medusajs/js-sdk"
import { decodeToken } from "react-jwt"
let MEDUSA_BACKEND_URL = "http://localhost:9000"
@@ -334,7 +347,7 @@ paths:
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
})
await sdk.auth.callback(
const token = await sdk.auth.callback(
"customer",
"github",
{
@@ -342,12 +355,23 @@ paths:
state: "456"
}
)
// all subsequent requests will use the token in the header
const { customer } = await sdk.store.customer.create({
email: "customer@gmail.com",
password: "supersecret"
})
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateCustomer = decodedToken.actor_id === ""
if (shouldCreateCustomer) {
const { customer } = await sdk.store.customer.create({
email: decodedToken.user_metadata.email as string,
})
// refresh auth token
await sdk.auth.refresh()
// all subsequent requests will use the new token in the header
} else {
// Customer already exists and is authenticated
}
tags:
- Auth
responses:

View File

@@ -256,6 +256,18 @@ In your frontend, decode the token using tools like [react-jwt](https://www.npmj
- If the decoded data has an `actor_id` property, the user is already registered. So, use this token for subsequent authenticated requests.
- If not, use the token in the header of a request that creates the user, such as the [Create Customer API route](!api!/store#customers_postcustomers).
The decoded data may look like this:
```json
{
"actor_id": "", // Empty if the user is not registered
"user_metadata": {
"email": "Whitney_Schultz@gmail.com"
}
// other fields...
}
```
---
## Refresh Token Route

View File

@@ -355,15 +355,12 @@ Finally, you'll add to the page a function that validates the authentication cal
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="React" value="react">
export const validateReactHighlights = [
["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 needs 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"]
["6", "shouldCreateCustomer", "Check if the decoded token has an `actor_id` property to decide whether a customer needs to be created"],
["9", "createCustomer", "Create a customer if the decoded token doesn't have `actor_id`"],
["11", "refreshToken", "Fetch a new token for the created customer"],
["15", "retrieve", "Retrieve the customer's details as an example of testing authentication"]
]
```tsx highlights={validateReactHighlights}
@@ -387,46 +384,9 @@ const validateCallback = async () => {
setLoading(false)
}
// TODO run validateCallback when the page loads
```
</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 needs 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)
}
// TODO run validateCallback when the page loads
```
</CodeTab>
</CodeTabs>
The `validateCallback` function uses the functions added earlier to implement the following flow:
1. Send a request to the [Validate Callback API route](!api!/store#auth_postactor_typeauth_providercallback). This returns an authentication token.
@@ -527,15 +487,17 @@ export default function GoogleCallback() {
const validateCallback = async () => {
const token = await sendCallback()
const shouldCreateCustomer = (decodeToken(token) as { actor_id: string }).actor_id === ""
const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
const shouldCreateCustomer = decodedToken.actor_id === ""
if (shouldCreateCustomer) {
await createCustomer()
await createCustomer(decodedToken.user_metadata.email as string)
await refreshToken()
}
// all subsequent requests are authenticated
// use token to send authenticated requests
const { customer: customerData } = await sdk.store.customer.retrieve()
setCustomer(customerData)

View File

@@ -1,7 +1,7 @@
export const generatedEditDates = {
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2025-01-13T11:31:35.361Z",
"app/commerce-modules/auth/auth-providers/page.mdx": "2025-05-20T07:51:40.707Z",
"app/commerce-modules/auth/authentication-route/page.mdx": "2025-03-04T09:13:45.919Z",
"app/commerce-modules/auth/authentication-route/page.mdx": "2025-11-24T07:39:10.358Z",
"app/commerce-modules/auth/examples/page.mdx": "2024-10-15T15:02:13.794Z",
"app/commerce-modules/auth/module-options/page.mdx": "2025-03-11T08:56:10.338Z",
"app/commerce-modules/auth/page.mdx": "2025-04-17T08:48:17.286Z",
@@ -830,7 +830,7 @@ export const generatedEditDates = {
"references/types/interfaces/types.BaseClaim/page.mdx": "2025-09-12T14:10:39.219Z",
"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-13T11:31:35.361Z",
"app/storefront-development/customers/third-party-login/page.mdx": "2025-09-17T11:42:53.434Z",
"app/storefront-development/customers/third-party-login/page.mdx": "2025-11-24T07:27:46.836Z",
"references/types/HttpTypes/types/types.HttpTypes.AdminWorkflowRunResponse/page.mdx": "2024-12-09T13:21:34.761Z",
"references/types/HttpTypes/types/types.HttpTypes.BatchResponse/page.mdx": "2025-04-11T09:04:46.523Z",
"references/types/WorkflowsSdkTypes/types/types.WorkflowsSdkTypes.Acknowledgement/page.mdx": "2024-12-09T13:21:35.873Z",

View File

@@ -26,6 +26,7 @@
* label: Google Provider
* source: |-
* import Medusa from "@medusajs/js-sdk"
* import { decodeToken } from "react-jwt"
*
* export const sdk = new Medusa({
* baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
@@ -35,7 +36,7 @@
* },
* })
*
* await sdk.auth.callback(
* const token = await sdk.auth.callback(
* "user",
* "google",
* {
@@ -43,23 +44,33 @@
* state: "456"
* }
* )
*
* // all subsequent requests will use the token in the header
* sdk.admin.invite.accept(
* {
* email: "user@gmail.com",
* first_name: "John",
* last_name: "Smith",
* invite_token: "12345..."
* },
* )
* .then(({ user }) => {
* console.log(user)
* })
*
* const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
*
* const shouldCreateUser = decodedToken.actor_id === ""
*
* if (shouldCreateUser) {
* const user = await sdk.admin.invite.accept(
* {
* email: decodedToken.user_metadata.email as string,
* first_name: "John",
* last_name: "Smith",
* invite_token: "12345..."
* },
* )
*
* // refresh auth token
* await sdk.auth.refresh()
* // all subsequent requests will use the new token in the header
* } else {
* // User already exists and is authenticated
* }
* - lang: TypeScript
* label: GitHub Provider
* source: |-
* import Medusa from "@medusajs/js-sdk"
* import { decodeToken } from "react-jwt"
*
* export const sdk = new Medusa({
* baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
@@ -69,27 +80,36 @@
* },
* })
*
* const authToken = await sdk.auth.callback(
* const token = await sdk.auth.callback(
* "user",
* "google",
* "github",
* {
* code: "123",
* state: "456"
* }
* )
*
* // all subsequent requests will use the token in the header
* sdk.admin.invite.accept(
* {
* email: "user@gmail.com",
* first_name: "John",
* last_name: "Smith",
* invite_token: "12345..."
* },
* )
* .then(({ user }) => {
* console.log(user)
* })
*
* const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
*
* const shouldCreateUser = decodedToken.actor_id === ""
*
* if (shouldCreateUser) {
* const user = await sdk.admin.invite.accept(
* {
* email: decodedToken.user_metadata.email as string,
* first_name: "John",
* last_name: "Smith",
* invite_token: "12345..."
* },
* )
*
* // refresh auth token
* await sdk.auth.refresh()
* // all subsequent requests will use the new token in the header
* } else {
* // User already exists and is authenticated
* }
* tags:
* - Auth
* responses:

View File

@@ -25,6 +25,7 @@
* label: Google Provider
* source: |-
* import Medusa from "@medusajs/js-sdk"
* import { decodeToken } from "react-jwt"
*
* let MEDUSA_BACKEND_URL = "http://localhost:9000"
*
@@ -38,7 +39,7 @@
* publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
* })
*
* await sdk.auth.callback(
* const token = await sdk.auth.callback(
* "customer",
* "google",
* {
@@ -46,16 +47,28 @@
* state: "456"
* }
* )
*
* // all subsequent requests will use the token in the header
* const { customer } = await sdk.store.customer.create({
* email: "customer@gmail.com",
* password: "supersecret"
* })
*
* const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
*
* const shouldCreateCustomer = decodedToken.actor_id === ""
*
* if (shouldCreateCustomer) {
* const { customer } = await sdk.store.customer.create({
* email: decodedToken.user_metadata.email as string,
* })
*
* // refresh auth token
* await sdk.auth.refresh()
* // all subsequent requests will use the new token in the header
* } else {
* // Customer already exists and is authenticated
* }
* - lang: TypeScript
* label: GitHub Provider
* source: |-
* import Medusa from "@medusajs/js-sdk"
* import { decodeToken } from "react-jwt"
*
* let MEDUSA_BACKEND_URL = "http://localhost:9000"
*
@@ -69,7 +82,7 @@
* publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
* })
*
* await sdk.auth.callback(
* const token = await sdk.auth.callback(
* "customer",
* "github",
* {
@@ -77,12 +90,23 @@
* state: "456"
* }
* )
*
* // all subsequent requests will use the token in the header
* const { customer } = await sdk.store.customer.create({
* email: "customer@gmail.com",
* password: "supersecret"
* })
*
* const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }
*
* const shouldCreateCustomer = decodedToken.actor_id === ""
*
* if (shouldCreateCustomer) {
* const { customer } = await sdk.store.customer.create({
* email: decodedToken.user_metadata.email as string,
* })
*
* // refresh auth token
* await sdk.auth.refresh()
* // all subsequent requests will use the new token in the header
* } else {
* // Customer already exists and is authenticated
* }
* tags:
* - Auth
* responses: