RocketChat/Rocket.Chat · error · Error

Insufficient data: Missing subject (sub) in auth response to

Error message

Insufficient data: Missing subject (sub) in auth response token

What it means

Thrown by handleIdentityToken when the JWT passed all cryptographic and claim checks but has no sub (subject) claim, which Rocket.Chat uses as the stable Apple user id (serviceData.id = payload.sub). Genuine Apple identity tokens always carry sub, so in practice this indicates an anomalous token: a correctly-signed-but-wrong-type JWT, an access token instead of an identity token, or a manipulated payload.

Source

Thrown at apps/meteor/server/lib/auth-providers/apple/handleIdentityToken.ts:157

}

export async function handleIdentityToken(identityToken: string, clientId: string): Promise<Record<string, any>> {
	const parts = identityToken.split('.');

	if (parts.length !== 3) {
		throw new Error('Malformed identityToken: JWT must have 3 parts');
	}

	const [headerB64, payloadB64, signatureB64] = parts;

	const payload = await verifyAppleJWT(headerB64, payloadB64, signatureB64, clientId);

	if (!payload) {
		throw new Error('identityToken is not a valid Apple JWT or has expired');
	}

	if (!payload.sub) {
		throw new Error('Insufficient data: Missing subject (sub) in auth response token');
	}

	const serviceData = {
		id: payload.sub,
		...payload,
	};

	return serviceData;
}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Send the identityToken (credential.identityToken / id_token), never an access token, to the Apple OAuth endpoint
  2. Client-side: decode the payload (middle base64url segment) and assert a non-empty sub before submitting
  3. If building your own flow, verify with Apple docs that you request the identity token scope

Example fix

// client-side guard
const parts = identityToken.split('.');
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
if (!payload.sub) throw new Error('Not an Apple identity token');
Defensive patterns

Strategy: validation

Validate before calling

function decodeJwtPayload(token: string): Record<string, unknown> {
	const payload = token.split('.')[1];
	return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
}

const payload = decodeJwtPayload(identityToken);
if (!payload.sub) {
	throw new Error('Token has no subject — send the Apple identityToken, not an access token');
}
await handleIdentityToken(identityToken, clientId);

Type guard

const hasSubject = (payload: Record<string, unknown>): payload is { sub: string } =>
	typeof payload.sub === 'string' && payload.sub.length > 0;

Try / catch

try {
	await handleIdentityToken(identityToken, clientId);
} catch (e) {
	if (e instanceof Error && e.message.includes('Missing subject')) {
		// wrong token type from the client — request the real identityToken
	} else throw e;
}

Prevention

When it happens

Trigger: Client sends an Apple access/authorization token (valid signature/iss/aud) that simply has no sub; payload stripped of claims before submission; IdP edge case where a non-identity JWT for the same audience is reused.

Common situations: Confusing token types in the Apple flow docs while building a custom client; interceptors that drop JWT payload fields; replaying tokens captured from a different Apple API.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/2201213b6ee14e4a. Report an issue: GitHub.