Back to Medusa

{metadata.title}

www/apps/book/app/learn/fundamentals/api-routes/protected-routes/page.mdx

2.14.211.3 KB
Original Source

import { Table } from "docs-ui"

export const metadata = { title: ${pageNumber} Protected API Routes, }

{metadata.title}

In this chapter, you’ll learn how to create protected API routes.

What is a Protected API Route?

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.

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 and Store 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 and Store 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
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

You can protect API routes using the authenticate middleware from the Medusa Framework. When applied to a route, the middleware checks that:

  • 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 = [ [ "10", "authenticate", "Only authenticated admin users can access routes starting with /custom/admin", ], [ "14", "authenticate", "Only authenticated customers can access routes starting with /custom/customers", ], ]

ts
import { 
  defineMiddlewares,
  authenticate,
} from "@medusajs/framework/http"

export default defineMiddlewares({
  routes: [
    {
      matcher: "/custom/admin*",
      middlewares: [authenticate("user", ["session", "bearer", "api-key"])],
    },
    {
      matcher: "/custom/customer*",
      middlewares: [authenticate("customer", ["session", "bearer"])],
    },
  ],
})

The authenticate middleware function accepts three parameters:

  1. The type of user authenticating. Use user for authenticating admin users, and customer for authenticating customers. You can also pass * to allow all types of users, or pass an array of actor types.
  2. An array of types of authentication methods allowed. Both user and customer scopes support session and bearer. The admin scope also supports the api-key authentication method.
  3. An optional object of configurations accepting the following properties:
    • allowUnauthenticated: (default: false) A boolean indicating whether authentication is required. For example, you may have an API route where you want to access the logged-in customer if available, but guest customers can still access it too.
    • allowUnregistered (default: false): A boolean indicating whether users can access this route with a registration token, instead of an authentication token. This is useful if you have a custom actor type, such as manager, and you're creating an API route that allows these users to register themselves. Learn more in the Custom Actor-Type Guide.

Example: Custom Actor Type

For example, to require authentication of a custom actor type manager to an API route:

ts
import { 
  defineMiddlewares,
  authenticate,
} from "@medusajs/framework/http"

export default defineMiddlewares({
  routes: [
    {
      matcher: "/manager*",
      middlewares: [authenticate("manager", ["session", "bearer"])],
    },
  ],
})
<Note title="Tip">

Refer to the Custom Actor-Type Guide for detailed explanation on how to create a custom actor type and apply authentication middlewares.

</Note>

Example: Allow Multiple Actor Types

To allow multiple actor types to access an API route, pass an array of actor types to the authenticate middleware:

ts
import { 
  defineMiddlewares,
  authenticate,
} from "@medusajs/framework/http"

export default defineMiddlewares({
  routes: [
    {
      matcher: "/custom*",
      middlewares: [authenticate(["user", "customer"], ["session", "bearer"])],
    },
  ],
})

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 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 chapter.


Access Authentication Details in API Routes

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:

ts
import type {
  AuthenticatedMedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"

export const GET = async (
  req: AuthenticatedMedusaRequest,
  res: MedusaResponse
) => {
  // ...
}

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
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 Opt-Out section, you can't access the authenticated user or customer anymore. Use the authenticate middleware 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 then use Query to retrieve the customer details, or pass the ID to a workflow that performs business logic.

For example:

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
import type {
  AuthenticatedMedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"

export const GET = async (
  req: AuthenticatedMedusaRequest,
  res: MedusaResponse
) => {
  const customerId = req.auth_context?.actor_id
  const query = req.scope.resolve("query")

  const { data: [customer] } = await query.graph({
    entity: "customer",
    fields: ["*"],
    filters: {
      id: customerId,
    },
  }, {
    throwIfKeyNotFound: true,
  })

  // do something with the customer data...
}

In this example, you retrieve the customer's ID and resolve Query from the Medusa container.

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 then use Query to retrieve the user details, or pass the ID to a workflow that performs business logic.

For example:

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
import type {
  AuthenticatedMedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"

export const GET = async (
  req: AuthenticatedMedusaRequest,
  res: MedusaResponse
) => {
  const userId = req.auth_context?.actor_id
  const query = req.scope.resolve("query")

  const { data: [user] } = await query.graph({
    entity: "user",
    fields: ["*"],
    filters: {
      id: userId,
    },
  }, {
    throwIfKeyNotFound: true,
  })

  // do something with the user data...
}

In this example, you retrieve the admin user's ID and resolve Query from the Medusa container.

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.