docs: updates to middlewares and protected API routes + new chapter (#12419)
This commit is contained in:
@@ -1,36 +1,102 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Protected Routes`,
|
||||
title: `${pageNumber} Protected API Routes`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to create protected routes.
|
||||
In this chapter, you’ll learn how to create protected API routes.
|
||||
|
||||
## What is a Protected Route?
|
||||
## What is a Protected API Route?
|
||||
|
||||
A protected route is a route that requires requests to be user-authenticated before performing the route's functionality. Otherwise, the request fails, and the user is prevented access.
|
||||
By default, an API route is publicly accessible, meaning that any user can access it without authentication. This is useful for public API routes that allow users to browse products, view collections, and so on.
|
||||
|
||||
---
|
||||
A protected API route is an API route that requires requests to be user-authenticated before performing the route's functionality. Otherwise, the request fails, and the user is prevented access.
|
||||
|
||||
## Default Protected Routes
|
||||
|
||||
Medusa applies an authentication guard on routes starting with `/admin`, including custom API routes.
|
||||
|
||||
Requests to `/admin` must be user-authenticated to access the route.
|
||||
Protected API routes are useful for routes that require user authentication, such as creating a product or managing an order. These routes must only be accessed by authenticated admin users.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Refer to the API Reference for [Admin](!api!/admin#authentication) and [Store](!api!/store#authentication) authentication methods.
|
||||
Refer to the API Reference for [Admin](!api!/admin#authentication) and [Store](!api!/store#authentication) to learn how to send authenticated requests.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Default Protected Routes
|
||||
|
||||
Any API route, including your custom API routes, are protected if they start with the following prefixes:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>
|
||||
Route Prefix
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>
|
||||
Access
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
`/admin`
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Only authenticated admin users can access.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
`/store/customers/me`
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Only authenticated customers can access.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
Refer to the API Reference for [Admin](!api!/admin#authentication) and [Store](!api!/store#authentication) to learn how to send authenticated requests.
|
||||
|
||||
### Opt-Out of Default Authentication Requirement
|
||||
|
||||
If you create a custom API route under a prefix that is protected by default, you can opt-out of the authentication requirement by exporting an `AUTHENTICATE` variable in the route file with its value set to `false`.
|
||||
|
||||
For example, to disable authentication requirement for a custom API route created at `/admin/custom`, you can export an `AUTHENTICATE` variable in the route file:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={[["15"]]}
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "Hello",
|
||||
})
|
||||
}
|
||||
|
||||
export const AUTHENTICATE = false
|
||||
```
|
||||
|
||||
Now, any request sent to the `/admin/custom` API route is allowed, regardless if the admin user is authenticated.
|
||||
|
||||
---
|
||||
|
||||
## Protect Custom API Routes
|
||||
|
||||
To protect custom API Routes to only allow authenticated customer or admin users, use the `authenticate` middleware from the Medusa Framework.
|
||||
You can protect API routes using the `authenticate` [middleware](../middlewares/page.mdx) from the Medusa Framework. When applied to a route, the middleware checks that:
|
||||
|
||||
For example:
|
||||
- The correct actor type (for example, `user`, `customer`, or a custom actor type) is authenticated.
|
||||
- The correct authentication method is used (for example, `session`, `bearer`, or `api-key`).
|
||||
|
||||
For example, you can add the `authenticate` middleware in the `src/api/middlewares.ts` file to protect a custom API route:
|
||||
|
||||
export const highlights = [
|
||||
[
|
||||
@@ -119,111 +185,148 @@ export default defineMiddlewares({
|
||||
})
|
||||
```
|
||||
|
||||
### Override Authentication for Medusa's API Routes
|
||||
|
||||
In some cases, you may want to override the authentication requirement for Medusa's API routes. For example, you may want to allow custom actor types to access existing protected API routes.
|
||||
|
||||
It's not possible to change the [authentication middleware](../middlewares/page.mdx) applied to an existing API route. Instead, you need to replicate the API route and apply the authentication middleware to it.
|
||||
|
||||
Learn more in the [Override API Routes](../override/page.mdx) chapter.
|
||||
|
||||
---
|
||||
|
||||
## Authentication Opt-Out
|
||||
## Access Authentication Details in API Routes
|
||||
|
||||
To disable the authentication guard on custom routes under the `/admin` path prefix, export an `AUTHENTICATE` variable in the route file with its value set to `false`.
|
||||
To access the authentication details in an API route, such as the logged-in user's ID, set the type of the first request parameter to `AuthenticatedMedusaRequest`. It extends `MedusaRequest`:
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={[["15"]]}
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
```ts highlights={[["7", "AuthenticatedMedusaRequest"]]}
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "Hello",
|
||||
})
|
||||
// ...
|
||||
}
|
||||
|
||||
export const AUTHENTICATE = false
|
||||
```
|
||||
|
||||
Now, any request sent to the `/admin/custom` API route is allowed, regardless if the admin user is authenticated.
|
||||
|
||||
---
|
||||
|
||||
## Authenticated Request Type
|
||||
|
||||
To access the authentication details in an API route, such as the logged-in user's ID, set the type of the first request parameter to `AuthenticatedMedusaRequest`. It extends `MedusaRequest`.
|
||||
|
||||
The `auth_context.actor_id` property of `AuthenticatedMedusaRequest` holds the ID of the authenticated user or customer. If there isn't any authenticated user or customer, `auth_context` is `undefined`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts" highlights={[["10", "actor_id"]]}
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.auth_context?.actor_id
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you retrieve the ID of the authenticated user, customer, or custom actor type from the `auth_context` property of the `AuthenticatedMedusaRequest` object.
|
||||
|
||||
<Note>
|
||||
|
||||
If you opt-out of authentication in a route as mentioned in the [previous section](#authentication-opt-out), you can't access the authenticated user or customer anymore. Use the [authenticate middleware](#protect-custom-api-routes) instead.
|
||||
If you opt-out of authentication in a route as mentioned in the [Opt-Out section](#opt-out-of-default-authentication-requirement), you can't access the authenticated user or customer anymore. Use the [authenticate middleware](#protect-custom-api-routes) instead to protect the route.
|
||||
|
||||
</Note>
|
||||
|
||||
### Retrieve Logged-In Customer's Details
|
||||
|
||||
You can access the logged-in customer’s ID in all API routes starting with `/store` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
|
||||
You can access the logged-in customer’s ID in all API routes starting with `/store` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object. You can then use [Query](../../module-links/query/page.mdx) to retrieve the customer details, or pass the ID to a [workflow](../../workflows/page.mdx) that performs business logic.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts" highlights={[["19", "req.auth_context.actor_id", "Access the logged-in customer's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
export const customerHighlights = [
|
||||
["10", "customerId", "Retrieve the logged-in customer's ID."],
|
||||
["13", "query", "Retrieve the customer details."],
|
||||
["20", "throwIfKeyNotFound", "Throw an error if the customer with\nthe specified ID is not found."],
|
||||
]
|
||||
|
||||
```ts title="src/api/store/custom/route.ts" highlights={customerHighlights}
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
if (req.auth_context?.actor_id) {
|
||||
// retrieve customer
|
||||
const customerModuleService: ICustomerModuleService = req.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
const customerId = req.auth_context?.actor_id
|
||||
const query = req.scope.resolve("query")
|
||||
|
||||
const customer = await customerModuleService.retrieveCustomer(
|
||||
req.auth_context.actor_id
|
||||
)
|
||||
}
|
||||
const { data: [customer] } = await query.graph({
|
||||
entity: "customer",
|
||||
fields: ["*"],
|
||||
filters: {
|
||||
id: customerId,
|
||||
},
|
||||
}, {
|
||||
throwIfKeyNotFound: true,
|
||||
})
|
||||
|
||||
// ...
|
||||
// do something with the customer data...
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you resolve the Customer Module's main service, then use it to retrieve the logged-in customer, if available.
|
||||
In this example, you retrieve the customer's ID and resolve Query from the [Medusa container](../../medusa-container/page.mdx).
|
||||
|
||||
Then, you use Query to retrieve the customer details. The `throwIfKeyNotFound` option throws an error if the customer with the specified ID is not found.
|
||||
|
||||
After that, you can use the customer's details in your API route.
|
||||
|
||||
### Retrieve Logged-In Admin User's Details
|
||||
|
||||
You can access the logged-in admin user’s ID in all API Routes starting with `/admin` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
|
||||
You can access the logged-in admin user’s ID in all API routes starting with `/admin` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object. You can then use [Query](../../module-links/query/page.mdx) to retrieve the user details, or pass the ID to a [workflow](../../workflows/page.mdx) that performs business logic.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={[["17", "req.auth_context.actor_id", "Access the logged-in admin user's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
export const adminHighlights = [
|
||||
["10", "userId", "Retrieve the logged-in admin user's ID."],
|
||||
["13", "query", "Retrieve the user details."],
|
||||
["20", "throwIfKeyNotFound", "Throw an error if the user with\nthe specified ID is not found."],
|
||||
]
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={adminHighlights}
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { IUserModuleService } from "@medusajs/framework/types"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const userModuleService: IUserModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
const userId = req.auth_context?.actor_id
|
||||
const query = req.scope.resolve("query")
|
||||
|
||||
const user = await userModuleService.retrieveUser(
|
||||
req.auth_context.actor_id
|
||||
)
|
||||
const { data: [user] } = await query.graph({
|
||||
entity: "user",
|
||||
fields: ["*"],
|
||||
filters: {
|
||||
id: userId,
|
||||
},
|
||||
}, {
|
||||
throwIfKeyNotFound: true,
|
||||
})
|
||||
|
||||
// ...
|
||||
// do something with the user data...
|
||||
}
|
||||
```
|
||||
|
||||
In the route handler, you resolve the User Module's main service, then use it to retrieve the logged-in admin user.
|
||||
In this example, you retrieve the admin user's ID and resolve Query from the [Medusa container](../../medusa-container/page.mdx).
|
||||
|
||||
Then, you use Query to retrieve the user details. The `throwIfKeyNotFound` option throws an error if the user with the specified ID is not found.
|
||||
|
||||
After that, you can use the user's details in your API route.
|
||||
|
||||
Reference in New Issue
Block a user