Ni
11/25/2025, 3:37 AMexport 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.
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!Ni
11/25/2025, 5:36 PMcache: 'no-store', in fetch fixed the issue.
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.
});finnbergquist
11/25/2025, 6:00 PMfinnbergquist
11/25/2025, 6:01 PMNi
11/25/2025, 6:52 PM