How to Add Custom Authentication in Medusa Admin

In this guide, you'll learn how to add custom authentication provider to the Medusa Admin.

Note: Authenticating into the Medusa Admin with third-party providers is supported from Medusa v2.12.0.

Overview#

By default, the Medusa Admin allows users to authenticate using their email and password. Medusa uses the Emailpass Authentication Provider to handle this authentication method.

You can also integrate a custom or third-party authentication provider to allow users registered with that provider to log in to the Medusa Admin. This delegates the authentication process to the third-party provider, which is useful if your organization uses a centralized authentication system.

Warning: Ensure the third-party authentication provider allows only your organization's users to log in to the Medusa Admin. For example, integrating a social login provider like Google or Facebook without restrictions will allow any user with an account on those platforms to access your Medusa Admin.

Summary of Custom Medusa Admin Authentication#

To authenticate admin users through a third-party provider, you need to:

  1. Create a custom Authentication Module Provider that integrates the third-party provider. For example, you can create a provider that integrates with Okta.
  2. Change the authentication type in the Medusa Admin dashboard to use JWT authentication.
  3. Add an API route with a workflow that handles creating the admin user after they authenticate through the third-party provider.
  4. Add a widget on the login page that provides the option to log in through the third-party provider.

Overview of the custom authentication process in Medusa Admin


Step 1: Create a Custom Authentication Module Provider#

The first step is to create a custom Authentication Module Provider that integrates with the third-party authentication provider you want to use.

For example, if your organization uses Okta for authentication, you can create an Okta Authentication Provider that uses the Okta SDK to authenticate users.

Refer to the Create Custom Authentication Module Provider guide to learn how to create a custom Authentication Module Provider.

Enable Custom Authentication Provider for Admin Users#

By default, registered Authentication Module Providers can be used for all actor types. This means both admin users and customers can authenticate through the provider.

It's recommended to restrict the custom authentication provider to admin users only. To do this, set the http.authMethodsPerActor configuration in medusa-config.ts to enable the custom authentication provider only for the user actor type:

medusa-config.ts
1module.exports = defineConfig({2  projectConfig: {3    http: {4      // ...5      authMethodsPerActor: {6        user: ["emailpass", "custom"],7        customer: ["emailpass"],8      },9    },10    // ...11  },12})

Where emailpass is the identifier of the Emailpass Authentication Provider, and custom is the identifier of your custom Authentication Module Provider.

Refer to the Authentication Module Providers guide to learn more about configuring allowed authentication providers for actor types.


Step 2: Change Authentication Type in Medusa Admin#

Next, you need to change the authentication type in the Medusa Admin dashboard to use JWT authentication.

To do this, set the ADMIN_AUTH_TYPE environment variable to jwt in your Medusa application's environment variables:

Terminal
ADMIN_AUTH_TYPE=jwt

Make sure to also create a JS SDK instance in your Medusa Admin customizations with the auth.type option set to jwt. For example, create the file src/lib/sdk.ts with the following content:

src/lib/sdk.ts
1import Medusa from "@medusajs/js-sdk"2
3export const sdk = new Medusa({4  baseUrl: import.meta.env.VITE_BACKEND_URL || "/",5  debug: import.meta.env.DEV,6  auth: {7    type: "jwt",8  },9})

Refer to the JS SDK Authentication guide to learn more about configuring authentication in the Medusa JS SDK.


Step 3: Create User API Route#

Next, you need to create an API route with a workflow that handles creating the admin user after they authenticate through the third-party provider.

Note that this API route will allow any user with a valid authentication token to create an admin user. Therefore, ensure that only authorized users can obtain an authentication token from the third-party provider.

Create User Workflow#

First, you need to create the workflow that creates a user and associates it with the authenticated identity.

Create the file src/workflows/create-user.ts with the following content:

src/workflows/create-user.ts
1import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk"2import { createUsersWorkflow, setAuthAppMetadataStep } from "@medusajs/medusa/core-flows"3
4type WorkflowInput = {5  email: string6  auth_identity_id: string7}8
9export const createUserWorkflow = createWorkflow(10  "create-user",11  (input: WorkflowInput) => {12    const users = createUsersWorkflow.runAsStep({13      input: {14        users: [15          {16            email: input.email,17          },18        ],19      },20    })21
22    const authUserInput = transform({ input, users }, ({ input, users }) => {23      const createdUser = users[0]24
25      return {26        authIdentityId: input.auth_identity_id,27        actorType: "user",28        value: createdUser.id,29      }30    })31
32    setAuthAppMetadataStep(authUserInput)33
34    return new WorkflowResponse({35      user: users[0],36    })37  }38)

The workflow receives an object with the following properties:

  • email: The email of the user being authenticated. You can modify this based on your third-party provider's identification method.
  • auth_identity_id: The ID of the auth identity created by the custom Authentication Module Provider. Refer to the Auth Identities guide to learn more.

In the workflow, you:

  1. Create a user with the provided email using createUsersWorkflow.
  2. Associate the created user with the authenticated identity using setAuthAppMetadataStep.

Create User API Route#

Next, you need to create the API route that calls the workflow to create the user.

Create the file src/api/custom-auth/admin/users/route.ts with the following content:

src/api/custom-auth/admin/users/route.ts
1import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework"2import { z } from "zod"3import { createUserWorkflow } from "../../../../workflows/create-user"4
5export const CreateUserSchema = z.object({6  email: z.string(),7})8
9type CreateUserBody = z.infer<typeof CreateUserSchema>10
11export const POST = async (req: AuthenticatedMedusaRequest<CreateUserBody>, res: MedusaResponse) => {12  const user = await createUserWorkflow(req.scope)13    .run({14      input: {15        email: req.body.email,16        auth_identity_id: req.auth_context!.auth_identity_id!,17      },18    })19
20  return res.status(201).json({ user })21}

You expose a POST API route at /custom-auth/admin/users that receives the user's email in the request body. It executes createUserWorkflow to create the user and returns the created user in the response.

Apply Authentication Middleware#

Finally, you need to apply the authentication middleware to the API route to ensure that only authenticated users can access it.

To do this, create the file src/api/middlewares.ts with the following content:

src/api/middlewares.ts
1import { authenticate, defineMiddlewares } from "@medusajs/framework/http"2
3export default defineMiddlewares({4  routes: [5    {6      matcher: "/custom-auth/admin/users",7      methods: ["POST"],8      middlewares: [9        authenticate("user", "bearer", {10          allowUnregistered: true,11        }),12      ],13    },14  ],15})

This middleware applies the authenticate middleware to the /custom-auth/admin/users POST route, allowing only authenticated users to access it. By enabling the allowUnregistered option, users who are authenticated through the third-party provider but don't yet have an associated Medusa user can still access the route to create their user.

Refer to the Protected Routes guide to learn more about protecting API routes with the authentication middleware.


Step 4: Add a Widget on the Login Page#

Finally, you need to add a widget to the Medusa Admin login page that provides the option to log in through the third-party provider.

The widget should display a button that authenticates or redirects the user to the third-party provider. The widget should also handle the redirection back to the Medusa Admin after successful authentication.

For example, the widget may look like this:

src/admin/widgets/custom-login.tsx
1import { defineWidgetConfig } from "@medusajs/admin-sdk"2import { Button, toast } from "@medusajs/ui"3import { decodeToken } from "react-jwt"4import { useSearchParams, useNavigate } from "react-router-dom"5import { useMutation } from "@tanstack/react-query"6import { sdk } from "../lib/sdk"7import { useEffect } from "react"8
9// Replace with the identifier of your custom authentication provider10const CUSTOM_AUTH_PROVIDER = "custom"11
12const CustomLogin = () => {13  // The third-party provider redirects back with query parameters14  // used to validate the authentication15  const [searchParams] = useSearchParams()16  const navigate = useNavigate()17  const { mutateAsync, isPending } = useMutation({18    mutationFn: async () => {19      if (isPending) {20        return21      }22      return await validateCallback()23    },24    onError: (error) => {25      console.error("Custom authentication error:", error)26    },27  })28  29  const sendCallback = async () => {30    try {31      return await sdk.auth.callback(32        "user", 33        CUSTOM_AUTH_PROVIDER, 34        Object.fromEntries(searchParams)35      )36    } catch (error) {37      toast.error("Authentication failed")38      throw error39    }40  }41
42  // Validate the authentication callback43  const validateCallback = async () => {44    const token = await sendCallback()45
46    const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record<string, unknown> }47
48    const userExists = decodedToken.actor_id !== ""49
50    if (!userExists) {51      // Create user52      await sdk.client.fetch("/custom-auth/admin/users", {53        method: "POST",54        body: {55          email: decodedToken.user_metadata?.email as string,56        },57      })58
59      const newToken = await sdk.auth.refresh()60
61      if (!newToken) {62        toast.error("Authentication failed")63        return64      }65    }66
67    // User is authenticated68    navigate("/orders")69  }70
71  // Handle custom login button click72  const customLogin = async () => {73    const result = await sdk.auth.login("user", CUSTOM_AUTH_PROVIDER, {})74
75    if (typeof result === "object" && result.location) {76      // Redirect to custom provider for authentication77      window.location.href = result.location78      return79    }80    81    if (typeof result !== "string") {82      // Result failed, show an error83      toast.error("Authentication failed")84      return85    }86
87    navigate("/app")88  }89
90  // Handle the redirection back from the third-party provider91  useEffect(() => {92    // Check for provider-specific query parameters93    if (searchParams.get("code")) {94      mutateAsync()95    }96  }, [searchParams, mutateAsync])97
98  return (99    <>100      <hr className="bg-ui-border-base my-4" />101      <Button 102        variant="secondary" 103        onClick={customLogin} 104        className="w-full"105      >106        Login with Custom Provider107      </Button>108    </>109  )110}111
112export const config = defineWidgetConfig({113  zone: "login.after",114})115
116export default CustomLogin

You inject the widget into the login.after zone. This displays it below the default email and password login form.

Note: You can't remove the default email and password login form from the Medusa Admin. You can only add additional login options through widgets.

In the widget, you:

  1. Display a button that initiates login through the third-party provider using the sdk.auth.login method.
    • If the login method returns a location property, redirect the user to that location to authenticate through the third-party provider.
    • Otherwise, if the login method returns a token, the user is authenticated and you navigate to the Orders page of the admin dashboard.
  2. Add a useEffect hook that checks for provider-specific query parameters in the URL, such as code, to handle the redirection back from the third-party provider after successful authentication.
  3. Upon redirection back, you:
    • Call the sdk.auth.callback method to validate the authentication with the third-party provider.
    • If the user doesn't exist in Medusa, call the custom API route you created to create the user, then refresh the authentication token.
    • Finally, if authentication is successful, navigate to the Orders page of the admin dashboard.

Test Custom Authentication in Medusa Admin#

To test the custom authentication in the Medusa Admin, run the following command to start your Medusa application:

Then, open the Medusa Admin at http://localhost:9000/app, which will redirect you to the login page.

On the login page, you should see the option to log in through the third-party provider you integrated. Click the button to log in through the provider and follow the authentication flow.

If authentication is successful, you'll have access to the Medusa Admin dashboard. Otherwise, you can troubleshoot the issue by checking your Medusa application's logs or the browser's console for errors.

Was this page helpful?
Ask Anything
Ask any questions about Medusa. Get help with your development.
You can also use the Medusa MCP server in Cursor, VSCode, etc...
FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break