calcom/cal.diy · error · ForbiddenException

PermissionsGuard - no authentication provided. Provide eithe

Error message

PermissionsGuard - no authentication provided. Provide either authorization bearer token containing managed user access token or oAuth client id in 'x-cal-client-id' header.

What it means

ForbiddenException from PermissionsGuard when the request carries neither a Bearer token nor an x-cal-client-id header (and is also not a NextAuth token, API key, or third-party OAuth token — those bypass the guard at line 44). The guard only enforces permissions for platform OAuth clients/credentials; the missing-auth case is a 403, not 401, by design.

Source

Thrown at apps/api/v2/src/modules/auth/guards/permissions/permissions.guard.ts:48

    if (!requiredPermissions?.length || !Object.keys(requiredPermissions)?.length) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const bearerToken = request.get("Authorization")?.replace("Bearer ", "");
    const nextAuthSecret = this.config.get("next.authSecret", { infer: true });
    const nextAuthToken = await getToken({ req: request, secret: nextAuthSecret });
    const oAuthClientId = request.params?.clientId || request.get(X_CAL_CLIENT_ID);
    const apiKey = bearerToken && isApiKey(bearerToken, this.config.get("api.apiKeyPrefix") ?? "cal_");
    const isThirdPartyBearerToken = bearerToken && this.getDecodedThirdPartyAccessToken(bearerToken);

    // only check permissions for accessTokens attached to platform oAuth Client or platform oAuth credentials, not for next token or api key or third party oauth client
    if (nextAuthToken || apiKey || isThirdPartyBearerToken) {
      return true;
    }

    if (!bearerToken && !oAuthClientId) {
      throw new ForbiddenException(
        "PermissionsGuard - no authentication provided. Provide either authorization bearer token containing managed user access token or oAuth client id in 'x-cal-client-id' header."
      );
    }

    const oAuthClient = bearerToken
      ? await this.getOAuthClientByAccessToken(bearerToken)
      : await this.getOAuthClientById(oAuthClientId);

    const hasRequiredPermissions = hasPermissions(oAuthClient.permissions, [...requiredPermissions]);

    if (!hasRequiredPermissions) {
      throw new ForbiddenException(
        `PermissionsGuard - oAuth client with id=${
          oAuthClient.id
        } does not have the required permissions=${requiredPermissions
          .map((permission) => this.oAuthClientsOutputService.transformOAuthClientPermission(permission))
          .join(
            ", "

View on GitHub (pinned to 176037d0af)

Solutions

  1. Send a valid Bearer token (managed-user access token) in the Authorization header, OR an x-cal-client-id header for a platform OAuth client.
  2. If using an API key (cal_…), confirm the prefix matches api.apiKeyPrefix (default 'cal_') so the apiKey short-circuit at line 41 fires.
  3. If using a NextAuth session, confirm NEXTAUTH_SECRET matches so getToken decodes a valid nextAuthToken.
  4. If using a third-party OAuth token, ensure getDecodedThirdPartyAccessToken recognizes it.

Example fix

// before
curl https://api.example.com/v2/atoms -H 'cal-api-version: 2024-06-14'

// after
curl https://api.example.com/v2/atoms \
  -H 'cal-api-version: 2024-06-14' \
  -H 'Authorization: Bearer <managed-user-access-token>'
Defensive patterns

Strategy: validation

Validate before calling

const bearer = request.headers['authorization'];
const clientId = request.headers['x-cal-client-id'];
if (!bearer && !clientId) {
  // client-side: tell user to provide credentials; do not send the request.
  throw new Error('Missing Authorization header and x-cal-client-id');
}

Type guard

function hasPlatformAuth(headers: Record<string,string|undefined>): boolean {
  return Boolean(headers['authorization'] || headers['x-cal-client-id']);
}

Prevention

When it happens

Trigger: Calling any platform endpoint guarded by PermissionsGuard with no Authorization header AND no x-cal-client-id header (and no clientId route param). Earlier short-circuits return true for NextAuth tokens, cal_ API keys, and recognized third-party bearer tokens.

Common situations: Client forgot the Authorization header; using x-cal-client-id but misspelled the header; curl/Postman request built without auth; SDK not configured with the OAuth client id; dev environment missing the platform client entirely.

Understand the failure class

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/8b538039dd469e3f. Report an issue: GitHub.