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
- Send the identityToken (credential.identityToken / id_token), never an access token, to the Apple OAuth endpoint
- Client-side: decode the payload (middle base64url segment) and assert a non-empty sub before submitting
- 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
- Use only Apple's identity token (id_token) for login; never access/authorization tokens
- In QA pipelines, generate tokens via Apple's flows rather than hand-built JWTs
- Client-side: assert the decoded payload has sub before calling the server
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
- Malformed identityToken: JWT must have 3 parts
- identityToken is not a valid Apple JWT or has expired
- Could not retrieve Apple public keys
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/2201213b6ee14e4a.
Report an issue: GitHub.