redis/node-redis · error · Error

Invalid token response

Error message

Invalid token response

What it means

Thrown by MSALIdentityProvider.requestToken() when the MSAL AuthenticationResult lacks accessToken or expiresOn (msal-identity-provider.ts:16). The identity provider feeds whatever the underlying MSAL/DefaultAzureCredential acquireToken returned into the token manager; a result with no usable accessToken or no expiry cannot be scheduled for refresh and is rejected as malformed.

Source

Thrown at packages/entraid/lib/msal-identity-provider.ts:17

import {
  AuthenticationResult
} from '@azure/msal-node';
import { IdentityProvider, TokenResponse } from '@redis/client/dist/lib/authx';

export class MSALIdentityProvider implements IdentityProvider<AuthenticationResult> {
  private readonly getToken: () => Promise<AuthenticationResult>;

  constructor(getToken: () => Promise<AuthenticationResult>) {
    this.getToken = getToken;
  }

  async requestToken(): Promise<TokenResponse<AuthenticationResult>> {
    const result = await this.getToken();

    if (!result?.accessToken || !result?.expiresOn) {
      throw new Error('Invalid token response');
    }
    return {
      token: result,
      ttlMs: result.expiresOn.getTime() - Date.now()
    };
  }

}

View on GitHub (pinned to bb5beb5657)

Solutions

  1. Verify scopes: client-credentials flows should use ['https://redis.azure.com/.default'].
  2. Check the service principal: client secret not expired, certificate valid and uploaded to the app registration.
  3. Confirm clientId/tenantId/authority match the app registration.
  4. Enable MSAL logging (loggerOptions.logLevel) to capture the underlying acquireToken result/error.
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check the MSAL result shape your provider returns before wrapping it.
function hasUsableToken(r: any): boolean {
  return !!r && typeof r.accessToken === 'string' && r.accessToken.length > 0 &&
    (r.expiresOn instanceof Date || typeof r.expiresOn?.getTime === 'function');
}

Type guard

function isAuthenticationResult(v: unknown): v is { accessToken: string; expiresOn: Date } {
  return typeof v === 'object' && v !== null &&
    typeof (v as any).accessToken === 'string' && (v as any).accessToken.length > 0 &&
    (v as any).expiresOn instanceof Date;
}

Try / catch

try {
  const provider = EntraIdCredentialsProviderFactory.createForClientCredentials(params);
} catch (e) {
  if (e instanceof Error && /Invalid token response/.test(e.message)) {
    // verify scopes, client secret/cert, and tenant; enable MSAL logging
  }
  throw e;
}

Prevention

When it happens

Trigger: MSAL acquireToken* resolves to a result where accessToken is null/empty or expiresOn is missing — e.g. wrong scopes requested, client secret/certificate misconfigured, the service principal disabled, or an unusual MSAL flow returning a partial result. Also triggered upstream by the `.then(x => x === null ? Promise.reject('Token is null') : x)` guards being bypassed by a non-null but empty result.

Common situations: Wrong scope string (e.g. not 'https://redis.azure.com/.default'); expired/revoked client secret; certificate thumbprint/privateKey mismatch; tenant misconfiguration returning an opaque token; MSAL cache returning a stale partial entry.

Related errors


AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03). Data as JSON: /data/errors/5a550ba95e753416.json. Report an issue: GitHub.