- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
How to Create an Actor Type
In this document, learn how to create an actor type and authenticate its associated data model.
0. Create Module with Data Model#
Before creating an actor type, you must have a module with a data model representing the actor type.
The rest of this guide uses this Manager
data model as an example:
1. Create Workflow#
Start by creating a workflow that does two things:
- Creates a record of the
Manager
data model. - Sets the
app_metadata
property of the associatedAuthIdentity
record based on the new actor type.
For example, create the file src/workflows/create-manager.ts
. with the following content:
1import { 2 createWorkflow, 3 createStep,4 StepResponse,5 WorkflowResponse,6} from "@medusajs/framework/workflows-sdk"7import { 8 setAuthAppMetadataStep,9} from "@medusajs/medusa/core-flows"10import ManagerModuleService from "../modules/manager/service"11 12type CreateManagerWorkflowInput = {13 manager: {14 first_name: string15 last_name: string16 email: string17 }18 authIdentityId: string19}20 21const createManagerStep = createStep(22 "create-manager-step",23 async ({ 24 manager: managerData,25 }: Pick<CreateManagerWorkflowInput, "manager">, 26 { container }) => {27 const managerModuleService: ManagerModuleService = 28 container.resolve("managerModuleService")29 30 const manager = await managerModuleService.createManager(31 managerData32 )33 34 return new StepResponse(manager)35 }36)37 38const createManagerWorkflow = createWorkflow(39 "create-manager",40 function (input: CreateManagerWorkflowInput) {41 const manager = createManagerStep({42 manager: input.manager,43 })44 45 setAuthAppMetadataStep({46 authIdentityId: input.authIdentityId,47 actorType: "manager",48 value: manager.id,49 })50 51 return new WorkflowResponse(manager)52 }53)54 55export default createManagerWorkflow
This workflow accepts the manager’s data and the associated auth identity’s ID as inputs. The next sections explain how the auth identity ID is retrieved.
The workflow has two steps:
- Create the manager using the
createManagerStep
. - Set the
app_metadata
property of the associated auth identity using thesetAuthAppMetadataStep
step imported from@medusajs/medusa/core-flows
. You specify the actor typemanager
in theactorType
property of the step’s input.
2. Define the Create API Route#
Next, you’ll use the workflow defined in the previous section in an API route that creates a manager.
So, create the file src/api/manager/route.ts
with the following content:
1import type { 2 AuthenticatedMedusaRequest,3 MedusaResponse,4} from "@medusajs/framework/http"5import { MedusaError } from "@medusajs/framework/utils"6import createManagerWorkflow from "../../workflows/create-manager"7 8type RequestBody = {9 first_name: string10 last_name: string11 email: string12}13 14export async function POST(15 req: AuthenticatedMedusaRequest<RequestBody>, 16 res: MedusaResponse17) {18 // If `actor_id` is present, the request carries 19 // authentication for an existing manager20 if (req.auth_context.actor_id) {21 throw new MedusaError(22 MedusaError.Types.INVALID_DATA,23 "Request already authenticated as a manager."24 )25 }26 27 const { result } = await createManagerWorkflow(req.scope)28 .run({29 input: {30 manager: req.body,31 authIdentityId: req.auth_context.auth_identity_id,32 },33 })34 35 res.status(200).json({ manager: result })36}
Since the manager must be associated with an AuthIdentity
record, the request is expected to be authenticated, even if the manager isn’t created yet. This can be achieved by:
- Obtaining a token usng the /auth route.
- Passing the token in the bearer header of the request to this route.
In the API route, you create the manager using the workflow from the previous section and return it in the response.
3. Apply the authenticate
Middleware#
The last step is to apply the authenticate
middleware on the API routes that require a manager’s authentication.
To do that, create the file src/api/middlewares.ts
with the following content:
1import { 2 defineMiddlewares,3 authenticate,4} from "@medusajs/medusa"5 6export default defineMiddlewares({7 routes: [8 {9 matcher: "/manager",10 method: "POST",11 middlewares: [12 authenticate("manager", ["session", "bearer"], {13 allowUnregistered: true,14 }),15 ],16 },17 {18 matcher: "/manager/me*",19 middlewares: [20 authenticate("manager", ["session", "bearer"]),21 ],22 },23 ],24})
This applies middlewares on two route patterns:
- The
authenticate
middleware is applied on the/manager
API route forPOST
requests while allowing unregistered managers. This requires that a bearer token be passed in the request to access the manager’s auth identity but doesn’t require the manager to be registered. - The
authenticate
middleware is applied on all routes starting with/manager/me
, restricting these routes to authenticated managers only.
Retrieve Manager API Route#
For example, create the file src/api/manager/me/route.ts
with the following content:
1import { 2 AuthenticatedMedusaRequest,3 MedusaResponse,4} from "@medusajs/framework/http"5import ManagerModuleService from "../../../modules/manager/service"6 7export async function GET(8 req: AuthenticatedMedusaRequest,9 res: MedusaResponse10): Promise<void> {11 const managerModuleService: ManagerModuleService = 12 req.scope.resolve("managerModuleService")13 14 const manager = await managerModuleService.retrieveManager(15 req.auth_context.actor_id16 )17 18 res.json({ manager })19}
This route is only accessible by authenticated managers. You access the manager’s ID using req.auth_context.actor_id
.
Test Custom Actor Type Authentication Flow#
To authenticate managers:
- Send a
POST
request to/auth/manager/emailpass/register
to create an auth identity for the manager:
Copy the returned token to use it in the next request.
- Send a
POST
request to/manager
to create a manager:
Replace {token}
with the token returned in the previous step.
- Send a
POST
request to/auth/manager/emailpass
again to retrieve an authenticated token for the manager:
- You can now send authenticated requests as a manager. For example, send a
GET
request to/manager/me
to retrieve the authenticated manager’s details:
Whenever you want to log in as a manager, use the /auth/manager/emailpass
API route, as explained in step 3.
Delete User of Actor Type#
When you delete a user of the actor type, you must update its auth identity to remove the association to the user.
For example, create the following workflow that deletes a manager and updates its auth identity, create the file src/workflows/delete-manager.ts
with the following content:
5import ManagerModuleService from "../modules/manager/service"6 7export type DeleteManagerWorkflow = {8 id: string9}10 11const deleteManagerStep = createStep(12 "delete-manager-step",13 async (14 { id }: DeleteManagerWorkflow, 15 { container }) => {16 const managerModuleService: ManagerModuleService = 17 container.resolve("managerModuleService")18 19 const manager = await managerModuleService.retrieve(id)20 21 await managerModuleService.deleteManagers(id)22 23 return new StepResponse(undefined, { manager })24 },25 async ({ manager }, { container }) => {26 const managerModuleService: ManagerModuleService = 27 container.resolve("managerModuleService")28 29 await managerModuleService.createManagers(manager)30 }31 )
You add a step that deletes the manager using the deleteManagers
method of the module's main service. In the compensation function, you create the manager again.
Next, in the same file, add the workflow that deletes a manager:
14// ...15 16export const deleteManagerWorkflow = createWorkflow(17 "delete-manager",18 (19 input: WorkflowData<DeleteManagerWorkflow>20 ): WorkflowResponse<string> => {21 deleteManagerStep(input)22 23 const { data: authIdentities } = useQueryGraphStep({24 entity: "auth_identity",25 fields: ["id"],26 filters: {27 app_metadata: {28 // the ID is of the format `{actor_type}_id`.29 manager_id: input.id,30 }31 }32 })33 34 const authIdentity = transform(35 { authIdentities },36 ({ authIdentities }) => {37 const authIdentity = authIdentities[0]38 39 if (!authIdentity) {40 throw new MedusaError(41 MedusaError.Types.NOT_FOUND,42 "Auth identity not found"43 )44 }45 46 return authIdentity47 }48 )49 50 setAuthAppMetadataStep({51 authIdentityId: authIdentity.id,52 actorType: "manager",53 value: null,54 })55 56 return new WorkflowResponse(input.id)57 }58)
In the workflow, you:
- Use the
deleteManagerStep
defined earlier to delete the manager. - Retrieve the auth identity of the manager using Query. To do that, you filter the
app_metadata
property of an auth identity, which holds the user's ID under{actor_type_name}_id
. So, in this case, it'smanager_id
. - Check that the auth identity exist, then, update the auth identity to remove the ID of the manager from it.
You can use this workflow when deleting a manager, such as in an API route.