toeverything/AFFiNE · error · AuthenticationRequired

authentication_required

authentication_required

Error message

You must sign in first to access this resource.

What it means

Thrown by `acceptInviteById` on the link-invitation branch when the mutation is invoked without an authenticated user. Link invites require a session because the resulting membership is bound to the caller's id; the mutation is marked `@Public()` only so that email-invite guests get a clearer error than a generic auth failure. Coded `authentication_required`.

Source

Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:711

    _workspaceId: string,
    @Args('sendAcceptMail', {
      nullable: true,
      deprecationReason: 'never used',
    })
    _sendAcceptMail: boolean
  ) {
    const role = await this.models.workspaceUser.getById(inviteId);
    // invitation by email
    if (role) {
      if (user && user.id !== role.userId) {
        throw new InvalidInvitation();
      }

      await this.acceptInvitationByEmail(role);
    } else {
      // invitation by link
      if (!user) {
        throw new AuthenticationRequired();
      }

      const invitation = await this.cache.get<{
        workspaceId: string;
        inviterUserId: string;
      }>(`workspace:inviteLinkId:${inviteId}`);

      if (!invitation) {
        throw new InvalidInvitation();
      }

      const role = await this.models.workspaceUser.get(
        invitation.workspaceId,
        user.id
      );

      if (role) {
        // if status is pending, should accept the invitation directly

View on GitHub (pinned to 26c515e050)

Solutions

  1. Redirect unauthenticated users to sign-in with a return URL pointing back to the invite.
  2. Ensure the auth token/cookie is attached to the GraphQL request (credentials: 'include' on the fetcher).
  3. Refresh the session before retrying if it may have expired.
  4. For link invites, detect the unauthenticated state up front and send the user through the sign-in flow.

Example fix

// before
await sdk.acceptInviteById({ inviteId }); // link invite, no session

// after
if (!currentUser) {
  router.push(`/signin?redirect=${encodeURIComponent('/invite/' + inviteId)}`);
  return;
}
await sdk.acceptInviteById({ inviteId });
Defensive patterns

Strategy: validation

Validate before calling

// Require a session for link invites
if (!currentUser) {
  router.push(`/signin?redirect=${encodeURIComponent('/invite/' + inviteId)}`);
  return;
}
await sdk.acceptInviteById({ inviteId });

Type guard

function hasSession(user) {
  return Boolean(user && user.id);
}

Try / catch

try {
  await sdk.acceptInviteById({ inviteId });
} catch (e) {
  if (e.code === 'authentication_required') {
    redirectToSignIn(location.pathname);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `acceptInviteById` with a link invite id (no matching `workspace_user` row) while `user` is `undefined` — e.g. an unauthenticated browser landing on the invite-accept route, or a client that did not attach the session token.

Common situations: Session expired between viewing the invite page and clicking accept; the auth cookie was blocked by third-party cookie restrictions; an unauthenticated user opened the link directly; misconfigured auth gateway stripping headers.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/12920541ba62e9a9. Report an issue: GitHub.