MedplumClient startClientLogin issue
# support
n
I created a function called getServiceMedplumClient for server-side auth. Before I call any api, I call this function for auth:
Copy code
export async function getServiceMedplumClient(): Promise<MedplumClient> {
  const client = new MedplumClient({ baseUrl: getMedplumBaseUrl() });
  
  try {
    await client.startClientLogin(getServiceAccountId(), getServiceAccountSecret());
    return client;
  } catch (error) {
    console.error('❌ Service client authentication failed:', error);
    throw error;
  }
}
It turns out it works when I have a new ClientApplication. After a while, the
startClientLogin
call starts to fail and returns a
Unauthorized; Token expired
error every second API call. What's bizarre is that the first API call's auth always works. It looks like the
verifyTokens
call always fails the second time. After changing my code to the below to bypass
verifyTokens
, the issue is resolved.
Copy code
export async function getServiceMedplumClient(): Promise<MedplumClient> {
const baseUrl = getMedplumBaseUrl();
  const clientId = getServiceAccountId();
  const clientSecret = getServiceAccountSecret();

  // try direct token
  const res = await fetch(`${baseUrl}/oauth2/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: clientId,
      client_secret: clientSecret,
    }),
  });
  if (!res.ok) throw new Error(`Token request failed: ${res.status} ${await res.text()}`);
  const { access_token: accessToken } = await res.json();

  return new MedplumClient({ baseUrl, accessToken });
}
I don't understand why
startClientLogin
/`verifyTokens` fails the 2nd API call but works for the first call. Could anyone point me in the right direction? Thanks!
I think I figured this out. I use Next.js for our app framework. In Next.js 14 App Router, fetch() is cached by default. Therefore, this is the Next.js issue, not Medplum issue. Adding
cache: 'no-store',
in fetch fixed the issue.
Copy code
const res = await fetch(`${baseUrl}/oauth2/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: clientId,
      client_secret: clientSecret,
    }),
    cache: 'no-store',          // Critical: disable caching; In Next.js 14 App Router, fetch() is cached by default. This is to prevent the token from being cached and used after it has expired. 
                                // In Next.js 15, fetch requests will use the no-store strategy by default. So this is may not be needed.
  });
f
Nice, glad you got it sorted out!
Appreciate you following up with thorough explanation of the issue and your fix! It is always great to have these documented for other developers that will inevitably run into the same problem in the community.
n
For sure! @finnbergquist
50 Views