Implementing User Invitation Workflows with Logto: A Complete Developer's Guide
Logto provides a complete organization invitation system that enables administrators to invite users via email magic links and allows invitees to accept invitations through a secure workflow, implemented across database schemas, REST APIs, and React components.
When building multi-tenant applications with the logto-io/logto identity platform, implementing user invitation workflows is essential for onboarding organization members securely. This guide examines the complete implementation, from the database migration that creates the organization_invitations table to the React components that handle magic-link consumption.
Understanding the Invitation Architecture
Logto's invitation system operates through three integrated layers: a PostgreSQL schema storing invitation states, REST API endpoints handling lifecycle transitions, and React components providing the user interface.
Database Schema and Migration
The foundation resides in packages/schemas/alterations/1.13.0-1704935001-add-organization-invitation-tables.ts. This migration creates the organization_invitations table with columns for the inviter's identity, the invitee's email address, the target organization (tenant) ID, and a status enum supporting Pending, Accepted, Expired, and Revoked states. Each record includes a unique magic-link identifier used for secure token-based access.
Backend API Endpoints
The core logic is encapsulated in the OrganizationInvitationService located in the packages/core directory. The service exposes several REST endpoints under /api/tenants/:tenantId/invitations:
- POST
/api/tenants/:tenantId/invitations– Creates a new invitation record and triggers the email service to send a magic link - GET
/api/invitations/:invitationId– Retrieves invitation details for the acceptance page - PATCH
/api/invitations/:invitationId/status– Updates status toAccepted,Revoked, or handles expiration - DELETE
/api/invitations/:invitationId– Removes the invitation record entirely
Frontend Component Hierarchy
The Console UI implements the workflow across several key components:
- InviteMemberModal (
packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx) – Admin interface for creating invitations - AcceptInvitation (
packages/console/src/pages/AcceptInvitation/index.tsx) – Public-facing page for magic-link consumption - Invitations List (
packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx) – Management interface for pending invitations - TenantInvitationDropdownItem (
packages/console/src/components/Topbar/TenantSelector/TenantInvitationDropdownItem/index.tsx) – Global navigation indicator for pending invites
Creating Organization Invitations (Admin Flow)
Administrators initiate invitations through the InviteMemberModal component, which posts to the tenant-scoped invitations endpoint. The request includes the invitee's email, assigned organization roles, and an optional expiration date.
// packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx
await cloudApi.post('/api/tenants/:tenantId/invitations', {
data: {
email: inviteeEmail,
organizationRoles: selectedRoles,
expireAt: expirationDate,
},
params: { tenantId: currentTenantId },
});
toast.success(t('tenant_members.messages.invitation_sent'));
The OrganizationInvitationService validates the request, inserts a record into organization_invitations with status Pending, and generates a magic-link URL containing the invitation ID. Logto's email service then dispatches the invitation to the specified address using templates defined in packages/phrases/src/locales/en/translation/admin-console/invitation.ts.
Accepting Invitations via Magic Links (Invitee Flow)
When an invitee clicks the magic link, the AcceptInvitation component loads the invitation details using a useSWR hook. The component first verifies the invitation status is OrganizationInvitationStatus.Pending before allowing acceptance.
// packages/console/src/pages/AcceptInvitation/index.tsx
const { data: invitation } = useSWR<InvitationResponse>(
isAuthenticated && invitationId && `/api/invitations/${invitationId}`,
() => silentCloudApi.get('/api/invitations/:invitationId', { params: { invitationId } })
);
if (invitation && invitation.status === OrganizationInvitationStatus.Pending) {
await cloudApi.patch('/api/invitations/:invitationId/status', {
params: { invitationId: invitation.id },
data: { status: 'Accepted' },
});
// redirect to the tenant after acceptance
navigateTenant(invitation.organizationId);
}
If the invitation has already been accepted, revoked, or expired, the UI displays an error state (invalid_invitation_status) and prevents further action. Upon successful acceptance, the user is automatically redirected to the tenant dashboard with appropriate role-based permissions.
Managing Invitation Lifecycles
Administrators can monitor and control pending invitations through the Invitations management page. Revoking an invitation updates its status to Revoked, preventing the magic link from functioning while preserving the audit trail.
// packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx
await cloudApi.patch('/api/tenants/:tenantId/invitations/:invitationId/status', {
params: { tenantId: currentTenantId, invitationId },
data: { status: 'Revoked' },
});
toast.success(t('messages.invitation_revoked'));
Users can view their pending invitations globally through the top-bar navigation, which utilizes the useUserInvitations hook to fetch pending items.
// packages/console/src/components/Topbar/TenantSelector/index.tsx
const { data: pendingInvitations } = useUserInvitations();
return pendingInvitations?.map(inv => (
<TenantInvitationDropdownItem key={inv.id} data={inv} />
));
Summary
- Logto implements a database-driven invitation system using the
organization_invitationstable with four distinct status states. - The backend API provides tenant-scoped endpoints for creation, retrieval, and status updates, wrapped by the
OrganizationInvitationService. - Magic links serve as secure tokens for invitation acceptance, consumed by the
AcceptInvitationpage component. - Role assignment occurs during invitation creation, allowing immediate access control upon acceptance.
- Lifecycle management includes revocation and expiration capabilities, with UI components disabling actions for non-pending invitations.
Frequently Asked Questions
How does Logto handle invitation expiration?
Logto checks the expireAt timestamp stored in the organization_invitations table during the acceptance flow. If the current time exceeds the expiration date, the system returns an expired status, and the AcceptInvitation component renders an error message preventing further action.
Can administrators resend invitations to users?
Yes, administrators can create a new invitation for the same email address. The InviteMemberModal component allows POSTing a fresh invitation request, which generates a new magic-link ID and sends a replacement email. The previous invitation remains in the database but can be revoked to prevent confusion.
What happens when an invitee clicks an expired or revoked invitation link?
The AcceptInvitation component fetches the invitation status via GET /api/invitations/:invitationId. If the status is not Pending (e.g., Expired, Revoked, or already Accepted), the UI displays a specific error state with the translation key invalid_invitation_status, informing the user that the link is no longer valid.
How do administrators monitor pending invitations across tenants?
Administrators use the Invitations list page at packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx, which queries the tenant-scoped invitations endpoint. The UI displays pending invitations with action buttons for revocation or deletion, while the TenantInvitationDropdownItem component provides a global notification mechanism for invitees viewing any page in the console.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →